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