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