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 }