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