1 module pixelperfectengine.concrete.elements.listview;
2 
3 import pixelperfectengine.concrete.elements.base;
4 import pixelperfectengine.concrete.elements.scrollbar;
5 
6 import pixelperfectengine.system.etc : clamp, min, max;
7 
8 //import pixelperfectengine.system.input.types : TextInputFieldType;
9 
10 /**
11  * Defines a single item in the listview.
12  * Draws directly onto the canvas to avoit using multiple framebuffers.
13  * Can be inherited from, and that's when non-alphabetical ordering can be implemented.
14  */
15 public class ListViewItem {
16 	/**
17 	 * Defines a single field (cell) in a listview.
18 	 */
19 	public struct Field {
20 		public uint			textInputType;		///Stores various flags (constraints, etc.)
21 		public Text			text;		///Stores the text of this field if there's any.
22 		public ABitmap		bitmap;		///Custom bitmap, can be 32 bit if target enables it.
23 		/**
24 		 * Default constructor.
25 		 */
26 		this(Text text, ABitmap bitmap, uint textInputType = TextInputFieldType.None) @nogc @safe pure nothrow {
27 			this.text = text;
28 			this.bitmap = bitmap;
29 			this.textInputType = textInputType;
30 		}
31 		///Returns whether the field is editable.
32 		public @property bool editable() @nogc @safe pure nothrow const {
33 			return textInputType != TextInputFieldType.None;
34 		}
35 		public dstring getText() @safe pure nothrow {
36 			return text.toDString();
37 		}
38 	}
39 	/**
40 	 * Stores the list of items to be displayed.
41 	 */
42 	public Field[]		fields;
43 	///Height of this item.
44 	public int			height;
45 	/**
46 	 * Creates a list view item from texts.
47 	 * Parameters:
48 	 *  height = the height of the entry in pixels.
49 	 *  fields = automatically defined text input fields.
50 	 */
51 	this (int height, Text[] fields) @safe pure nothrow {
52 		this.height = height;
53 		this.fields.reserve = fields.length;
54 		foreach (Text key; fields) {
55 			this.fields ~= Field(key, null);
56 		}
57 	}
58 	/**
59 	 * Creates a ListViewItem from fields directly.
60 	 * Parameters:
61 	 *  height = the height of the entry in pixels.
62 	 *  fields = each field directly defined through a Field struct.
63 	 */
64 	this (int height, Field[] fields) @nogc @safe pure nothrow {
65 		this.height = height;
66 		this.fields = fields;
67 	}
68 	/**
69 	 * Creates a ListViewItem with default text formatting.
70 	 * Parameters:
71 	 *  height = the height of the entry in pixels.
72 	 *  ds = a string array containing the text of each field. Default formatting will be used.
73 	 */
74 	this (int height, dstring[] ds) @safe nothrow {
75 		this.height = height;
76 		fields.reserve = ds.length;
77 		foreach (dstring key ; ds) {
78 			this.fields ~= Field(new Text(key, globalDefaultStyle.getChrFormatting("ListViewItem")), null);
79 		}
80 	}
81 	/**
82 	 * Creates a ListViewItem with default text formatting and input type.
83 	 * Parameters:
84 	 *  height = the height of the entry in pixels.
85 	 *  ds = a string array containing the text of each field. Default formatting will be used.
86 	 *  inputTypes = specifies each field's input type. Mus be the same length as parameter `ds`.
87 	 */
88 	this (int height, dstring[] ds, TextInputFieldType[] inputTypes) @safe nothrow {
89 		this.height = height;
90 		fields.reserve = ds.length;
91 		assert (ds.length == inputTypes.length, "Mismatch in inputTypes and text length");
92 		for (size_t i ; i < ds.length ; i++) {
93 			Field f = Field(new Text(ds[i], globalDefaultStyle.getChrFormatting("ListViewItem")), null, inputTypes[i]);
94 			fields ~= f;
95 		}
96 	}
97 	/**
98 	 * Accesses fields like an array.
99 	 */
100 	public ref Field opIndex(size_t index) @nogc @safe pure nothrow {
101 		return fields[index];
102 	}
103 	/**
104 	 * Accesses fields like an array.
105 	 */
106 	public Field opIndexAssign(Field value, size_t index) @nogc @safe pure nothrow {
107 		fields[index] = value;
108 		return value;
109 	}
110 	///Returns the amount of fields in this item.
111 	public size_t length() @nogc @safe pure nothrow {
112 		return fields.length;
113 	}
114 	/**
115 	 * Draws the ListViewItem. Draw parameters are supplied via a nested class found in ListView.
116 	 * Parameters:
117 	 *  parent: the ListView this instance belongs to.
118 	 */
119 	public void draw(ListView parent) {
120 		StyleSheet ss = parent.drawParams.ss;
121 		Box target = parent.drawParams.target;
122 		Box t = Box(target.left, target.top, target.left, target.bottom);
123 		Point offset = Point(parent.drawParams.offsetP, parent.drawParams.offsetFR);
124 		for (int i = parent.drawParams.offsetC ; i <= parent.drawParams.targetC ; i++) {
125 			t.right = min(t.left + parent.drawParams.columnWidths[i] - offset.x, target.right);
126 			parent.drawTextSL(t.pad(ss.drawParameters["ListViewColPadding"], ss.drawParameters["ListViewRowPadding"]), 
127 					fields[i].text, offset);
128 			t.left = t.right;
129 			offset.x = 0;
130 		}
131 		parent.drawParams.target.relMove(0, height - parent.drawParams.offsetFR);
132 		parent.drawParams.offsetFR = 0;
133 	}
134 }
135 /**
136  * Defines the header of a ListView.
137  * Extended from a ListViewItem.
138  */
139 public class ListViewHeader : ListViewItem {
140 	public int[]				columnWidths;	///Width of each columns
141 	/**
142 	 * Default CTOR.
143 	 * Parameters:
144 	 *  height = the height of the header.
145 	 *  columnWidths = the width of each column. Array must match the length of the next parameter
146 	 *  fields: specifies the text of each field. Custom formatting is supported
147 	 */
148 	this(int height, int[] columnWidths, Text[] fields) @safe pure nothrow {
149 		assert (columnWidths.length == fields.length, "Length mismatch between the two arrays!");
150 		this.columnWidths = columnWidths;
151 		super(height, fields);
152 	}
153 	/**
154 	 * CTOR for creating fields with default text formatting
155 	 * Parameters:
156 	 *  height = the height of the header.
157 	 *  columnWidths = the width of each column. Array must match the length of the next parameter
158 	 */
159 	this(int height, int[] columnWidths, dstring[] ds) @safe nothrow {
160 		Text[] fields;
161 		fields.reserve = ds.length;
162 		foreach (dstring key; ds) {
163 			fields ~= new Text(key, globalDefaultStyle.getChrFormatting("ListViewHeader"));
164 		}
165 		this(height, columnWidths, fields);
166 	}
167 	/**
168 	 * Draws the header. Draw parameters are supplied via a nested class found in ListView.
169 	 * Parameters:
170 	 *  parent: the ListView this instance belongs to.
171 	 */
172 	public override void draw(ListView parent) {
173 		if (!height) return;
174 		StyleSheet ss = parent.drawParams.ss;
175 		Box target = parent.drawParams.target;
176 		Box t = Box(target.left, target.top, target.left, target.bottom);
177 		t.bottom = min (t.bottom, parent.getPosition. bottom);
178 		Point offset = Point(parent.drawParams.offsetP, 0);
179 		for (int i = parent.drawParams.offsetC ; i <= parent.drawParams.targetC ; i++) {
180 			t.right = min(t.left + parent.drawParams.columnWidths[i] - offset.x, target.right);
181 			if (!offset.x) {
182 				parent.drawLine(t.cornerUL, t.cornerLL, ss.getColor("windowascent"));
183 			}
184 			if (t.left + parent.drawParams.columnWidths[i] < target.right) {
185 				Point from = t.cornerUR, to = t.cornerLR;
186 				from.x = from.x - 1;
187 				to.x = to.x - 1;
188 				parent.drawLine(from, to, ss.getColor("windowdescent"));
189 			}
190 			with (parent) {
191 				drawLine(t.cornerUL, t.cornerUR, ss.getColor("windowascent"));
192 				drawLine(t.cornerLL, t.cornerLR, ss.getColor("windowdescent"));
193 				drawTextSL(t.pad(ss.drawParameters["ListViewColPadding"], ss.drawParameters["ListViewRowPadding"]), fields[i].text, 
194 						offset);
195 			}
196 			t.left = t.right;
197 			offset.x = 0;
198 		}
199 		parent.drawParams.target.relMove(0, height);
200 	}
201 }
202 /**
203  * Implements a basic ListView 
204  */
205 public class ListView : WindowElement, ElementContainer, TextInputListener {
206 	///Supplies draw parameters to the items
207 	public class DrawParameters {
208 		///StyleSheet that is being used currently
209 		StyleSheet 				ss;
210 		///Contains the reference to the header's columnWidth attribute
211 		int[]					columnWidths;
212 		///The first column to be drawn
213 		const int				offsetC;
214 		///The last column to be drawn
215 		const int				targetC;
216 		///Offset in pixels for the first column
217 		const int				offsetP;
218 		///Offset of the first row. Should be set to zero after the first row has been drawn.
219 		int						offsetFR;
220 		///The prelimiter where the item should be drawn.
221 		Box						target;
222 		///CTOR
223 		this (StyleSheet ss, int[] columnWidths, const int offsetC, const int targetC, const int offsetP, 
224 				int offsetFR) @safe @nogc pure nothrow {
225 			this.ss = ss;
226 			this.columnWidths = columnWidths;
227 			this.offsetC = offsetC;
228 			this.targetC = targetC;
229 			this.offsetP = offsetP;
230 			this.offsetFR = offsetFR;
231 		}
232 	}
233 	protected HorizScrollBar	horizSlider;	///Horizontal scroll bar.
234 	protected VertScrollBar		vertSlider;		///Vertical scroll bar.
235 	///The header of the ListView. 
236 	///Accessed in a safe manner to ensure it's being updated on the output raster.
237 	protected ListViewHeader	_header;
238 	///Entries in the ListView.
239 	///Accessed in a safe manner to ensure it's being updated on the output raster and that the number of columns match.
240 	protected ListViewItem[]	entries;
241 	protected int				selection;		///Selected item's number, or -1 if none selected.
242 	protected int				hSelection;		///Horizontal selection for text editing.
243 	protected int				tselect;		///Lenght of selected characters.
244 	protected int				cursorPos;		///Position of cursor.
245 	protected int				horizTextOffset;///Horizontal text offset if text cannot fit the cell.
246 	public int					hScrollSpeed = 1;///Horizontal scrolling speed.
247 	public int					vScrollSpeed = 8;///Vertical scrolling speed.
248 	///Text editing area.
249 	protected Box				textArea;
250 	///Filters the input to the cell if not null.
251 	protected InputFilter		filter;
252 	///Holds shared draw parameters that are used when the element is being drawn.
253 	///Should be set to null otherwise.
254 	public DrawParameters		drawParams;
255 	///Called when an item is selected
256 	public EventDeleg			onItemSelect;
257 	///Called when text input is finished and accepted
258 	///Event value is `CellEditEvent`
259 	public EventDeleg			onTextInput;
260 	protected static enum	EDIT_EN = 1<<9;
261 	protected static enum	MULTICELL_EDIT_EN = 1<<10;
262 	protected static enum	TEXTINPUT_EN = 1<<11;
263 	protected static enum	INSERT = 1<<12;
264 	/**
265 	 * Creates an instance of a ListView with the supplied parameters.
266 	 * Parameters:
267 	 *  header: Specifies an initial header for the element. Null if there's none.
268 	 *  entries: Specifies initial entries for the element. Null if there're none.
269 	 *  source: Sets all event output's source parameter.
270 	 *  position: Tells where the element should be drawn on the window.
271 	 */
272 	public this(ListViewHeader header, ListViewItem[] entries, string source, Box position) {
273 		_header = header;
274 		this.entries = entries;
275 		this.source = source;
276 		this.position = position;
277 		recalculateTotalSizes();
278 	}
279 	/**
280 	 * Accesses data entries in a safe manner.
281 	 */
282 	public ListViewItem opIndex(size_t index) @nogc @safe pure nothrow {
283 		return entries[index];
284 		/+scope(exit) {
285 			assert(entries[index].length == _header.length, "Column number mismatch error!");
286 		}+/
287 	}
288 	/**
289 	 * Accesses data entries in a safe manner.
290 	 */
291 	public ListViewItem opIndexAssign(ListViewItem value, size_t index) @safe pure {
292 		if (value.length == _header.length) {
293 			if (entries.length == index) {
294 				entries ~= value;
295 			} else {
296 				entries[index] = value;
297 			}
298 		} else throw new Exception("Column number mismatch!");
299 		return value;
300 	}
301 	/**
302 	 * Allows to append a single element to the entry list.
303 	 */
304 	public ListViewItem opOpAssign(string op)(ListViewItem value) {
305 		static if (op == "~" || op == "+") {
306 			if (value.length == _header.length) {
307 				entries ~= value;
308 			} else throw new Exception("Column number mismatch!");
309 		} else static assert (0, "Unsupported operator!");
310 		return value;
311 	}
312 	/**
313 	 * Allows to append multiple elements to the entry list.
314 	 */
315 	public ListViewItem[] opOpAssign(string op)(ListViewItem[] value) {
316 		static if (op == "~" || op == "+") {
317 			foreach (ListViewItem key; value) {
318 				if (key.length == _header.length) {
319 					entries ~= key;
320 				} else throw new Exception("Column number mismatch!");
321 			}
322 		} else static assert (0, "Unsupported operator!");
323 		return value;
324 	}
325 	override public void draw() {
326 		StyleSheet ss = getStyleSheet;
327 		if (flags & TEXTINPUT_EN) { //only redraw the editing cell in this case
328 			const int textPadding = ss.drawParameters["TextSpacingSides"];
329 			
330 			clearArea(textArea);
331 			//drawBox(position, ss.getColor("windowascent"));
332 			
333 			//draw cursor
334 			//if (flags & ENABLE_TEXT_EDIT) {
335 			//calculate cursor first
336 			Box cursor = Box(textArea.left + textPadding, textArea.top + textPadding, textArea.left + textPadding, 
337 					textArea.bottom - textPadding);
338 			cursor.left += text.getWidth(0, cursorPos) - horizTextOffset;
339 			//cursor must be at least single pixel wide
340 			cursor.right = cursor.left;
341 			if (tselect) {
342 				cursor.right += text.getWidth(cursorPos, cursorPos + tselect);
343 			} else if (flags & INSERT) {
344 				if (cursorPos < text.charLength) cursor.right += text.getWidth(cursorPos, cursorPos+1);
345 				else cursor.right += text.font.chars(' ').xadvance;
346 			} else {
347 				cursor.right++;
348 			}
349 			//Clamp down if cursor is wider than the text editing area
350 			cursor.right = cursor.right <= textArea.right - textPadding ? cursor.right : textArea.right - textPadding;
351 			//Draw cursor
352 			parent.drawFilledBox(cursor, ss.getColor("selection"));
353 
354 			//}
355 			//draw text
356 			parent.drawTextSL(textArea - textPadding, text, Point(horizTextOffset, 0));
357 		} else {
358 			parent.clearArea(position);
359 
360 			parent.drawBox(position, ss.getColor("windowascent"));
361 			Point upper = Point(0, position.top + _header.height);
362 			Point lower = Point(0, position.bottom);
363 			{	///Calculate first column stuff
364 				int offsetP, offsetC, targetC, targetP;
365 				if (horizSlider) {
366 					offsetP = horizSlider.value();
367 					for (; _header.columnWidths[offsetC] < offsetP ; offsetC++) {
368 						offsetP -= _header.columnWidths[offsetC];
369 					}
370 					offsetP = max(0, offsetP);
371 					///Calculate last column number
372 					targetP = horizSlider.value() + position.width;
373 					for (; _header.columnWidths.length > targetC && _header.columnWidths[targetC] < targetP ; targetC++) {
374 						targetP -= _header.columnWidths[targetC];
375 					}
376 					targetC = min(cast(int)(_header.columnWidths.length) - 1, targetC);
377 					lower.y -= horizSlider.getPosition().height;
378 				} else {
379 					targetC = cast(int)_header.columnWidths.length - 1;
380 				}
381 				drawParams = new DrawParameters(ss, _header.columnWidths, offsetC, targetC, offsetP, 0);
382 			}
383 
384 			drawParams.target = Box(position.left, position.top, position.right, position.top + _header.height);
385 
386 			if (vertSlider) {
387 				drawParams.target.right -= vertSlider.getPosition.width;
388 
389 			}
390 
391 			_header.draw(this);
392 			/+Point upper = Point(drawParams.columnWidths[drawParams.offsetC] + position.left, position.top + _header.height);
393 			Point lower = Point(upper.x, position.bottom);+/
394 			int firstRow, lastRow;
395 			if (vertSlider) {
396 				int pixelsTotal = vertSlider.value();
397 				for (; entries[firstRow].height < pixelsTotal ; firstRow++) {
398 					pixelsTotal -= entries[firstRow].height;
399 				}
400 				drawParams.offsetFR = pixelsTotal;
401 				pixelsTotal += position.height;
402 				pixelsTotal -= _header.height;
403 				if (horizSlider) pixelsTotal -= horizSlider.getPosition().height;
404 				lastRow = firstRow;
405 				for (; entries.length > lastRow && entries[lastRow].height < pixelsTotal ; lastRow++) {
406 					pixelsTotal -= entries[lastRow].height;
407 				}
408 				lastRow = min(cast(int)(entries.length) - 1, lastRow);
409 			} else {
410 				lastRow = cast(int)entries.length - 1;
411 			}
412 
413 			for (int i = firstRow ; i <= lastRow ; i++) {
414 				if (drawParams.target.bottom > position.bottom)
415 					drawParams.target.bottom = position.bottom;
416 				if (ss.getColor("ListViewHSep") && i != lastRow) {
417 					parent.drawLine(drawParams.target.cornerLL, drawParams.target.cornerLR, ss.getColor("ListViewHSep"));
418 				}
419 				if (selection == i) {
420 					Box target = drawParams.target - 1;
421 					target.bottom -= drawParams.offsetFR;
422 					parent.drawFilledBox(target, ss.getColor("selection"));
423 				}
424 				entries[i].draw(this);	
425 			}
426 
427 			if (ss.getColor("ListViewVSep")) {
428 				for (int i = drawParams.offsetC ; i <= drawParams.targetC ; i++) {
429 					upper.x = drawParams.columnWidths[i];
430 					lower.x = drawParams.columnWidths[i];
431 					parent.drawLine(upper, lower, ss.getColor("ListViewVSep"));
432 				}
433 			}
434 			if (horizSlider) horizSlider.draw;
435 			if (vertSlider) vertSlider.draw;
436 
437 			drawParams = null;
438 		}
439 		if (onDraw !is null) {
440 			onDraw();
441 		}
442 	}
443 	/** 
444 	 * Returns the numfer of entries the ListView has.
445 	 */
446 	public @property size_t numEntries() @nogc @safe pure nothrow const {
447 		return entries.length;
448 	}
449 	/**
450 	 * Returns the number of the selected item.
451 	 */
452 	public @property int value() @nogc @safe pure nothrow const {
453 		return selection;
454 	}
455 	/**
456 	 * Sets the selected item and then does a redraw.
457 	 * -1 sets selection to none.
458 	 */
459 	public @property int value(int val) {
460 		selection = val;
461 		clamp(val, -1, cast(int)(entries.length) - 1);
462 		draw;
463 		return selection;
464 	}
465 	/** 
466 	 * Returns the currently selected element, or null if none is selected.
467 	 */
468 	public @property ListViewItem selectedElement() @nogc @safe pure nothrow {
469 		if (selection >= 0)
470 			return entries[selection];
471 		else
472 			return null;
473 	}
474 	/**
475 	 * Enables or disables the text editing of this element.
476 	 */
477 	public @property bool editEnable(bool val) @nogc @safe pure nothrow {
478 		if (val) flags |= EDIT_EN;
479 		else flags &= ~EDIT_EN;
480 		return flags & EDIT_EN ? true : false;
481 	}
482 	/**
483 	 * Returns true if text editing is enabled.
484 	 */
485 	public @property bool editEnable() @nogc @safe pure nothrow const {
486 		return flags & EDIT_EN ? true : false;
487 	}
488 	/**
489 	 * Enables or disables editing for multiple cells.
490 	 * If disabled, the first cell with editing enabled will be able to be edited.
491 	 */
492 	public @property bool multicellEditEnable(bool val) @nogc @safe pure nothrow {
493 		if (val) flags |= MULTICELL_EDIT_EN;
494 		else flags &= ~MULTICELL_EDIT_EN;
495 		return flags & MULTICELL_EDIT_EN ? true : false;
496 	}
497 	/**
498 	 * Returns true if text editing for multiple cells is enabled.
499 	 */
500 	public @property bool multicellEditEnable() @nogc @safe pure nothrow const {
501 		return flags & MULTICELL_EDIT_EN ? true : false;
502 	}
503 	/**
504 	 * Sets a new header, also able to supply new entries.
505 	 */
506 	public void setHeader(ListViewHeader header, ListViewItem[] entries) {
507 		_header = header;
508 		foreach (ListViewItem key; entries) {
509 			assert(key.length == header.length);
510 		}
511 		this.entries = entries;
512 		refresh();
513 		draw();
514 	}
515 	/**
516 	 * Removes an item from the entries.
517 	 * Returns the removed entry.
518 	 */
519 	public ListViewItem removeEntry(size_t index) {
520 		import std.algorithm.mutation : remove;
521 		ListViewItem result = entries[index];
522 		entries = entries.remove(index);
523 		if (selection >= entries.length) selection--;
524 		//draw;
525 		return result;
526 	}
527 	/** 
528 	 * Inserts an element at the given index.
529 	 * Params:
530 	 *   index = Where the new element should be inserted.
531 	 *   item = The item to be inserted.
532 	 * Returns: The inserted element, or null if out of bounds.
533 	 */
534 	public ListViewItem insertAt(size_t index, ListViewItem item) {
535 		if (!index)
536 			entries = item ~ entries;
537 		else if (entries.length > index)
538 			entries = entries[0..index] ~ item ~ entries[index..$];
539 		else if (entries.length == index)
540 			entries ~= item;
541 		else
542 			return null;
543 		return item;
544 	}
545 	public int[2] scroll() {
546 		int[2] result;
547 		if (horizSlider !is null)
548 			result[0] = horizSlider.value;
549 		if (vertSlider !is null)
550 			result[1] = vertSlider.value;
551 		return result;
552 	}
553 	public int[2] scroll(int[2] pos) {
554 		if (horizSlider !is null)
555 			horizSlider.value = pos[0];
556 		if (vertSlider !is null)
557 			vertSlider.value = pos[1];
558 		return scroll();
559 	}
560 	/**
561 	 * Removes all entries in the list.
562 	 */
563 	public void clear() @safe {
564 		entries.length = 0;
565 	}
566 	/**
567 	 * Refreshes the list view.
568 	 * Must be called every time when adding new items is finished.
569 	 */
570 	public void refresh() {
571 		selection = -1;
572 		recalculateTotalSizes;
573 		draw;
574 	}
575 	/**
576 	 * Recalculates the total width and height of the list view's field, also generates scrollbars if needed.
577 	 */
578 	protected void recalculateTotalSizes() {
579 		int totalWidth, totalHeight;
580 		foreach (i ; _header.columnWidths) {
581 			totalWidth += i;
582 		}
583 		foreach (ListViewItem key; entries) {
584 			totalHeight += key.height;
585 		}
586 		totalHeight += _header.height;
587 		StyleSheet ss = getStyleSheet();
588 		bool needsVSB, needsHSB;
589 		if (totalWidth > position.width) 
590 			needsHSB = true;
591 		if (totalHeight > position.height)
592 			needsVSB = true;
593 		if (needsVSB && totalWidth > position.width - ss.drawParameters["VertScrollBarSize"])
594 			needsHSB = true;
595 		if (needsHSB && totalHeight > position.height - ss.drawParameters["HorizScrollBarSize"])
596 			needsVSB = true;
597 		totalHeight -= _header.height;
598 		if (needsVSB) {
599 			const int maxvalue = needsHSB ? totalHeight - (position.height - ss.drawParameters["HorizScrollBarSize"]
600 					- _header.height) : totalHeight - (position.height - _header.height);
601 			
602 			const Box target = Box(position.right - ss.drawParameters["HorizScrollBarSize"] + 2, position.top, 
603 					position.right, needsHSB ? position.bottom - ss.drawParameters["VertScrollBarSize"] : position.bottom);
604 			vertSlider = new VertScrollBar(maxvalue, source ~ "VSB", target);
605 			vertSlider.setParent(this);
606 			vertSlider.onScrolling = &scrollBarEventOut;
607 		} else vertSlider = null;
608 		if (needsHSB){
609 			const int maxvalue = needsVSB ? totalWidth - (position.width - ss.drawParameters["VertScrollBarSize"]) : 
610 					totalWidth - position.width;
611 			const Box target = Box(position.left, position.bottom - ss.drawParameters["VertScrollBarSize"] + 2, 
612 					needsVSB ? position.right - ss.drawParameters["HorizScrollBarSize"] : position.right,
613 					position.bottom);
614 			horizSlider = new HorizScrollBar(maxvalue, source ~ "VSB", target);
615 			horizSlider.setParent(this);
616 			horizSlider.onScrolling = &scrollBarEventOut;
617 		} else horizSlider = null;
618 	}
619 	protected void scrollBarEventOut(Event ev) {
620 		draw;
621 	}
622 	/**
623 	 * Returns the absolute position of the element.
624 	 */
625 	public Box getAbsolutePosition(WindowElement sender) {
626 		return parent.getAbsolutePosition(sender);
627 	}
628 	/**
629 	 * Gives focus to the element if applicable
630 	 */
631 	public void requestFocus(WindowElement sender) {
632 		
633 	}
634 	/**
635 	 * Sets the cursor to the given type on request.
636 	 */
637 	public void requestCursor(CursorType type) {
638 
639 	}
640 	///Draws a line.
641 	public void drawLine(Point from, Point to, ubyte color) @trusted {
642 		parent.drawLine(from, to, color);
643 	}
644 	///Draws a line pattern.
645 	public void drawLinePattern(Point from, Point to, ubyte[] pattern) @trusted {
646 		parent.drawLinePattern(from, to, pattern);
647 	}
648 	///Draws an empty rectangle.
649 	public void drawBox(Box target, ubyte color) @trusted {
650 		parent.drawBox(target, color);
651 	}
652 	///Draws an empty rectangle with line patterns.
653 	public void drawBoxPattern(Box target, ubyte[] pattern) @trusted {
654 		parent.drawBoxPattern(target, pattern);
655 	}
656 	///Draws a filled rectangle with a specified color.
657 	public void drawFilledBox(Box target, ubyte color) @trusted {
658 		parent.drawFilledBox(target, color);
659 	}
660 	///Pastes a bitmap to the given point using blitter, which threats color #0 as transparency.
661 	public void bitBLT(Point target, ABitmap source) @trusted {
662 		parent.bitBLT(target, source);
663 	}
664 	///Pastes a slice of a bitmap to the given point using blitter, which threats color #0 as transparency.
665 	public void bitBLT(Point target, ABitmap source, Box slice) @trusted {
666 		parent.bitBLT(target, source, slice);
667 	}
668 	///Pastes a repeated bitmap pattern over the specified area.
669 	public void bitBLTPattern(Box target, ABitmap pattern) @trusted {
670 		parent.bitBLTPattern(target, pattern);
671 	}
672 	///XOR blits a repeated bitmap pattern over the specified area.
673 	public void xorBitBLT(Box target, ABitmap pattern) @trusted {
674 		parent.xorBitBLT(target, pattern);
675 	}
676 	///XOR blits a color index over a specified area.
677 	public void xorBitBLT(Box target, ubyte color) @trusted {
678 		parent.xorBitBLT(target, color);
679 	}
680 	///Fills an area with the specified color.
681 	public void fill(Point target, ubyte color, ubyte background = 0) @trusted {
682 		parent.fill(target, color, background);
683 	}
684 	///Draws a single line text within the given prelimiter.
685 	public void drawTextSL(Box target, Text text, Point offset) @trusted {
686 		parent.drawTextSL(target, text, offset);
687 	}
688 	///Draws a multi line text within the given prelimiter.
689 	public void drawTextML(Box target, Text text, Point offset) @trusted {
690 		parent.drawTextML(target, text, offset);
691 	}
692 	///Clears the area within the target
693 	public void clearArea(Box target) @trusted {
694 		parent.clearArea(target);
695 	}
696 	///Passes mouse click event
697 	public override void passMCE(MouseEventCommons mec, MouseClickEvent mce) {
698 		///TODO: Handle mouse click when in text editing mode
699 		if (state != ElementState.Enabled) return;
700 		if (vertSlider) {
701 			const Box p = vertSlider.getPosition();
702 			if (p.isBetween(mce.x, mce.y)) {
703 				vertSlider.passMCE(mec, mce);
704 				return;
705 			}
706 		}
707 		if (horizSlider) {
708 			const Box p = horizSlider.getPosition();
709 			if (p.isBetween(mce.x, mce.y)) {
710 				horizSlider.passMCE(mec, mce);
711 				return;
712 			}
713 		}
714 
715 		//if (mce.button != MouseButton.Left && !mce.state) return;
716 
717 		mce.x -= position.left;
718 		mce.y -= position.top;
719 		if (entries.length && mce.y > _header.height && mce.button == MouseButton.Left && mce.state) {
720 			textArea.top = position.top;
721 			textArea.left = position.left;
722 			mce.y -= _header.height;
723 			int pixelsTotal = mce.y, pos;
724 			if (vertSlider) ///calculate outscrolled area
725 				pixelsTotal += vertSlider.value;
726 			while (pos < entries.length) {
727 				if (pixelsTotal > entries[pos].height) {
728 					pixelsTotal -= entries[pos].height;
729 					textArea.top += entries[pos].height;
730 					if (pos + 1 < entries.length)
731 						pos++;
732 				} else {
733 					break;
734 				}
735 			}
736 			if (pos >= entries.length) {
737 				selection = -1;
738 			} else if (selection == pos && (flags & EDIT_EN)) {
739 				//Calculate horizontal selection for Multicell editing if needed
740 				/+if (flags & MULTICELL_EDIT_EN) {
741 				
742 				} else {+/
743 				textArea.top += _header.height;
744 				foreach (size_t i, ListViewItem.Field f ; entries[selection].fields) {
745 					if (f.editable) {
746 						hSelection = cast(int)i;
747 						
748 						
749 						with (textArea) {
750 							bottom = entries[selection].height + textArea.top;
751 							right = _header.columnWidths[i] + textArea.left;
752 							left = max(textArea.left, position.left);
753 							top = max(textArea.top, position.top);
754 							right = min(textArea.right, position.right);
755 							bottom = min(textArea.bottom, position.bottom);
756 						}
757 						text = new Text(entries[selection][hSelection].text);
758 						cursorPos = 0;
759 						tselect = cast(int)text.charLength;
760 						//oldText = text;
761 						if (flags & MULTICELL_EDIT_EN) {
762 							if (textArea.left < mce.x && textArea.right > mce.x) {
763 								if (vertSlider) {
764 									textArea.top -= vertSlider.value;
765 									textArea.bottom = entries[selection].height + textArea.top;
766 								}
767 								if (horizSlider) textArea.left -= horizSlider.value;
768 								inputHandler.startTextInput(this);
769 								break;
770 							}
771 						} else {
772 							if (vertSlider) {
773 								textArea.top -= vertSlider.value;
774 								textArea.bottom = entries[selection].height + textArea.top;
775 							}
776 							if (horizSlider) textArea.left -= horizSlider.value;
777 							inputHandler.startTextInput(this);
778 							break;
779 						}
780 						
781 					}
782 					textArea.left += _header.columnWidths[i];
783 				}
784 				//}
785 				selection = pos;
786 			} else 
787 				selection = pos;
788 
789 			if (onItemSelect !is null && selection != -1)
790 				onItemSelect(new Event(this, entries[selection], EventType.Selection, SourceType.WindowElement));
791 		} else if (!entries.length) {
792 			selection = -1;
793 		} else {
794 			super.passMCE(mec, mce);
795 		}
796 		
797 		draw();
798 	}
799 	///Passes mouse move event
800 	public override void passMME(MouseEventCommons mec, MouseMotionEvent mme) {
801 		if (state != ElementState.Enabled) return;
802 		if (vertSlider) {
803 			const Box p = vertSlider.getPosition();
804 			if (p.isBetween(mme.x, mme.y)) {
805 				mme.x -= p.left - position.left;
806 				mme.y -= p.top - position.top;
807 				vertSlider.passMME(mec, mme);
808 				return;
809 			}
810 		}
811 		if (horizSlider) {
812 			const Box p = horizSlider.getPosition();
813 			if (p.isBetween(mme.x, mme.y)) {
814 				mme.x -= p.left - position.left;
815 				mme.y -= p.top - position.top;
816 				horizSlider.passMME(mec, mme);
817 				return;
818 			}
819 		}
820 	}
821 	///Passes mouse scroll event
822 	public override void passMWE(MouseEventCommons mec, MouseWheelEvent mwe) {
823 		mwe.x *= hScrollSpeed;
824 		mwe.y *= vScrollSpeed;
825 		if (state != ElementState.Enabled) return;
826 		if (horizSlider) horizSlider.passMWE(mec, mwe);
827 		if (vertSlider) vertSlider.passMWE(mec, mwe);
828 	}
829 	/**
830 	 * Puts a PopUpElement on the GUI.
831 	 */
832 	public void addPopUpElement(PopUpElement p) {
833 		parent.addPopUpElement(p);
834 	}
835 	/**
836 	 * Puts a PopUpElement on the GUI at the given position.
837 	 */
838 	public void addPopUpElement(PopUpElement p, int x, int y) {
839 		parent.addPopUpElement(p, x, y);
840 	}
841 	/** 
842 	 * Ends the popup session and closes all popups.
843 	 */
844 	public void endPopUpSession(PopUpElement p) {
845 		parent.endPopUpSession(p);
846 	}
847 	/**
848 	 * Closes a single popup element.
849 	 */
850 	public void closePopUp(PopUpElement p) {
851 		parent.closePopUp(p);
852 	}
853 	//Interface `TextInputListener` starts here
854 	/**
855 	 * Passes the inputted text to the target, alongside with a window ID and a timestamp.
856 	 */
857 	public void textInputEvent(uint timestamp, uint windowID, dstring text) {
858 		import pixelperfectengine.system.etc : removeUnallowedSymbols;
859 		/+if (allowedChars.length) {
860 			text = removeUnallowedSymbols(text, allowedChars);
861 			if (!text.length) return;
862 		}+/
863 		if (tselect) {
864 			this.text.removeChar(cursorPos, tselect);
865 			tselect = 0;
866 			for(int j ; j < text.length ; j++){
867 				this.text.insertChar(cursorPos++, text[j]);
868 			}
869 		} else if (flags & INSERT) {
870 			for(int j ; j < text.length ; j++){
871 				this.text.overwriteChar(cursorPos++, text[j]);
872 			}
873 		} else {
874 			for(int j ; j < text.length ; j++){
875 				this.text.insertChar(cursorPos++, text[j]);
876 			}
877 		}
878 		if (filter) {
879 			dstring s = this.text.text;
880 			filter.use(s);
881 			this.text.text = s;
882 			cursorPos = min(cursorPos, cast(uint)this.text.charLength);
883 		}
884 		const int textPadding = getStyleSheet.drawParameters["TextSpacingSides"];
885 		const Coordinate textPos = Coordinate(textPadding,(position.height / 2) - (this.text.font.size / 2) ,
886 				position.width,position.height - textPadding);
887 		const int x = this.text.getWidth(), cursorPixelPos = this.text.getWidth(0, cursorPos);
888 		if(x > textPos.width) {
889 			 if(cursorPos == this.text.text.length) {
890 				horizTextOffset = x - textPos.width;
891 			 } else if(cursorPixelPos < horizTextOffset) { //Test for whether the cursor would fall out from the current text area
892 				horizTextOffset = cursorPixelPos;
893 			 } else if(cursorPixelPos > horizTextOffset + textPos.width) {
894 				horizTextOffset = horizTextOffset + textPos.width;
895 			 }
896 		}
897 		draw();
898 	}
899 	/**
900 	 * Passes text editing events to the target, alongside with a window ID and a timestamp.
901 	 */
902 	public void textEditingEvent(uint timestamp, uint windowID, dstring text, int start, int length) {
903 		for (int i ; i < length ; i++) {
904 			this.text.overwriteChar(start + i, text[i]);
905 		}
906 		cursorPos = start + length;
907 	}
908 	/**
909 	 * Passes text input key events to the target, e.g. cursor keys.
910 	 */
911 	public void textInputKeyEvent(uint timestamp, uint windowID, TextInputKey key, ushort modifier) {
912 		switch(key) {
913 			case TextInputKey.Enter:
914 				entries[selection][hSelection].text = text;
915 				inputHandler.stopTextInput();
916 				if(onTextInput !is null)
917 					onTextInput(new CellEditEvent(this, entries[selection], selection, hSelection));
918 					//onTextInput(new Event(source, null, null, null, text, 0, EventType.T, null, this));
919 				break;
920 			case TextInputKey.Escape:
921 				//text = oldText;
922 				inputHandler.stopTextInput();
923 				
924 
925 				break;
926 			case TextInputKey.Backspace:
927 				if(cursorPos > 0){
928 					deleteCharacter(cursorPos - 1);
929 					cursorPos--;
930 					draw();
931 				}
932 				break;
933 			case TextInputKey.Delete:
934 				if (tselect) {
935 					for (int i ; i < tselect ; i++) {
936 						deleteCharacter(cursorPos);
937 					}
938 					tselect = 0;
939 				} else {
940 					deleteCharacter(cursorPos);
941 				}
942 				draw();
943 				break;
944 			case TextInputKey.CursorLeft:
945 				if (modifier != KeyModifier.Shift) {
946 					tselect = 0;
947 					if(cursorPos > 0){
948 						--cursorPos;
949 						draw();
950 					}
951 				}
952 				break;
953 			case TextInputKey.CursorRight:
954 				if (modifier != KeyModifier.Shift) {
955 					tselect = 0;
956 					if(cursorPos < text.charLength){
957 						++cursorPos;
958 						draw();
959 					}
960 				}
961 				break;
962 			case TextInputKey.Home:
963 				if (modifier != KeyModifier.Shift) {
964 					tselect = 0;
965 					cursorPos = 0;
966 					draw();
967 				}
968 				break;
969 			case TextInputKey.End:
970 				if (modifier != KeyModifier.Shift) {
971 					tselect = 0;
972 					cursorPos = cast(int)text.charLength;
973 					draw();
974 				}
975 				break;
976 			case TextInputKey.Insert:
977 				flags ^= INSERT;
978 				draw();
979 				break;
980 			default:
981 				break;
982 		}
983 	}
984 	/**
985 	 * When called, the listener should drop all text input.
986 	 */
987 	public void dropTextInput() {
988 		flags &= ~TEXTINPUT_EN;
989 		draw;
990 	}
991 	/**
992 	 * Called if text input should be initialized.
993 	 */
994 	public void initTextInput() {
995 		flags |= TEXTINPUT_EN;
996 		ListViewItem.Field f = opIndex(selection)[hSelection];
997 		switch(f.textInputType) {
998 			default:
999 				filter = null;
1000 				break;
1001 			case TextInputFieldType.ASCIIText:
1002 				filter = new ASCIITextFilter();
1003 				break;
1004 			case TextInputFieldType.Decimal:
1005 				filter = new DecimalFilter!true();
1006 				break;
1007 			case TextInputFieldType.Integer:
1008 				filter = new IntegerFilter!true();
1009 				break;
1010 			case TextInputFieldType.DecimalP:
1011 				filter = new DecimalFilter!false();
1012 				break;
1013 			case TextInputFieldType.IntegerP:
1014 				filter = new IntegerFilter!false();
1015 				break;
1016 			case TextInputFieldType.Hex:
1017 				filter = new HexadecimalFilter();
1018 				break;
1019 			case TextInputFieldType.Oct:
1020 				break;
1021 			case TextInputFieldType.Bin:
1022 				break;
1023 		}
1024 	}
1025 	private void deleteCharacter(size_t n){
1026 		text.removeChar(n);
1027 	}
1028 }