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 }