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 }