1 module rpui.widgets.text_input.carriage;
2 
3 import rpui.input;
4 import rpui.events;
5 import rpui.math;
6 import rpui.widgets.text_input.edit_component;
7 import std.algorithm.comparison;
8 import std.algorithm.searching;
9 import rpui.theme;
10 
11 struct Carriage {
12     const commonSplitChars = " ,.;:?'!|/\\~*+-=(){}<>[]#%&^@$№`\""d;
13     const japanesePunctuation = "\u3000{}()[]【】、,…‥。〽「」『』〝〟〜:!?"d;
14     const splitChars = commonSplitChars ~ japanesePunctuation;
15 
16     float timer = 0;
17     int pos = 0;
18     bool visible = true;
19     const blinkThreshold = 2f;
20     vec2 absolutePosition;
21     float scrollDelta = 0.0f;
22 
23     EditComponent *editComponent;
24 
25     void attach(EditComponent *editComponent) {
26         this.editComponent = editComponent;
27     }
28 
29     void reset() {
30         pos = 0;
31         visible = true;
32         timer = 0;
33         scrollDelta = 0;
34     }
35 
36     void onProgress(in ProgressEvent event) {
37         if (!editComponent.textInput.isFocused) {
38             timer = 0;
39             return;
40         }
41 
42         timer += event.deltaTime;
43 
44         if (timer >= blinkThreshold) {
45             visible = !visible;
46             timer = 0;
47         }
48     }
49 
50     void moveCarriage(in int delta) {
51         setCarriagePos(pos + delta);
52     }
53 
54     int navigateCarriage(in int direction)
55         // in(direction == -1 || direction == 1)
56     {
57         int i = pos + direction;
58 
59         if (i <= 0 || i >= editComponent.text.length)
60             return clamp(i, 0, editComponent.text.length);
61 
62         auto skipSplitChars = splitChars.canFind(editComponent.text[i]);
63 
64         while (true) {
65             i += direction;
66 
67             if (i <= 0 || i >= editComponent.text.length)
68                 return clamp(i, 0, editComponent.text.length);
69 
70             if (splitChars.canFind(editComponent.text[i])) {
71                 if (!skipSplitChars)
72                     return direction == -1 ? i + 1 : i;
73             } else {
74                 skipSplitChars = false;
75             }
76         }
77     }
78 
79     void setCarriagePos(in int newPos) {
80         timer = 0;
81         visible = true;
82 
83         pos = clamp(newPos, 0, editComponent.text.length);
84         editComponent.selectRegion.updateSelect(pos);
85 
86         if (!isKeyPressed(KeyCode.Shift))
87             editComponent.selectRegion.stopSelection();
88     }
89 
90     void setCarriagePosWithoutCheckSelection(in int newPos) {
91         timer = 0;
92         visible = true;
93 
94         pos = clamp(newPos, 0, editComponent.text.length);
95         editComponent.selectRegion.updateSelect(pos);
96     }
97 
98     void setCarriagePosFromMousePos(in int x, in int y) {
99         const textPosition = editComponent.absoulteTextPosition.x +
100             editComponent.textInput.measure.textRelativePosition.x;
101 
102         const relativeCursorPos = x - textPosition;
103 
104         if (x > textPosition + editComponent.getTextWidth()) {
105             setCarriagePosWithoutCheckSelection(cast(int) editComponent.text.length);
106             return;
107         }
108 
109         /**
110          * Find optimal position
111          * Traverse all sizes of slice of text from [0..1, 0..2, ... , 0..text.size()-1]
112          * And get max size with condition: size <= bmax
113          */
114         for (int i = 0; i < editComponent.text.length; ++i) {
115             const sliceWidth = editComponent.getTextRegionSize(0, i);
116 
117             if (sliceWidth + 6 > relativeCursorPos) {
118                 setCarriagePosWithoutCheckSelection(i);
119                 break;
120             }
121         }
122     }
123 }