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 if (parent is null) return;
327 StyleSheet ss = getStyleSheet;
328 if (flags & TEXTINPUT_EN) { //only redraw the editing cell in this case
329 const int textPadding = ss.drawParameters["TextSpacingSides"];
330
331 clearArea(textArea);
332 //drawBox(position, ss.getColor("windowascent"));
333
334 //draw cursor
335 //if (flags & ENABLE_TEXT_EDIT) {
336 //calculate cursor first
337 Box cursor = Box(textArea.left + textPadding, textArea.top + textPadding, textArea.left + textPadding,
338 textArea.bottom - textPadding);
339 cursor.left += text.getWidth(0, cursorPos) - horizTextOffset;
340 //cursor must be at least single pixel wide
341 cursor.right = cursor.left;
342 if (tselect) {
343 cursor.right += text.getWidth(cursorPos, cursorPos + tselect);
344 } else if (flags & INSERT) {
345 if (cursorPos < text.charLength) cursor.right += text.getWidth(cursorPos, cursorPos+1);
346 else cursor.right += text.font.chars(' ').xadvance;
347 } else {
348 cursor.right++;
349 }
350 //Clamp down if cursor is wider than the text editing area
351 cursor.right = cursor.right <= textArea.right - textPadding ? cursor.right : textArea.right - textPadding;
352 //Draw cursor if it doesn't fall out of bounds
353 if (cursor.left < position.right && cursor.right < position.right)
354 parent.drawFilledBox(cursor, ss.getColor("selection"));
355
356 //}
357 //draw text
358 parent.drawTextSL(textArea - textPadding, text, Point(horizTextOffset, 0));
359 } else {
360 parent.clearArea(position);
361
362 parent.drawBox(position, ss.getColor("windowascent"));
363 Point upper = Point(0, position.top + _header.height);
364 Point lower = Point(0, position.bottom);
365 { ///Calculate first column stuff
366 int offsetP, offsetC, targetC, targetP;
367 if (horizSlider) {
368 offsetP = horizSlider.value();
369 for (; _header.columnWidths[offsetC] < offsetP ; offsetC++) {
370 offsetP -= _header.columnWidths[offsetC];
371 }
372 offsetP = max(0, offsetP);
373 ///Calculate last column number
374 targetP = horizSlider.value() + position.width;
375 for (; _header.columnWidths.length > targetC && _header.columnWidths[targetC] < targetP ; targetC++) {
376 targetP -= _header.columnWidths[targetC];
377 }
378 targetC = min(cast(int)(_header.columnWidths.length) - 1, targetC);
379 lower.y -= horizSlider.getPosition().height;
380 } else {
381 targetC = cast(int)_header.columnWidths.length - 1;
382 }
383 drawParams = new DrawParameters(ss, _header.columnWidths, offsetC, targetC, offsetP, 0);
384 }
385
386 drawParams.target = Box(position.left, position.top, position.right, position.top + _header.height);
387
388 if (vertSlider) {
389 drawParams.target.right -= vertSlider.getPosition.width;
390
391 }
392
393 _header.draw(this);
394 /+Point upper = Point(drawParams.columnWidths[drawParams.offsetC] + position.left, position.top + _header.height);
395 Point lower = Point(upper.x, position.bottom);+/
396 int firstRow, lastRow;
397 if (vertSlider) {
398 int pixelsTotal = vertSlider.value();
399 for (; entries[firstRow].height < pixelsTotal ; firstRow++) {
400 pixelsTotal -= entries[firstRow].height;
401 }
402 drawParams.offsetFR = pixelsTotal;
403 pixelsTotal += position.height;
404 pixelsTotal -= _header.height;
405 if (horizSlider) pixelsTotal -= horizSlider.getPosition().height;
406 lastRow = firstRow;
407 for (; entries.length > lastRow && entries[lastRow].height < pixelsTotal ; lastRow++) {
408 pixelsTotal -= entries[lastRow].height;
409 }
410 lastRow = min(cast(int)(entries.length) - 1, lastRow);
411 } else {
412 lastRow = cast(int)entries.length - 1;
413 }
414
415 for (int i = firstRow ; i <= lastRow ; i++) {
416 if (drawParams.target.bottom > position.bottom)
417 drawParams.target.bottom = position.bottom;
418 if (ss.getColor("ListViewHSep") && i != lastRow) {
419 parent.drawLine(drawParams.target.cornerLL, drawParams.target.cornerLR, ss.getColor("ListViewHSep"));
420 }
421 if (selection == i) {
422 Box target = drawParams.target - 1;
423 target.bottom -= drawParams.offsetFR;
424 parent.drawFilledBox(target, ss.getColor("selection"));
425 }
426 entries[i].draw(this);
427 }
428
429 if (ss.getColor("ListViewVSep")) {
430 for (int i = drawParams.offsetC ; i <= drawParams.targetC ; i++) {
431 upper.x = drawParams.columnWidths[i];
432 lower.x = drawParams.columnWidths[i];
433 parent.drawLine(upper, lower, ss.getColor("ListViewVSep"));
434 }
435 }
436 if (horizSlider) horizSlider.draw;
437 if (vertSlider) vertSlider.draw;
438
439 drawParams = null;
440 }
441 if (onDraw !is null) {
442 onDraw();
443 }
444 }
445 /**
446 * Returns the numfer of entries the ListView has.
447 */
448 public @property size_t numEntries() @nogc @safe pure nothrow const {
449 return entries.length;
450 }
451 /**
452 * Returns the number of the selected item, or minus one if nothing is selected.
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 * Returns the currently selected element, or null if none is selected.
469 */
470 public @property ListViewItem selectedElement() @nogc @safe pure nothrow {
471 if (selection >= 0)
472 return entries[selection];
473 else
474 return null;
475 }
476 /**
477 * Enables or disables the text editing of this element.
478 */
479 public @property bool editEnable(bool val) @nogc @safe pure nothrow {
480 if (val) flags |= EDIT_EN;
481 else flags &= ~EDIT_EN;
482 return flags & EDIT_EN ? true : false;
483 }
484 /**
485 * Returns true if text editing is enabled.
486 */
487 public @property bool editEnable() @nogc @safe pure nothrow const {
488 return flags & EDIT_EN ? true : false;
489 }
490 /**
491 * Enables or disables editing for multiple cells.
492 * If disabled, the first cell with editing enabled will be able to be edited.
493 */
494 public @property bool multicellEditEnable(bool val) @nogc @safe pure nothrow {
495 if (val) flags |= MULTICELL_EDIT_EN;
496 else flags &= ~MULTICELL_EDIT_EN;
497 return flags & MULTICELL_EDIT_EN ? true : false;
498 }
499 /**
500 * Returns true if text editing for multiple cells is enabled.
501 */
502 public @property bool multicellEditEnable() @nogc @safe pure nothrow const {
503 return flags & MULTICELL_EDIT_EN ? true : false;
504 }
505 /**
506 * Sets a new header, also able to supply new entries.
507 */
508 public void setHeader(ListViewHeader header, ListViewItem[] entries) {
509 _header = header;
510 foreach (ListViewItem key; entries) {
511 assert(key.length == header.length);
512 }
513 this.entries = entries;
514 refresh();
515 draw();
516 }
517 /**
518 * Removes an item from the entries.
519 * Returns the removed entry.
520 */
521 public ListViewItem removeEntry(size_t index) {
522 import std.algorithm.mutation : remove;
523 ListViewItem result = entries[index];
524 entries = entries.remove(index);
525 if (selection >= entries.length) selection--;
526 //draw;
527 return result;
528 }
529 /**
530 * Inserts an element at the given index.
531 * Params:
532 * index = Where the new element should be inserted.
533 * item = The item to be inserted.
534 * Returns: The inserted element, or null if out of bounds.
535 */
536 public ListViewItem insertAt(size_t index, ListViewItem item) {
537 if (!index)
538 entries = item ~ entries;
539 else if (entries.length > index)
540 entries = entries[0..index] ~ item ~ entries[index..$];
541 else if (entries.length == index)
542 entries ~= item;
543 else
544 return null;
545 return item;
546 }
547 /**
548 * Moves the entry to the given position.
549 * Params:
550 * index: the entry to be moved.
551 * target: the position to be moved to.
552 */
553 public void moveEntry(size_t index, size_t target) {
554 ListViewItem[] backup = entries[0..index - 1] ~ entries[index..$];
555 entries = backup[0..target] ~ entries[index] ~ backup[target..$];
556 }
557 /**
558 * Returns the current x and y scroll positions.
559 */
560 public int[2] scroll() {
561 int[2] result;
562 if (horizSlider !is null)
563 result[0] = horizSlider.value;
564 if (vertSlider !is null)
565 result[1] = vertSlider.value;
566 return result;
567 }
568 /**
569 * Sets the scrolling to `pos`, then returns the new scrolling positions.
570 */
571 public int[2] scroll(int[2] pos) {
572 if (horizSlider !is null)
573 horizSlider.value = pos[0];
574 if (vertSlider !is null)
575 vertSlider.value = pos[1];
576 return scroll();
577 }
578 /**
579 * Removes all entries in the list.
580 */
581 public void clear() @safe {
582 entries.length = 0;
583 }
584 /**
585 * Refreshes the list view.
586 * Must be called every time when adding new items is finished.
587 */
588 public void refresh() {
589 if (selection >= entries.length)
590 selection = cast(int)entries.length - 1;
591 recalculateTotalSizes;
592 draw;
593 }
594 /**
595 * Recalculates the total width and height of the list view's field, also generates scrollbars if needed.
596 */
597 protected void recalculateTotalSizes() {
598 int totalWidth, totalHeight;
599 foreach (i ; _header.columnWidths) {
600 totalWidth += i;
601 }
602 foreach (ListViewItem key; entries) {
603 totalHeight += key.height;
604 }
605 if (_header)
606 totalHeight += _header.height;
607 StyleSheet ss = getStyleSheet();
608 bool needsVSB, needsHSB;
609 if (totalWidth > position.width)
610 needsHSB = true;
611 if (totalHeight > position.height)
612 needsVSB = true;
613 if (needsVSB && totalWidth > position.width - ss.drawParameters["VertScrollBarSize"])
614 needsHSB = true;
615 if (needsHSB && totalHeight > position.height - ss.drawParameters["HorizScrollBarSize"])
616 needsVSB = true;
617 totalHeight -= _header.height;
618 int hPos, vPos;
619 if (horizSlider)
620 hPos = horizSlider.value;
621 if (vertSlider)
622 vPos = vertSlider.value;
623 if (needsVSB) {
624 const int maxvalue = needsHSB ? totalHeight - (position.height - ss.drawParameters["HorizScrollBarSize"]
625 - _header.height) : totalHeight - (position.height - _header.height);
626
627 const Box target = Box(position.right - ss.drawParameters["HorizScrollBarSize"] + 2, position.top,
628 position.right, needsHSB ? position.bottom - ss.drawParameters["VertScrollBarSize"] : position.bottom);
629 vertSlider = new VertScrollBar(maxvalue, source ~ "VSB", target);
630 vertSlider.setParent(this);
631 vertSlider.value = vPos;
632 vertSlider.onScrolling = &scrollBarEventOut;
633 } else vertSlider = null;
634 if (needsHSB) {
635 const int maxvalue = needsVSB ? totalWidth - (position.width - ss.drawParameters["VertScrollBarSize"]) :
636 totalWidth - position.width;
637 const Box target = Box(position.left, position.bottom - ss.drawParameters["VertScrollBarSize"] + 2,
638 needsVSB ? position.right - ss.drawParameters["HorizScrollBarSize"] : position.right,
639 position.bottom);
640 horizSlider = new HorizScrollBar(maxvalue, source ~ "VSB", target);
641 horizSlider.setParent(this);
642 horizSlider.value = hPos;
643 horizSlider.onScrolling = &scrollBarEventOut;
644 } else horizSlider = null;
645 }
646 protected void scrollBarEventOut(Event ev) {
647 draw;
648 }
649 /**
650 * Returns the absolute position of the element.
651 */
652 public Box getAbsolutePosition(WindowElement sender) {
653 return parent.getAbsolutePosition(sender);
654 }
655 /**
656 * Gives focus to the element if applicable
657 */
658 public void requestFocus(WindowElement sender) {
659
660 }
661 /**
662 * Sets the cursor to the given type on request.
663 */
664 public void requestCursor(CursorType type) {
665
666 }
667 ///Draws a line.
668 public void drawLine(Point from, Point to, ubyte color) @trusted {
669 if (parent !is null) parent.drawLine(from, to, color);
670 }
671 ///Draws a line pattern.
672 public void drawLinePattern(Point from, Point to, ubyte[] pattern) @trusted {
673 if (parent !is null) parent.drawLinePattern(from, to, pattern);
674 }
675 ///Draws an empty rectangle.
676 public void drawBox(Box target, ubyte color) @trusted {
677 if (parent !is null) parent.drawBox(target, color);
678 }
679 ///Draws an empty rectangle with line patterns.
680 public void drawBoxPattern(Box target, ubyte[] pattern) @trusted {
681 if (parent !is null) parent.drawBoxPattern(target, pattern);
682 }
683 ///Draws a filled rectangle with a specified color.
684 public void drawFilledBox(Box target, ubyte color) @trusted {
685 if (parent !is null) parent.drawFilledBox(target, color);
686 }
687 ///Pastes a bitmap to the given point using blitter, which threats color #0 as transparency.
688 public void bitBLT(Point target, ABitmap source) @trusted {
689 if (parent !is null) parent.bitBLT(target, source);
690 }
691 ///Pastes a slice of a bitmap to the given point using blitter, which threats color #0 as transparency.
692 public void bitBLT(Point target, ABitmap source, Box slice) @trusted {
693 if (parent !is null) parent.bitBLT(target, source, slice);
694 }
695 ///Pastes a repeated bitmap pattern over the specified area.
696 public void bitBLTPattern(Box target, ABitmap pattern) @trusted {
697 if (parent !is null) parent.bitBLTPattern(target, pattern);
698 }
699 ///XOR blits a repeated bitmap pattern over the specified area.
700 public void xorBitBLT(Box target, ABitmap pattern) @trusted {
701 if (parent !is null) parent.xorBitBLT(target, pattern);
702 }
703 ///XOR blits a color index over a specified area.
704 public void xorBitBLT(Box target, ubyte color) @trusted {
705 if (parent !is null) parent.xorBitBLT(target, color);
706 }
707 ///Fills an area with the specified color.
708 public void fill(Point target, ubyte color, ubyte background = 0) @trusted {
709 if (parent !is null) parent.fill(target, color, background);
710 }
711 ///Draws a single line text within the given prelimiter.
712 public void drawTextSL(Box target, Text text, Point offset) @trusted {
713 if (parent !is null) parent.drawTextSL(target, text, offset);
714 }
715 ///Draws a multi line text within the given prelimiter.
716 public void drawTextML(Box target, Text text, Point offset) @trusted {
717 if (parent !is null) parent.drawTextML(target, text, offset);
718 }
719 ///Clears the area within the target
720 public void clearArea(Box target) @trusted {
721 if (parent !is null) parent.clearArea(target);
722 }
723 ///Passes mouse click event
724 public override void passMCE(MouseEventCommons mec, MouseClickEvent mce) {
725 ///TODO: Handle mouse click when in text editing mode
726 if (state != ElementState.Enabled) return;
727 if (vertSlider) {
728 const Box p = vertSlider.getPosition();
729 if (p.isBetween(mce.x, mce.y)) {
730 vertSlider.passMCE(mec, mce);
731 return;
732 }
733 }
734 if (horizSlider) {
735 const Box p = horizSlider.getPosition();
736 if (p.isBetween(mce.x, mce.y)) {
737 horizSlider.passMCE(mec, mce);
738 return;
739 }
740 }
741 if (!textArea.isBetween(mce.x, mce.y) && (flags & TEXTINPUT_EN)){
742 inputHandler.stopTextInput();
743 return;
744 }
745
746 //if (mce.button != MouseButton.Left && !mce.state) return;
747
748 mce.x -= position.left;
749 mce.y -= position.top;
750 if (entries.length && mce.y > _header.height && mce.button == MouseButton.Left && mce.state) {
751 textArea.top = position.top;
752 textArea.left = position.left;
753 mce.y -= _header.height;
754 int pixelsTotal = mce.y, pos;
755 if (vertSlider) ///calculate outscrolled area
756 pixelsTotal += vertSlider.value;
757 while (pos < entries.length) {
758 if (pixelsTotal > entries[pos].height) {
759 pixelsTotal -= entries[pos].height;
760 textArea.top += entries[pos].height;
761 if (pos + 1 < entries.length)
762 pos++;
763 } else {
764 break;
765 }
766 }
767 if (pos >= entries.length) {
768 selection = -1;
769 } else if (selection == pos && (flags & EDIT_EN)) {
770 //Calculate horizontal selection for Multicell editing if needed
771 /+if (flags & MULTICELL_EDIT_EN) {
772
773 } else {+/
774 textArea.top += _header.height;
775 foreach (size_t i, ListViewItem.Field f ; entries[selection].fields) {
776 if (f.editable) {
777 hSelection = cast(int)i;
778
779
780 with (textArea) {
781 bottom = entries[selection].height + textArea.top;
782 right = _header.columnWidths[i] + textArea.left;
783 left = max(textArea.left, position.left);
784 top = max(textArea.top, position.top);
785 right = min(textArea.right, position.right);
786 bottom = min(textArea.bottom, position.bottom);
787 }
788 text = new Text(entries[selection][hSelection].text);
789 cursorPos = 0;
790 tselect = cast(int)text.charLength;
791 //oldText = text;
792 if (flags & MULTICELL_EDIT_EN) {
793 if (textArea.left < mce.x && textArea.right > mce.x) {
794 if (vertSlider) {
795 textArea.top -= vertSlider.value;
796 textArea.bottom = entries[selection].height + textArea.top;
797 }
798 if (horizSlider) textArea.left -= horizSlider.value;
799 inputHandler.startTextInput(this);
800 break;
801 }
802 } else {
803 if (vertSlider) {
804 textArea.top -= vertSlider.value;
805 textArea.bottom = entries[selection].height + textArea.top;
806 }
807 if (horizSlider) textArea.left -= horizSlider.value;
808 inputHandler.startTextInput(this);
809 break;
810 }
811
812 }
813 textArea.left += _header.columnWidths[i];
814 }
815 //}
816 selection = pos;
817 } else
818 selection = pos;
819
820 if (onItemSelect !is null && selection != -1)
821 onItemSelect(new Event(this, entries[selection], EventType.Selection, SourceType.WindowElement));
822 } else if (!entries.length) {
823 selection = -1;
824 } else {
825 super.passMCE(mec, mce);
826 }
827
828 draw();
829 }
830 ///Passes mouse move event
831 public override void passMME(MouseEventCommons mec, MouseMotionEvent mme) {
832 if (state != ElementState.Enabled) return;
833 if (vertSlider) {
834 const Box p = vertSlider.getPosition();
835 if (p.isBetween(mme.x, mme.y)) {
836 mme.x -= p.left - position.left;
837 mme.y -= p.top - position.top;
838 vertSlider.passMME(mec, mme);
839 return;
840 }
841 }
842 if (horizSlider) {
843 const Box p = horizSlider.getPosition();
844 if (p.isBetween(mme.x, mme.y)) {
845 mme.x -= p.left - position.left;
846 mme.y -= p.top - position.top;
847 horizSlider.passMME(mec, mme);
848 return;
849 }
850 }
851 }
852 ///Passes mouse scroll event
853 public override void passMWE(MouseEventCommons mec, MouseWheelEvent mwe) {
854 mwe.x *= hScrollSpeed;
855 mwe.y *= vScrollSpeed;
856 if (state != ElementState.Enabled) return;
857 if (horizSlider) horizSlider.passMWE(mec, mwe);
858 if (vertSlider) vertSlider.passMWE(mec, mwe);
859 }
860 /**
861 * Puts a PopUpElement on the GUI.
862 */
863 public void addPopUpElement(PopUpElement p) {
864 parent.addPopUpElement(p);
865 }
866 /**
867 * Puts a PopUpElement on the GUI at the given position.
868 */
869 public void addPopUpElement(PopUpElement p, int x, int y) {
870 parent.addPopUpElement(p, x, y);
871 }
872 /**
873 * Ends the popup session and closes all popups.
874 */
875 public void endPopUpSession(PopUpElement p) {
876 parent.endPopUpSession(p);
877 }
878 /**
879 * Closes a single popup element.
880 */
881 public void closePopUp(PopUpElement p) {
882 parent.closePopUp(p);
883 }
884 //Interface `TextInputListener` starts here
885 /**
886 * Passes the inputted text to the target, alongside with a window ID and a timestamp.
887 */
888 public void textInputEvent(uint timestamp, uint windowID, dstring text) {
889 import pixelperfectengine.system.etc : removeUnallowedSymbols;
890 /+if (allowedChars.length) {
891 text = removeUnallowedSymbols(text, allowedChars);
892 if (!text.length) return;
893 }+/
894 if (tselect) {
895 this.text.removeChar(cursorPos, tselect);
896 tselect = 0;
897 for(int j ; j < text.length ; j++){
898 this.text.insertChar(cursorPos++, text[j]);
899 }
900 } else if (flags & INSERT) {
901 for(int j ; j < text.length ; j++){
902 this.text.overwriteChar(cursorPos++, text[j]);
903 }
904 } else {
905 for(int j ; j < text.length ; j++){
906 this.text.insertChar(cursorPos++, text[j]);
907 }
908 }
909 if (filter) {
910 dstring s = this.text.text;
911 filter.use(s);
912 this.text.text = s;
913 cursorPos = min(cursorPos, cast(uint)this.text.charLength);
914 }
915 const int textPadding = getStyleSheet.drawParameters["TextSpacingSides"];
916 const Coordinate textPos = Coordinate(textPadding,(position.height / 2) - (this.text.font.size / 2) ,
917 position.width,position.height - textPadding);
918 const int x = this.text.getWidth(), cursorPixelPos = this.text.getWidth(0, cursorPos);
919 if(x > textPos.width) {
920 if(cursorPos == this.text.text.length) {
921 horizTextOffset = x - textPos.width;
922 } else if(cursorPixelPos < horizTextOffset) { //Test for whether the cursor would fall out from the current text area
923 horizTextOffset = cursorPixelPos;
924 } else if(cursorPixelPos > horizTextOffset + textPos.width) {
925 horizTextOffset = horizTextOffset + textPos.width;
926 }
927 }
928 draw();
929 }
930 /**
931 * Passes text editing events to the target, alongside with a window ID and a timestamp.
932 */
933 public void textEditingEvent(uint timestamp, uint windowID, dstring text, int start, int length) {
934 for (int i ; i < length ; i++) {
935 this.text.overwriteChar(start + i, text[i]);
936 }
937 cursorPos = start + length;
938 }
939 /**
940 * Passes text input key events to the target, e.g. cursor keys.
941 */
942 public void textInputKeyEvent(uint timestamp, uint windowID, TextInputKey key, ushort modifier) {
943 switch(key) {
944 case TextInputKey.Enter:
945 entries[selection][hSelection].text = text;
946 inputHandler.stopTextInput();
947 if(onTextInput !is null)
948 onTextInput(new CellEditEvent(this, entries[selection], selection, hSelection));
949 //onTextInput(new Event(source, null, null, null, text, 0, EventType.T, null, this));
950 break;
951 case TextInputKey.Escape:
952 //text = oldText;
953 inputHandler.stopTextInput();
954
955
956 break;
957 case TextInputKey.Backspace:
958 if(cursorPos > 0){
959 deleteCharacter(cursorPos - 1);
960 cursorPos--;
961 draw();
962 }
963 break;
964 case TextInputKey.Delete:
965 if (tselect) {
966 for (int i ; i < tselect ; i++) {
967 deleteCharacter(cursorPos);
968 }
969 tselect = 0;
970 } else {
971 deleteCharacter(cursorPos);
972 }
973 draw();
974 break;
975 case TextInputKey.CursorLeft:
976 if (modifier != KeyModifier.Shift) {
977 tselect = 0;
978 if(cursorPos > 0){
979 --cursorPos;
980 draw();
981 }
982 }
983 break;
984 case TextInputKey.CursorRight:
985 if (modifier != KeyModifier.Shift) {
986 tselect = 0;
987 if(cursorPos < text.charLength){
988 ++cursorPos;
989 draw();
990 }
991 }
992 break;
993 case TextInputKey.Home:
994 if (modifier != KeyModifier.Shift) {
995 tselect = 0;
996 cursorPos = 0;
997 draw();
998 }
999 break;
1000 case TextInputKey.End:
1001 if (modifier != KeyModifier.Shift) {
1002 tselect = 0;
1003 cursorPos = cast(int)text.charLength;
1004 draw();
1005 }
1006 break;
1007 case TextInputKey.Insert:
1008 flags ^= INSERT;
1009 draw();
1010 break;
1011 default:
1012 break;
1013 }
1014 }
1015 /**
1016 * When called, the listener should drop all text input.
1017 */
1018 public void dropTextInput() {
1019 flags &= ~TEXTINPUT_EN;
1020 draw;
1021 }
1022 /**
1023 * Called if text input should be initialized.
1024 */
1025 public void initTextInput() {
1026 flags |= TEXTINPUT_EN;
1027 ListViewItem.Field f = opIndex(selection)[hSelection];
1028 switch(f.textInputType) {
1029 default:
1030 filter = null;
1031 break;
1032 case TextInputFieldType.ASCIIText:
1033 filter = new ASCIITextFilter();
1034 break;
1035 case TextInputFieldType.Decimal:
1036 filter = new DecimalFilter!true();
1037 break;
1038 case TextInputFieldType.Integer:
1039 filter = new IntegerFilter!true();
1040 break;
1041 case TextInputFieldType.DecimalP:
1042 filter = new DecimalFilter!false();
1043 break;
1044 case TextInputFieldType.IntegerP:
1045 filter = new IntegerFilter!false();
1046 break;
1047 case TextInputFieldType.Hex:
1048 filter = new HexadecimalFilter();
1049 break;
1050 case TextInputFieldType.Oct:
1051 break;
1052 case TextInputFieldType.Bin:
1053 break;
1054 }
1055 }
1056 private void deleteCharacter(size_t n){
1057 text.removeChar(n);
1058 }
1059 public int[2] getRasterSizes() {
1060 return parent.getRasterSizes();
1061 }
1062 }