1 module rpui.widgets.text_input.transforms_system;
2 
3 import std.container.array;
4 import std.math;
5 
6 import rpui.events;
7 import rpui.widgets.text_input.widget;
8 import rpui.render.components_factory;
9 import rpui.render.components;
10 import rpui.render.transforms;
11 import rpui.theme;
12 import rpui.widgets.text_input.render_system;
13 import rpui.math;
14 import rpui.primitives;
15 import rpui.widget;
16 
17 struct RenderTransforms {
18     vec2 focusOffsets;
19     float focusResize;
20     float selectRegionHeight;
21     vec2 selectRegionOffset;
22     vec2 arrowOffsets;
23     float prefixMargin;
24     float postfixMargin;
25     float softPostfixMargin;
26     float arrowAreaSize;
27     float softPostfixWidth;
28 
29     HorizontalChainTransforms background;
30     HorizontalChainTransforms focusGlow;
31     QuadTransforms carriage;
32     UiTextTransforms text;
33     UiTextTransforms prefix;
34     UiTextTransforms postfix;
35     QuadTransforms selectRegion;
36     QuadTransforms leftArrow;
37     QuadTransforms rightArrow;
38 }
39 
40 final class TextInputTransformsSystem : TransformsSystem {
41     private RenderTransforms* transforms;
42     private TextInput widget;
43     private Theme theme;
44     private RenderData* renderData;
45 
46     this(TextInput widget, RenderData* renderData, RenderTransforms* transforms) {
47         this.widget = widget;
48         this.theme = widget.view.theme;
49         this.renderData = renderData;
50         this.transforms = transforms;
51     }
52 
53     override void onProgress(in ProgressEvent event) {
54         updateBackground();
55         updateCarriage();
56         updateTextPosition();
57         updatePrefix();
58         updatePostfix();
59         updateText();
60         updateSoftPostfix();
61         updateSelectRegion();
62         updateArrows();
63     }
64 
65     private void updateBackground() {
66         transforms.background = updateHorizontalChainTransforms(
67             renderData.background.widths,
68             widget.view.cameraView,
69             widget.absolutePosition,
70             widget.size,
71             widget.partDraws
72         );
73 
74         if (widget.focusable && widget.isFocused) {
75             transforms.focusGlow = updateHorizontalChainTransforms(
76                 renderData.background.widths,
77                 widget.view.cameraView,
78                 widget.absolutePosition + transforms.focusOffsets,
79                 widget.size + vec2(transforms.focusResize),
80                 widget.partDraws
81             );
82         }
83     }
84 
85     private void updateCarriage() {
86          transforms.carriage = updateQuadTransforms(
87             widget.view.cameraView,
88             widget.editComponent.carriage.absolutePosition,
89             renderData.carriage.texCoords.originalTexCoords.size
90         );
91     }
92 
93     private void updateArrows() {
94         if (widget.isNumberMode()) {
95             transforms.arrowAreaSize = widget.measure.arrowsAreaWidth;
96         } else {
97             transforms.arrowAreaSize = 0;
98         }
99 
100         renderData.leftArrow.state = widget.numberInputTypeComponent.leftArrow.state;
101         renderData.rightArrow.state = widget.numberInputTypeComponent.rightArrow.state;
102 
103         updateArrowAbsolutePositions();
104 
105         transforms.leftArrow = updateQuadTransforms(
106             widget.view.cameraView,
107             widget.numberInputTypeComponent.leftArrow.absolutePosition,
108             renderData.leftArrow.currentTexCoords.originalTexCoords.size
109         );
110 
111         transforms.rightArrow = updateQuadTransforms(
112             widget.view.cameraView,
113             widget.numberInputTypeComponent.rightArrow.absolutePosition,
114             renderData.rightArrow.currentTexCoords.originalTexCoords.size
115         );
116     }
117 
118     private void updateArrowAbsolutePositions() {
119         with (widget.numberInputTypeComponent) {
120             leftArrow.absolutePosition = widget.absolutePosition + transforms.arrowOffsets;
121 
122             const rightArrowWidth = renderData.rightArrow.currentTexCoords.originalTexCoords.size.x;
123             const rightArrowOffsets = vec2(
124                 widget.size.x - rightArrowWidth - transforms.arrowOffsets.x,
125                 transforms.arrowOffsets.y
126             );
127 
128             rightArrow.absolutePosition = widget.absolutePosition + rightArrowOffsets;
129         }
130     }
131 
132     private void updateText() {
133         with (renderData.text.attrs[widget.state]) {
134             caption = widget.editComponent.text;
135             textAlign = widget.textAlign;
136         }
137 
138         vec2 textOffset = vec2(0);
139 
140         if (widget.textAlign == Align.left) {
141             textOffset.x += widget.measure.prefixWidth;
142         }
143 
144         if (widget.textAlign == Align.right) {
145             textOffset.x -= widget.measure.postfixWidth;
146         }
147 
148         if (widget.softPostfix && widget.textAlign == Align.center) {
149             textOffset.x -= transforms.softPostfixWidth / 2;
150         }
151 
152         if (widget.softPostfix && widget.textAlign == Align.right) {
153             textOffset.x -= transforms.softPostfixWidth + transforms.softPostfixMargin;
154         }
155 
156         transforms.text = updateUiTextTransforms(
157             &renderData.text.render,
158             &theme.regularFont,
159             transforms.text,
160             renderData.text.attrs[widget.state],
161             widget.view.cameraView,
162             widget.editComponent.absoulteTextPosition + textOffset,
163             widget.size
164         );
165 
166         widget.measure.textWidth = transforms.text.size.x;
167         widget.measure.lineHeight = transforms.text.size.y;
168         widget.measure.textRelativePosition = vec2(transforms.text.relativePosition.x, 0) + textOffset;
169     }
170 
171     private void updatePrefix() {
172         if (widget.prefix == "") {
173             widget.measure.prefixWidth = 0;
174             return;
175         }
176 
177         with (renderData.prefix.attrs[widget.state]) {
178             caption = widget.prefix;
179             textAlign = Align.left;
180         }
181 
182         transforms.prefix = updateUiTextTransforms(
183             &renderData.prefix.render,
184             &theme.regularFont,
185             transforms.prefix,
186             renderData.prefix.attrs[widget.state],
187             widget.view.cameraView,
188             widget.absolutePosition + vec2(widget.measure.textLeftMargin + transforms.arrowAreaSize, 0),
189             widget.size
190         );
191 
192         widget.measure.prefixWidth = transforms.prefix.size.x + transforms.prefixMargin +
193             transforms.arrowAreaSize;
194     }
195 
196     private void updatePostfix() {
197         if (widget.postfix == "" || widget.softPostfix) {
198             widget.measure.postfixWidth = 0;
199             return;
200         }
201 
202         with (renderData.postfix.attrs[widget.state]) {
203             caption = widget.postfix;
204             textAlign = Align.right;
205         }
206 
207         transforms.postfix = updateUiTextTransforms(
208             &renderData.postfix.render,
209             &theme.regularFont,
210             transforms.postfix,
211             renderData.postfix.attrs[widget.state],
212             widget.view.cameraView,
213             widget.absolutePosition - vec2(widget.measure.textRightMargin + transforms.arrowAreaSize, 0),
214             widget.size
215         );
216 
217         widget.measure.postfixWidth = transforms.postfix.size.x + transforms.postfixMargin +
218             transforms.arrowAreaSize;
219     }
220 
221     private void updateSoftPostfix() {
222         if (widget.postfix == "" || !widget.softPostfix) {
223             return;
224         }
225 
226         with (renderData.postfix.attrs[widget.state]) {
227             caption = widget.postfix;
228             textAlign = Align.left;
229         }
230 
231         float textOffset = widget.measure.textWidth + transforms.softPostfixMargin +
232             transforms.text.relativePosition.x;
233 
234         switch (widget.textAlign) {
235             case Align.left:
236                 textOffset += widget.measure.prefixWidth;
237                 break;
238 
239             case Align.right:
240                 textOffset -= transforms.softPostfixWidth + transforms.softPostfixMargin;
241                 break;
242 
243             case Align.center:
244                 textOffset -= transforms.softPostfixWidth / 2;
245                 break;
246 
247             default:
248                 break;
249         }
250 
251         transforms.postfix = updateUiTextTransforms(
252             &renderData.postfix.render,
253             &theme.regularFont,
254             transforms.postfix,
255             renderData.postfix.attrs[widget.state],
256             widget.view.cameraView,
257             widget.editComponent.absoulteTextPosition + vec2(textOffset, 0),
258             widget.size
259         );
260 
261         transforms.softPostfixWidth = transforms.postfix.size.x;
262         widget.measure.postfixWidth = 0;
263     }
264 
265     private void updateTextPosition() {
266         auto textPosition = widget.absolutePosition;
267 
268         if (widget.textAlign == Align.left) {
269             textPosition.x += widget.measure.textLeftMargin + widget.editComponent.scrollDelta;
270         }
271         else if (widget.textAlign == Align.right) {
272             textPosition.x -= widget.measure.textRightMargin - widget.editComponent.scrollDelta;
273         }
274 
275         widget.editComponent.absoulteTextPosition = textPosition;
276     }
277 
278     package float getRegionTextWidth(in int start, in int end) {
279         auto attrs = renderData.text.attrs[widget.state];
280         attrs.caption = widget.editComponent.text[start .. end];
281         return getUiTextBounds(&renderData.text.render, &theme.regularFont, attrs).x;
282     }
283 
284     private void updateSelectRegion() {
285         with (widget.editComponent) {
286             if (!selectRegion.textIsSelected())
287                 return;
288 
289             selectRegion.clampSelectRegion();
290             const regionSize = getRegionTextWidth(selectRegion.start, selectRegion.end);
291 
292             selectRegion.size = vec2(regionSize, transforms.selectRegionHeight);
293             selectRegion.absolutePosition = widget.absolutePosition +
294                 getTextRegionOffset(selectRegion.start) +
295                 transforms.selectRegionOffset;
296 
297             transforms.selectRegion = updateQuadTransforms(
298                 widget.view.cameraView,
299                 selectRegion.absolutePosition,
300                 selectRegion.size
301             );
302         }
303     }
304 }