1 module pixelperfectengine.concrete.elements.textbox; 2 3 public import pixelperfectengine.concrete.elements.base; 4 5 /** 6 * Text input box. 7 */ 8 public class TextBox : WindowElement, TextInputListener { 9 //protected bool enableEdit, insert; 10 protected static enum INSERT = 1<<9; 11 protected static enum ENABLE_TEXT_EDIT = 1<<10; 12 protected size_t cursorPos; 13 protected int horizTextOffset, select; 14 protected Text oldText; 15 ///Contains an input filter. or null if no filter is used. 16 protected InputFilter filter; 17 //public int brush, textpos; 18 //public TextInputHandler tih; 19 public void delegate(Event ev) onTextInput; 20 public this(dstring text, string source, Coordinate coordinates) { 21 this(new Text(text, getStyleSheet().getChrFormatting("textBox")), source, coordinates); 22 } 23 public this(Text text, string source, Coordinate coordinates) { 24 position = coordinates; 25 this.text = text; 26 this.source = source; 27 //inputHandler.addTextInputListener(source, this); 28 //insert = true; 29 //draw(); 30 } 31 /** 32 * Sets an external input filter. 33 */ 34 public void setFilter (InputFilter f) { 35 filter = f; 36 } 37 /** 38 * Sets an internal input filter 39 */ 40 public void setFilter (TextInputFieldType t) { 41 final switch (t) with(TextInputFieldType) { 42 case None: 43 filter = null; 44 break; 45 case Text: 46 filter = null; 47 break; 48 case ASCIIText: 49 break; 50 case Decimal: 51 filter = new DecimalFilter!true(); 52 break; 53 case Integer: 54 filter = new IntegerFilter!true(); 55 break; 56 case DecimalP: 57 filter = new DecimalFilter!false(); 58 break; 59 case IntegerP: 60 filter = new IntegerFilter!false(); 61 break; 62 case Hex: 63 break; 64 case Oct: 65 break; 66 case Bin: 67 break; 68 } 69 } 70 ///Called when an object loses focus. 71 public void focusLost() { 72 flags &= ~IS_FOCUSED; 73 dropTextInput(); 74 inputHandler.stopTextInput(); 75 76 } 77 public override void passMCE(MouseEventCommons mec, MouseClickEvent mce) { 78 if (!(flags & ENABLE_TEXT_EDIT)) inputHandler.startTextInput(this, false, position); 79 super.passMCE(mec, mce); 80 } 81 public override void draw(){ 82 if (parent is null) return; 83 StyleSheet ss = getStyleSheet(); 84 const int textPadding = ss.drawParameters["TextSpacingSides"]; 85 with (parent) { 86 clearArea(position); 87 drawBox(position, ss.getColor("windowascent")); 88 } 89 //draw cursor 90 if (flags & ENABLE_TEXT_EDIT) { 91 //calculate cursor first 92 Box cursor = Box(position.left + textPadding, position.top + textPadding, position.left + textPadding, position.bottom - textPadding); 93 cursor.left += text.getWidth(0, cursorPos) - horizTextOffset; 94 //cursor must be at least single pixel wide 95 cursor.right = cursor.left; 96 if (select) { 97 cursor.right += text.getWidth(cursorPos, cursorPos + select); 98 } else if (flags & INSERT) { 99 if (cursorPos < text.charLength) cursor.right += text.getWidth(cursorPos, cursorPos+1); 100 else cursor.right += text.font.chars(' ').xadvance; 101 } else { 102 cursor.right++; 103 } 104 //Clamp down if cursor is wider than the text editing area 105 cursor.right = cursor.right <= position.right - textPadding ? cursor.right : position.right - textPadding; 106 //Draw cursor 107 parent.drawFilledBox(cursor, ss.getColor("selection")); 108 109 } 110 //draw text 111 parent.drawTextSL(position - textPadding, text, Point(horizTextOffset, 0)); 112 if (isFocused) { 113 parent.drawBoxPattern(position - textPadding, ss.pattern["blackDottedLine"]); 114 } 115 116 if (state == ElementState.Disabled) { 117 parent.bitBLTPattern(position, ss.getImage("ElementDisabledPtrn")); 118 } 119 120 if (onDraw !is null) { 121 onDraw(); 122 } 123 } 124 /** 125 * Passes text editing events to the target, alongside with a window ID and a timestamp. 126 */ 127 public void textEditingEvent(uint timestamp, uint windowID, dstring text, int start, int length) { 128 for (int i ; i < length ; i++) { 129 this.text.overwriteChar(start + i, text[i]); 130 } 131 cursorPos = start + length; 132 } 133 private void deleteCharacter(size_t n){ 134 text.removeChar(n); 135 } 136 public void textInputEvent(uint timestamp, uint windowID, dstring text) { 137 import pixelperfectengine.system.etc : removeUnallowedSymbols; 138 if (filter) { 139 filter.use(text); 140 if (!text.length) return; 141 } 142 if (select) { 143 this.text.removeChar(cursorPos, select); 144 select = 0; 145 for(int j ; j < text.length ; j++){ 146 this.text.insertChar(cursorPos++, text[j]); 147 } 148 } else if (flags & INSERT) { 149 for(int j ; j < text.length ; j++){ 150 this.text.overwriteChar(cursorPos++, text[j]); 151 } 152 } else { 153 for(int j ; j < text.length ; j++){ 154 this.text.insertChar(cursorPos++, text[j]); 155 } 156 } 157 const int textPadding = getStyleSheet.drawParameters["TextSpacingSides"]; 158 const Coordinate textPos = Coordinate(textPadding,(position.height / 2) - (this.text.font.size / 2) , 159 position.width,position.height - textPadding); 160 const int x = this.text.getWidth(), cursorPixelPos = this.text.getWidth(0, cursorPos); 161 if(x > textPos.width) { 162 if(cursorPos == this.text.text.length) { 163 horizTextOffset = x - textPos.width; 164 } else if(cursorPixelPos < horizTextOffset) { //Test for whether the cursor would fall out from the current text area 165 horizTextOffset = cursorPixelPos; 166 } else if(cursorPixelPos > horizTextOffset + textPos.width) { 167 horizTextOffset = horizTextOffset + textPos.width; 168 } 169 } 170 draw(); 171 } 172 173 public void dropTextInput() { 174 flags &= ~ENABLE_TEXT_EDIT; 175 horizTextOffset = 0; 176 cursorPos = 0; 177 //inputHandler.stopTextInput(source); 178 draw(); 179 //invokeActionEvent(EventType.TEXTINPUT, 0, text); 180 /+if(onTextInput !is null) 181 onTextInput(new Event(source, null, null, null, text, 0, EventType.TEXTINPUT, null, this));+/ 182 } 183 public void initTextInput() { 184 flags |= ENABLE_TEXT_EDIT; 185 select = cast(int)text.charLength; 186 oldText = new Text(text); 187 draw(); 188 } 189 190 public void textInputKeyEvent(uint timestamp, uint windowID, TextInputKey key, ushort modifier = 0){ 191 switch(key) { 192 case TextInputKey.Enter: 193 inputHandler.stopTextInput(); 194 if(onTextInput !is null) 195 onTextInput(new Event(this, text, EventType.TextInput, SourceType.WindowElement)); 196 //onTextInput(new Event(source, null, null, null, text, 0, EventType.T, null, this)); 197 break; 198 case TextInputKey.Escape: 199 text = oldText; 200 inputHandler.stopTextInput(); 201 202 203 break; 204 case TextInputKey.Backspace: 205 if (select) { 206 for (int i ; i < select ; i++) { 207 deleteCharacter(cursorPos - 1); 208 cursorPos--; 209 } 210 select = 0; 211 } else if (cursorPos > 0) { 212 deleteCharacter(cursorPos - 1); 213 cursorPos--; 214 draw(); 215 } 216 break; 217 case TextInputKey.Delete: 218 if (select) { 219 for (int i ; i < select ; i++) { 220 deleteCharacter(cursorPos); 221 } 222 select = 0; 223 } else { 224 deleteCharacter(cursorPos); 225 } 226 draw(); 227 break; 228 case TextInputKey.CursorLeft: 229 if (modifier != KeyModifier.Shift) { 230 select = 0; 231 if(cursorPos > 0){ 232 --cursorPos; 233 draw(); 234 } 235 } 236 break; 237 case TextInputKey.CursorRight: 238 if (modifier != KeyModifier.Shift) { 239 select = 0; 240 if(cursorPos < text.charLength){ 241 ++cursorPos; 242 draw(); 243 } 244 } 245 break; 246 case TextInputKey.Home: 247 if (modifier != KeyModifier.Shift) { 248 select = 0; 249 cursorPos = 0; 250 draw(); 251 } 252 break; 253 case TextInputKey.End: 254 if (modifier != KeyModifier.Shift) { 255 select = 0; 256 cursorPos = text.charLength; 257 draw(); 258 } 259 break; 260 case TextInputKey.Insert: 261 flags ^= INSERT; 262 draw(); 263 break; 264 default: 265 break; 266 } 267 } 268 }