1 /** 2 Input field 3 4 Copyright: (c) Enalye 2017 5 License: Zlib 6 Authors: Enalye 7 */ 8 9 module atelier.ui.inputfield; 10 11 import std.utf; 12 import std.conv : to; 13 import std..string : indexOf; 14 import atelier.core, atelier.render, atelier.common; 15 import atelier.ui.gui_element, atelier.ui.label; 16 17 /// Editable field. 18 class InputField : GuiElement { 19 private { 20 Label _label; 21 Color _borderColor; 22 float _caretAlpha = 1f; 23 dstring _text, _allowedCharacters; 24 Timer _timer; 25 uint _caretIndex = 0U, _selectionIndex = 0u; 26 Vec2f _caretPosition = Vec2f.zero, _selectionPosition = Vec2f.zero; 27 uint _limit = 80u; 28 } 29 30 @property { 31 /// The displayed text. 32 string text() const { 33 return to!string(_text); 34 } 35 /// Ditto 36 string text(string text_) { 37 _text = to!dstring(text_); 38 _caretIndex = to!uint(_text.length); 39 _selectionIndex = _caretIndex; 40 _label.text = text_; 41 return text_; 42 } 43 44 /// Label font 45 Font font() { 46 return _label.font; 47 } 48 /// Ditto 49 Font font(Font font_) { 50 return _label.font = font_; 51 } 52 53 /// Max number of characters. 54 uint limit() const { 55 return _limit; 56 } 57 /// Ditto 58 uint limit(uint newLimit) { 59 _limit = newLimit; 60 if (_text.length > _limit) { 61 _text.length = _limit; 62 _label.text = to!string(_text); 63 } 64 if (_caretIndex > _limit) 65 _caretIndex = _limit; 66 return _limit; 67 } 68 } 69 70 /// Color of the inputfield's selection area 71 Color selectionColor = Color(.23f, .30f, .37f); 72 /// Color of the inputfield's cursor 73 Color caretColor = Color.white; 74 75 /// The size is used to setup a canvas, avoid resizing too often. \ 76 /// Set startWithFocus to true if you want the inputfield to accept inputs immediatly. 77 this(Vec2f size_, string defaultText = "", bool startWithFocus = false) { 78 size = size_; 79 _label = new Label(defaultText, new TrueTypeFont(veraMonoFontData)); 80 _label.setAlign(GuiAlignX.left, GuiAlignY.center); 81 appendChild(_label); 82 hasFocus = startWithFocus; 83 _text = to!dstring(defaultText); 84 _caretIndex = to!uint(_text.length); 85 _selectionIndex = _caretIndex; 86 87 _borderColor = Color.white; 88 _caretAlpha = 1f; 89 _timer.mode = Timer.Mode.bounce; 90 _timer.start(1f); 91 hasCanvas(true); 92 } 93 94 private void insertText(dstring textInput) { 95 if (_caretIndex == _selectionIndex) { 96 if (_caretIndex == _text.length) 97 _text ~= textInput; 98 else if (_caretIndex == 0U) 99 _text = textInput ~ _text; 100 else 101 _text = _text[0U .. _caretIndex] ~ textInput ~ _text[_caretIndex .. $]; 102 _caretIndex += textInput.length; 103 } 104 else { 105 const int minSelect = min(_caretIndex, _selectionIndex); 106 const int maxSelect = max(_caretIndex, _selectionIndex); 107 if (minSelect == 0U && maxSelect == _text.length) { 108 _text = textInput; 109 _caretIndex = cast(uint) _text.length; 110 } 111 else if (minSelect == 0U) { 112 _text = textInput ~ _text[maxSelect .. $]; 113 _caretIndex = cast(uint) textInput.length; 114 } 115 else if (maxSelect == _text.length) { 116 _text = _text[0U .. minSelect] ~ textInput; 117 _caretIndex = cast(uint) _text.length; 118 } 119 else { 120 _text = _text[0U .. minSelect] ~ textInput ~ _text[maxSelect .. $]; 121 _caretIndex = minSelect + cast(uint) textInput.length; 122 } 123 } 124 _label.text = to!string(_text); 125 _selectionIndex = _caretIndex; 126 triggerCallback(); 127 } 128 129 private void removeSelection(int direction) { 130 if (_text.length) { 131 if (_caretIndex == _selectionIndex) { 132 if (direction > 0) { 133 if (_caretIndex == 0U) 134 _text = _text[1U .. $]; 135 else if (_caretIndex != _text.length) { 136 _text = _text[0U .. _caretIndex] ~ _text[_caretIndex + 1 .. $]; 137 } 138 } 139 else { 140 if (_caretIndex == _text.length) { 141 _text.length--; 142 _caretIndex--; 143 } 144 else if (_caretIndex != 0U) { 145 _text = _text[0U .. _caretIndex - 1] ~ _text[_caretIndex .. $]; 146 _caretIndex--; 147 } 148 } 149 } 150 else { 151 const int minSelect = min(_caretIndex, _selectionIndex); 152 const int maxSelect = max(_caretIndex, _selectionIndex); 153 if (minSelect == 0 && maxSelect == _text.length) { 154 _text.length = 0; 155 _caretIndex = 0; 156 } 157 else if (minSelect == 0) { 158 _text = _text[maxSelect .. $]; 159 _caretIndex = 0; 160 } 161 else if (maxSelect == _text.length) { 162 _text = _text[0 .. minSelect]; 163 _caretIndex = minSelect; 164 } 165 else { 166 _text = _text[0 .. minSelect] ~ _text[maxSelect .. $]; 167 _caretIndex = minSelect; 168 } 169 } 170 _label.text = to!string(_text); 171 _selectionIndex = _caretIndex; 172 } 173 triggerCallback(); 174 } 175 176 private string getSelection() { 177 dstring txt = to!dstring(_label.text); 178 if (_selectionIndex == _caretIndex || (txt.length == 0)) { 179 return ""; 180 } 181 const int minIndex = min(_selectionIndex, _caretIndex); 182 const int maxIndex = max(_selectionIndex, _caretIndex); 183 txt = txt[minIndex .. maxIndex]; 184 return to!string(txt); 185 } 186 187 override void onEvent(Event event) { 188 if (hasFocus) { 189 switch (event.type) with (Event.Type) { 190 case keyInput: 191 if (_caretIndex >= _limit) 192 break; 193 const auto textInput = to!dstring(event.input.text); 194 if (_allowedCharacters.length) { 195 if (indexOf(_allowedCharacters, textInput) == -1) 196 break; 197 } 198 insertText(textInput); 199 break; 200 case keyDown: 201 switch (event.key.button) with (KeyButton) { 202 case right: 203 if (_caretIndex < _text.length) 204 _caretIndex++; 205 if (!isButtonDown(KeyButton.leftShift) && !isButtonDown(KeyButton.rightShift)) { 206 _selectionIndex = _caretIndex; 207 } 208 break; 209 case left: 210 if (_caretIndex > 0U) 211 _caretIndex--; 212 if (!isButtonDown(KeyButton.leftShift) && !isButtonDown(KeyButton.rightShift)) { 213 _selectionIndex = _caretIndex; 214 } 215 break; 216 case remove: 217 removeSelection(1); 218 break; 219 case backspace: 220 removeSelection(-1); 221 break; 222 case end: 223 _caretIndex = cast(uint) _text.length; 224 if (!isButtonDown(KeyButton.leftShift) && !isButtonDown(KeyButton.rightShift)) { 225 _selectionIndex = _caretIndex; 226 } 227 break; 228 case home: 229 _caretIndex = 0; 230 if (!isButtonDown(KeyButton.leftShift) && !isButtonDown(KeyButton.rightShift)) { 231 _selectionIndex = _caretIndex; 232 } 233 break; 234 case v: 235 if (isButtonDown(KeyButton.leftControl) || isButtonDown(KeyButton.rightControl)) { 236 if (hasClipboard()) { 237 insertText(to!dstring(getClipboard())); 238 } 239 } 240 break; 241 case c: 242 if (isButtonDown(KeyButton.leftControl) || isButtonDown(KeyButton.rightControl)) { 243 setClipboard(getSelection()); 244 } 245 break; 246 case x: 247 if (isButtonDown(KeyButton.leftControl) || isButtonDown(KeyButton.rightControl)) { 248 setClipboard(getSelection()); 249 removeSelection(0); 250 } 251 break; 252 case a: 253 if (isButtonDown(KeyButton.leftControl) || isButtonDown(KeyButton.rightControl)) { 254 _caretIndex = cast(uint) _text.length; 255 _selectionIndex = 0U; 256 } 257 break; 258 default: 259 break; 260 } 261 break; 262 default: 263 break; 264 } 265 } 266 } 267 268 override void update(float deltaTime) { 269 _caretPosition = Vec2f(_label.position.x + (_label.size.x / _text.length) * _caretIndex, 270 _label.origin.y); 271 _selectionPosition = Vec2f(_label.position.x + ( 272 _label.size.x / _text.length) * _selectionIndex, _label.origin.y); 273 _label.position = Vec2f(10f, 0f); 274 275 if (_caretPosition.x > canvas.position.x + canvas.size.x / 2f - 10f) 276 canvas.position.x = _caretPosition.x - canvas.size.x / 2f + 10f; 277 else if (_caretPosition.x < canvas.position.x - canvas.size.x / 2f + 10f) 278 canvas.position.x = _caretPosition.x + canvas.size.x / 2f - 10f; 279 280 if (hasFocus) 281 _borderColor = lerp(_borderColor, Color.white, deltaTime * .25f); 282 else 283 _borderColor = lerp(_borderColor, Color.white * .21f, deltaTime * .1f); 284 285 _timer.update(deltaTime); 286 _caretAlpha = lerp(1f, .21f, _timer.value01); 287 } 288 289 override void draw() { 290 if (_text.length && hasFocus) { 291 if (_caretIndex != _selectionIndex) { 292 const float minPos = min(_selectionPosition.x, _caretPosition.x); 293 const float selectionSize = abs(_selectionPosition.x - _caretPosition.x); 294 drawFilledRect(Vec2f(minPos, _selectionPosition.y), 295 Vec2f(selectionSize, _label.size.y), selectionColor, .7f); 296 } 297 drawFilledRect(_caretPosition, Vec2f(2f, _label.size.y), caretColor, _caretAlpha); 298 } 299 } 300 301 override void drawOverlay() { 302 drawRect(origin, size, _borderColor); 303 } 304 305 /// Resets the text. 306 void clear() { 307 _text.length = 0L; 308 _caretIndex = 0u; 309 _selectionIndex = 0u; 310 _label.text = ""; 311 } 312 313 /// It will discard any characters from keyboard that aren't in this list. 314 void setAllowedCharacters(dstring allowedCharacters) { 315 _allowedCharacters = allowedCharacters; 316 } 317 }