1 module rpui.widgets.text_input.widget; 2 3 import rpui.cursor; 4 import rpui.events; 5 import rpui.widget_events; 6 import rpui.input; 7 import rpui.math; 8 import rpui.primitives; 9 import rpui.widget; 10 import rpui.platform; 11 import rpui.widgets.text_input.renderer; 12 import rpui.widgets.text_input.edit_component; 13 import rpui.widgets.text_input.number_input_type_component; 14 15 class TextInput : Widget { 16 enum InputType { text, integer, number } 17 18 @field Align textAlign = Align.left; 19 @field bool autoSelectOnFocus = false; 20 @field float maxValue = float.max; 21 @field float minValue = float.min_normal; 22 @field float numberStep = 1f; 23 @field utf32string prefix = ""; 24 @field utf32string postfix = ""; 25 @field bool softPostfix = false; 26 @field bool displaySlider = true; 27 28 private InputType inputType_ = InputType.text; 29 30 @field 31 @property InputType inputType() { return inputType_; } 32 @property void inputType(in InputType val) { 33 inputType_ = val; 34 35 if (val == InputType.integer || val == InputType.number) { 36 focusOnMousUp = true; 37 autoSelectOnFocus = true; 38 } else { 39 focusOnMousUp = false; 40 } 41 } 42 43 @field 44 @property utf32string text() { return editComponent.text; } 45 46 @property void text(in utf32string value) { 47 editComponent.text = value; 48 } 49 50 @property utf32string selectedText() { 51 return editComponent.text[editComponent.selectRegion.start .. editComponent.selectRegion.end]; 52 } 53 54 package struct Measure { 55 float textWidth = 0; 56 float lineHeight = 0; 57 float textTopMargin; 58 float textLeftMargin; 59 float textRightMargin; 60 float prefixWidth = 0; 61 float postfixWidth = 0; 62 float carriageBoundary; 63 vec2 textRelativePosition = vec2(0); 64 float arrowsAreaWidth = 0; 65 } 66 67 package Measure measure; 68 package EditComponent editComponent; 69 package NumberInputTypeComponent numberInputTypeComponent; 70 71 this(in string style = "TextInput") { 72 super(style); 73 this.drawChildren = false; 74 this.renderer = new TextInputRenderer(); 75 this.drawChildren = false; 76 77 size = vec2(50, 21); 78 } 79 80 override void onProgress(in ProgressEvent event) { 81 editComponent.carriage.onProgress(event); 82 updateCarriagePostion(); 83 updateScroll(); 84 85 if (isNumberMode()) { 86 // updateArrowAbsolutePositions(); 87 numberInputTypeComponent.updateArrowStates(); 88 } 89 90 locator.updateAbsolutePosition(); 91 locator.updateLocationAlign(); 92 locator.updateVerticalLocationAlign(); 93 locator.updateRegionAlign(); 94 95 renderer.onProgress(event); 96 } 97 98 override void onCreate() { 99 super.onCreate(); 100 101 loadMeasure(); 102 editComponent.carriage.attach(&editComponent); 103 numberInputTypeComponent.attach(this); 104 105 if (isNumberMode()) { 106 textAlign = Align.center; 107 } 108 109 events.subscribe!CopyCommand(&onCopy); 110 events.subscribe!CutCommand(&onCut); 111 events.subscribe!PasteCommand(&onPaste); 112 events.subscribe!UnselectCommand(&onUnselect); 113 events.subscribe!SelectAllCommand(&onSelectAll); 114 } 115 116 private void onCopy(in CopyCommand command) { 117 if (!isFocused) { 118 return; 119 } 120 121 platformSetClipboardTextUtf32(selectedText); 122 editComponent.unselect(); 123 } 124 125 private void onCut(in CutCommand command) { 126 if (!isFocused) { 127 return; 128 } 129 130 if (selectedText.length == 0) 131 return; 132 133 platformSetClipboardTextUtf32(selectedText); 134 editComponent.removeSelectedRegion(); 135 events.notify(ChangeEvent()); 136 } 137 138 private void onPaste(in PasteCommand command) { 139 if (!isFocused) { 140 return; 141 } 142 143 const text = platformGetClipboardTextUtf32(); 144 145 if (editComponent.enterText(text)) 146 events.notify(ChangeEvent()); 147 } 148 149 private void onUnselect(in UnselectCommand command) { 150 if (!isFocused) { 151 return; 152 } 153 154 editComponent.unselect(); 155 } 156 157 private void onSelectAll(in SelectAllCommand command) { 158 if (!isFocused) { 159 return; 160 } 161 162 editComponent.selectAll(); 163 } 164 165 private void loadMeasure() { 166 with (view.theme.tree) { 167 measure.textLeftMargin = data.getNumber(style ~ ".textLeftMargin.0"); 168 measure.textRightMargin = data.getNumber(style ~ ".textRightMargin.0"); 169 measure.textTopMargin = data.getNumber(style ~ ".textTopMargin.0"); 170 measure.carriageBoundary = data.getNumber(style ~ ".carriageBoundary.0"); 171 measure.arrowsAreaWidth = data.getNumber(style ~ ".arrowsAreaWidth.0"); 172 } 173 } 174 175 override void updateSize() { 176 super.updateSize(); 177 updateCarriagePostion(); 178 179 if (isNumberMode()) { 180 // updateArrowAbsolutePositions(); 181 numberInputTypeComponent.updateArrowStates(); 182 } 183 } 184 185 override void onBlur(in BlurEvent event) { 186 editComponent.reset(); 187 editComponent.onBlur(); 188 } 189 190 override void onFocus(in FocusEvent event) { 191 editComponent.onFocus(); 192 193 if (autoSelectOnFocus && !isFocused) 194 editComponent.selectAll(); 195 } 196 197 package bool isNumberMode() { 198 return inputType == InputType.integer || inputType == InputType.number; 199 } 200 201 /// Change system cursor when mouse entering to arrows. 202 override void onCursor() { 203 if (isFocused && (isEnter || isClick)) { 204 view.cursor = CursorIcon.iBeam; 205 } 206 207 if (numberInputTypeComponent.leftArrow.isEnter) { 208 view.cursor = CursorIcon.normal; 209 } 210 else if (numberInputTypeComponent.rightArrow.isEnter) { 211 view.cursor = CursorIcon.normal; 212 } 213 } 214 215 private void updateScroll() { 216 if (textAlign == Align.center) { 217 editComponent.scrollDelta = 0; 218 return; 219 } 220 221 const rightBorder = absolutePosition.x + size.x - measure.textRightMargin - measure.postfixWidth; 222 const leftBorder = absolutePosition.x + measure.textLeftMargin + measure.prefixWidth; 223 const padding = measure.textRightMargin + measure.textLeftMargin + 224 measure.postfixWidth + measure.prefixWidth; 225 const regionOffset = editComponent.getTextRegionSize(0, editComponent.carriage.pos); 226 const textSize = cast(float) measure.textWidth; 227 const minScroll = -textSize + size.x - padding; 228 229 if (editComponent.scrollDelta <= minScroll) 230 editComponent.scrollDelta = minScroll; 231 232 if (textSize + padding < size.x) { 233 editComponent.scrollDelta = 0; 234 } 235 else if (editComponent.carriage.absolutePosition.x > rightBorder) { 236 editComponent.scrollDelta = -regionOffset + size.x - padding; 237 } 238 else if (editComponent.carriage.absolutePosition.x < leftBorder) { 239 editComponent.scrollDelta = -regionOffset; 240 } 241 242 updateCarriagePostion(); 243 } 244 245 private void updateCarriagePostion() { 246 editComponent.carriage.absolutePosition = absolutePosition + 247 editComponent.getTextRegionOffset(editComponent.carriage.pos); 248 } 249 250 // Events ------------------------------------------------------------------------------------------ 251 252 override void onTextEntered(in TextEnteredEvent event) { 253 if (!isFocused) 254 return; 255 256 if (editComponent.onTextEntered(event)) 257 events.notify(ChangeEvent()); 258 } 259 260 override void onKeyPressed(in KeyPressedEvent event) { 261 if (!isFocused) 262 return; 263 264 editComponent.onKeyPressed(event); 265 } 266 267 override void onMouseDown(in MouseDownEvent event) { 268 if (isEnter) { 269 editComponent.onMouseDown(event); 270 } 271 272 if (isNumberMode()) 273 numberInputTypeComponent.onMouseDown(event); 274 } 275 276 override void onMouseUp(in MouseUpEvent event) { 277 if (isNumberMode()) 278 numberInputTypeComponent.onMouseUp(event); 279 } 280 281 override void onMouseMove(in MouseMoveEvent event) { 282 if (isEnter) 283 editComponent.onMouseMove(event); 284 285 if (isNumberMode()) 286 numberInputTypeComponent.onMouseMove(event); 287 } 288 289 override void onDblClick(in DblClickEvent event) { 290 if (isEnter) 291 editComponent.onDblClick(event); 292 } 293 294 override void onTripleClick(in TripleClickEvent event) { 295 if (isEnter) 296 editComponent.onTripleClick(event); 297 } 298 }