1 // Written in the D programming language. 2 3 /** 4 DLANGUI library. 5 6 This module contains declaration of Widget class - base class for all widgets. 7 8 9 10 Synopsis: 11 12 ---- 13 import dlangui.widgets.widget; 14 15 ---- 16 17 Copyright: Vadim Lopatin, 2014 18 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 19 Authors: $(WEB coolreader.org, Vadim Lopatin) 20 */ 21 module dlangui.widgets.widget; 22 23 public import dlangui.core.types; 24 public import dlangui.core.events; 25 public import dlangui.widgets.styles; 26 public import dlangui.graphics.drawbuf; 27 //public import dlangui.graphics.images; 28 public import dlangui.graphics.resources; 29 public import dlangui.graphics.fonts; 30 public import dlangui.core.i18n; 31 32 //public import std.signals; 33 public import dlangui.core.signals; 34 35 import dlangui.platforms.common.platform; 36 37 import std.algorithm; 38 39 40 /// Visibility (see Android View Visibility) 41 enum Visibility : ubyte { 42 /// Visible on screen (default) 43 Visible, 44 /// Not visible, but occupies a space in layout 45 Invisible, 46 /// Completely hidden, as not has been added 47 Gone 48 } 49 50 enum Orientation : ubyte { 51 Vertical, 52 Horizontal 53 } 54 55 /// interface - slot for onClick 56 interface OnClickHandler { 57 bool onClick(Widget source); 58 } 59 60 /// interface - slot for onCheckChanged 61 interface OnCheckHandler { 62 bool onCheckChanged(Widget source, bool checked); 63 } 64 65 /// interface - slot for onFocusChanged 66 interface OnFocusHandler { 67 bool onFocusChanged(Widget source, bool focused); 68 } 69 70 class Widget { 71 /// widget id 72 protected string _id; 73 /// current widget position, set by layout() 74 protected Rect _pos; 75 /// widget visibility: either Visible, Invisible, Gone 76 protected Visibility _visibility = Visibility.Visible; // visible by default 77 /// style id to lookup style in theme 78 protected string _styleId; 79 /// own copy of style - to override some of style properties, null of no properties overriden 80 protected Style _ownStyle; 81 82 /// widget state (set of flags from State enum) 83 protected uint _state; 84 85 /// width measured by measure() 86 protected int _measuredWidth; 87 /// height measured by measure() 88 protected int _measuredHeight; 89 /// true to force layout 90 protected bool _needLayout = true; 91 /// true to force redraw 92 protected bool _needDraw = true; 93 /// parent widget 94 protected Widget _parent; 95 /// window (to be used for top level widgets only!) 96 protected Window _window; 97 98 /// does widget need to track mouse Hover 99 protected bool _trackHover; 100 101 /// mouse movement processing flag (when true, widget will change Hover state while mouse is moving) 102 @property bool trackHover() const { return _trackHover; } 103 /// set new trackHover flag value (when true, widget will change Hover state while mouse is moving) 104 @property Widget trackHover(bool v) { _trackHover = v; return this; } 105 106 //private static int _instanceCount = 0; 107 /// create widget, with optional id 108 this(string ID = null) { 109 _id = ID; 110 _state = State.Enabled; 111 //Log.d("Created widget, count = ", ++_instanceCount); 112 } 113 ~this() { 114 if (_ownStyle !is null) 115 destroy(_ownStyle); 116 _ownStyle = null; 117 //Log.d("Destroyed widget, count = ", --_instanceCount); 118 } 119 120 /// accessor to style - by lookup in theme by styleId (if style id is not set, theme base style will be used). 121 protected @property const (Style) style() const { 122 if (_ownStyle !is null) 123 return _ownStyle; 124 return currentTheme.get(_styleId); 125 } 126 /// accessor to style - by lookup in theme by styleId (if style id is not set, theme base style will be used). 127 protected @property const (Style) style(uint stateFlags) const { 128 const (Style) normalStyle = style(); 129 if (stateFlags == State.Normal) // state is normal 130 return normalStyle; 131 const (Style) stateStyle = normalStyle.forState(stateFlags); 132 if (stateStyle !is normalStyle) 133 return stateStyle; // found style for state in current style 134 //// lookup state style in parent (one level max) 135 //const (Style) parentStyle = normalStyle.parentStyle; 136 //if (parentStyle is normalStyle) 137 // return normalStyle; // no parent 138 //const (Style) parentStateStyle = parentStyle.forState(stateFlags); 139 //if (parentStateStyle !is parentStyle) 140 // return parentStateStyle; // found style for state in parent 141 return normalStyle; // fallback to current style 142 } 143 /// returns style for current widget state 144 protected @property const(Style) stateStyle() const { 145 return style(state); 146 } 147 148 /// enforces widget's own style - allows override some of style properties 149 protected @property Style ownStyle() { 150 if (_ownStyle is null) 151 _ownStyle = currentTheme.modifyStyle(_styleId); 152 return _ownStyle; 153 } 154 155 /// returns widget id, null if not set 156 @property string id() const { return _id; } 157 /// set widget id 158 @property Widget id(string id) { _id = id; return this; } 159 /// compare widget id with specified value, returs true if matches 160 bool compareId(string id) const { return (_id !is null) && id.equal(_id); } 161 162 /// widget state (set of flags from State enum) 163 @property uint state() const { 164 if ((_state & State.Parent) != 0 && _parent !is null) 165 return _parent.state; 166 return _state | State.WindowFocused; // TODO: 167 } 168 /// override to handle focus changes 169 protected void handleFocusChange(bool focused) { 170 onFocusChangeListener(this, checked); 171 } 172 /// override to handle check changes 173 protected void handleCheckChange(bool checked) { 174 onCheckChangeListener(this, checked); 175 } 176 /// set new widget state (set of flags from State enum) 177 @property Widget state(uint newState) { 178 if (newState != _state) { 179 uint oldState = _state; 180 _state = newState; 181 // need to redraw 182 invalidate(); 183 // notify focus changes 184 if ((oldState & State.Focused) && !(newState & State.Focused)) 185 handleFocusChange(false); 186 else if (!(oldState & State.Focused) && (newState & State.Focused)) 187 handleFocusChange(true); 188 // notify checked changes 189 if ((oldState & State.Checked) && !(newState & State.Checked)) 190 handleCheckChange(false); 191 else if (!(oldState & State.Checked) && (newState & State.Checked)) 192 handleCheckChange(true); 193 } 194 return this; 195 } 196 /// add state flags (set of flags from State enum) 197 @property Widget setState(uint stateFlagsToSet) { 198 return state(state | stateFlagsToSet); 199 } 200 /// remove state flags (set of flags from State enum) 201 @property Widget resetState(uint stateFlagsToUnset) { 202 return state(state & ~stateFlagsToUnset); 203 } 204 205 206 207 //====================================================== 208 // Style related properties 209 210 /// returns widget style id, null if not set 211 @property string styleId() const { return _styleId; } 212 /// set widget style id 213 @property Widget styleId(string id) { _styleId = id; return this; } 214 /// get margins (between widget bounds and its background) 215 @property Rect margins() const { return style.margins; } 216 /// set margins for widget - override one from style 217 @property Widget margins(Rect rc) { ownStyle.margins = rc; return this; } 218 /// get padding (between background bounds and content of widget) 219 @property Rect padding() const { 220 // get max padding from style padding and background drawable padding 221 Rect p = style.padding; 222 DrawableRef d = style.backgroundDrawable; 223 if (!d.isNull) { 224 Rect dp = style.backgroundDrawable.padding; 225 if (p.left < dp.left) 226 p.left = dp.left; 227 if (p.right < dp.right) 228 p.right = dp.right; 229 if (p.top < dp.top) 230 p.top = dp.top; 231 if (p.bottom < dp.bottom) 232 p.bottom = dp.bottom; 233 } 234 return p; 235 } 236 /// set padding for widget - override one from style 237 @property Widget padding(Rect rc) { ownStyle.padding = rc; return this; } 238 /// returns background color 239 @property uint backgroundColor() const { return stateStyle.backgroundColor; } 240 /// set background color for widget - override one from style 241 @property Widget backgroundColor(uint color) { ownStyle.backgroundColor = color; return this; } 242 /// get text color (ARGB 32 bit value) 243 @property uint textColor() const { return stateStyle.textColor; } 244 /// set text color (ARGB 32 bit value) 245 @property Widget textColor(uint value) { ownStyle.textColor = value; return this; } 246 /// returns font face 247 @property string fontFace() const { return stateStyle.fontFace; } 248 /// set font face for widget - override one from style 249 @property Widget fontFace(string face) { ownStyle.fontFace = face; return this; } 250 /// returns font style (italic/normal) 251 @property bool fontItalic() const { return stateStyle.fontItalic; } 252 /// set font style (italic/normal) for widget - override one from style 253 @property Widget fontItalic(bool italic) { ownStyle.fontStyle = italic ? FONT_STYLE_ITALIC : FONT_STYLE_NORMAL; return this; } 254 /// returns font weight 255 @property ushort fontWeight() const { return stateStyle.fontWeight; } 256 /// set font weight for widget - override one from style 257 @property Widget fontWeight(ushort weight) { ownStyle.fontWeight = weight; return this; } 258 /// returns font size in pixels 259 @property ushort fontSize() const { return stateStyle.fontSize; } 260 /// set font size for widget - override one from style 261 @property Widget fontSize(ushort size) { ownStyle.fontSize = size; return this; } 262 /// returns font family 263 @property FontFamily fontFamily() const { return stateStyle.fontFamily; } 264 /// set font family for widget - override one from style 265 @property Widget fontFamily(FontFamily family) { ownStyle.fontFamily = family; return this; } 266 /// returns alignment (combined vertical and horizontal) 267 @property ubyte alignment() const { return style.alignment; } 268 /// sets alignment (combined vertical and horizontal) 269 @property Widget alignment(ubyte value) { ownStyle.alignment = value; return this; } 270 /// returns horizontal alignment 271 @property Align valign() { return cast(Align)(alignment & Align.VCenter); } 272 /// returns vertical alignment 273 @property Align halign() { return cast(Align)(alignment & Align.HCenter); } 274 /// returns font set for widget using style or set manually 275 @property FontRef font() const { return stateStyle.font; } 276 277 /// returns widget content text (override to support this) 278 @property dstring text() { return ""; } 279 /// sets widget content text (override to support this) 280 @property Widget text(dstring s) { return this; } 281 /// sets widget content text (override to support this) 282 @property Widget text(ref UIString s) { return this; } 283 284 //================================================================== 285 // Layout and drawing related methods 286 287 /// returns true if layout is required for widget and its children 288 @property bool needLayout() { return _needLayout; } 289 /// returns true if redraw is required for widget and its children 290 @property bool needDraw() { return _needDraw; } 291 /// returns true is widget is being animated - need to call animate() and redraw 292 @property bool animating() { return false; } 293 /// animates window; interval is time left from previous draw, in hnsecs (1/10000 of second) 294 void animate(long interval) { 295 } 296 /// returns measured width (calculated during measure() call) 297 @property measuredWidth() { return _measuredWidth; } 298 /// returns measured height (calculated during measure() call) 299 @property measuredHeight() { return _measuredHeight; } 300 /// returns current width of widget in pixels 301 @property int width() { return _pos.width; } 302 /// returns current height of widget in pixels 303 @property int height() { return _pos.height; } 304 /// returns widget rectangle top position 305 @property int top() { return _pos.top; } 306 /// returns widget rectangle left position 307 @property int left() { return _pos.left; } 308 /// returns widget rectangle 309 @property Rect pos() { return _pos; } 310 /// returns min width constraint 311 @property int minWidth() { return style.minWidth; } 312 /// returns max width constraint (SIZE_UNSPECIFIED if no constraint set) 313 @property int maxWidth() { return style.maxWidth; } 314 /// returns min height constraint 315 @property int minHeight() { return style.minHeight; } 316 /// returns max height constraint (SIZE_UNSPECIFIED if no constraint set) 317 @property int maxHeight() { return style.maxHeight; } 318 319 /// set max width constraint (SIZE_UNSPECIFIED for no constraint) 320 @property Widget maxWidth(int value) { ownStyle.maxWidth = value; return this; } 321 /// set max width constraint (0 for no constraint) 322 @property Widget minWidth(int value) { ownStyle.minWidth = value; return this; } 323 /// set max height constraint (SIZE_UNSPECIFIED for no constraint) 324 @property Widget maxHeight(int value) { ownStyle.maxHeight = value; return this; } 325 /// set max height constraint (0 for no constraint) 326 @property Widget minHeight(int value) { ownStyle.minHeight = value; return this; } 327 328 /// returns layout width options (WRAP_CONTENT, FILL_PARENT, or some constant value) 329 @property int layoutWidth() { return style.layoutWidth; } 330 /// returns layout height options (WRAP_CONTENT, FILL_PARENT, or some constant value) 331 @property int layoutHeight() { return style.layoutHeight; } 332 /// returns layout weight (while resizing to fill parent, widget will be resized proportionally to this value) 333 @property int layoutWeight() { return style.layoutWeight; } 334 335 /// sets layout width options (WRAP_CONTENT, FILL_PARENT, or some constant value) 336 @property Widget layoutWidth(int value) { ownStyle.layoutWidth = value; return this; } 337 /// sets layout height options (WRAP_CONTENT, FILL_PARENT, or some constant value) 338 @property Widget layoutHeight(int value) { ownStyle.layoutHeight = value; return this; } 339 /// sets layout weight (while resizing to fill parent, widget will be resized proportionally to this value) 340 @property Widget layoutWeight(int value) { ownStyle.layoutWeight = value; return this; } 341 342 /// returns widget visibility (Visible, Invisible, Gone) 343 @property Visibility visibility() { return _visibility; } 344 /// sets widget visibility (Visible, Invisible, Gone) 345 @property Widget visibility(Visibility visible) { 346 if (_visibility != visible) { 347 if ((_visibility == Visibility.Gone) || (visible == Visibility.Gone)) 348 requestLayout(); 349 else 350 invalidate(); 351 _visibility = visible; 352 } 353 return this; 354 } 355 356 /// returns true if point is inside of this widget 357 bool isPointInside(int x, int y) { 358 return _pos.isPointInside(x, y); 359 } 360 361 /// return true if state has State.Enabled flag set 362 @property bool enabled() { return (state & State.Enabled) != 0; } 363 /// change enabled state 364 @property Widget enabled(bool flg) { flg ? setState(State.Enabled) : resetState(State.Enabled); return this; } 365 366 protected bool _clickable; 367 /// when true, user can click this control, and get onClick listeners called 368 @property bool clickable() { return _clickable; } 369 @property Widget clickable(bool flg) { _clickable = flg; return this; } 370 @property bool canClick() { return _clickable && enabled && visible; } 371 372 protected bool _checkable; 373 /// when true, control supports Checked state 374 @property bool checkable() { return _checkable; } 375 @property Widget checkable(bool flg) { _checkable = flg; return this; } 376 @property bool canCheck() { return _checkable && enabled && visible; } 377 378 379 protected bool _checked; 380 /// get checked state 381 @property bool checked() { return (state & State.Checked) != 0; } 382 /// set checked state 383 @property Widget checked(bool flg) { 384 if (flg != checked) { 385 if (flg) 386 setState(State.Checked); 387 else 388 resetState(State.Checked); 389 invalidate(); 390 } 391 return this; 392 } 393 394 protected bool _focusable; 395 @property bool focusable() { return _focusable; } 396 @property Widget focusable(bool flg) { _focusable = flg; return this; } 397 398 @property bool focused() { 399 return (window !is null && window.focusedWidget is this && (state & State.Focused)); 400 } 401 402 403 404 /// returns true if this widget and all its parents are visible 405 @property bool visible() { 406 if (visibility != Visibility.Visible) 407 return false; 408 if (parent is null) 409 return true; 410 return parent.visible; 411 } 412 /// returns true if widget is focusable and visible 413 @property bool canFocus() { 414 return focusable && visible; 415 } 416 /// sets focus to this widget or suitable focusable child, returns previously focused widget 417 Widget setFocus() { 418 if (window is null) 419 return null; 420 if (!visible) 421 return window.focusedWidget; 422 if (!canFocus) { 423 Widget w = findFocusableChild(true); 424 if (!w) 425 w = findFocusableChild(false); 426 if (w) 427 return window.setFocus(w); 428 // try to find focusable child 429 return window.focusedWidget; 430 } 431 return window.setFocus(this); 432 } 433 /// searches children for first focusable item, returns null if not found 434 Widget findFocusableChild(bool defaultOnly) { 435 for(int i = 0; i < childCount; i++) { 436 Widget w = child(i); 437 if (w.canFocus && (!defaultOnly || (w.state & State.Default) != 0)) 438 return w; 439 w = w.findFocusableChild(defaultOnly); 440 if (w !is null) 441 return w; 442 } 443 if (canFocus) 444 return this; 445 return null; 446 } 447 448 // ======================================================= 449 // Events 450 451 // called to process click and notify listeners 452 protected bool handleClick() { 453 bool res = onClickListener(this); 454 return res; 455 } 456 457 /// process key event, return true if event is processed. 458 bool onKeyEvent(KeyEvent event) { 459 if (canClick) { 460 // support onClick event initiated by Space or Return keys 461 if (event.action == KeyAction.KeyDown) { 462 if (event.keyCode == KeyCode.SPACE || event.keyCode == KeyCode.RETURN) { 463 setState(State.Pressed); 464 return true; 465 } 466 } 467 if (event.action == KeyAction.KeyUp) { 468 if (event.keyCode == KeyCode.SPACE || event.keyCode == KeyCode.RETURN) { 469 resetState(State.Pressed); 470 handleClick(); 471 return true; 472 } 473 } 474 } 475 return false; 476 } 477 478 /// process mouse event; return true if event is processed by widget. 479 bool onMouseEvent(MouseEvent event) { 480 //Log.d("onMouseEvent ", id, " ", event.action, " (", event.x, ",", event.y, ")"); 481 // support onClick 482 if (canClick) { 483 if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) { 484 setState(State.Pressed); 485 if (focusable) 486 setFocus(); 487 return true; 488 } 489 if (event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) { 490 resetState(State.Pressed); 491 handleClick(); 492 return true; 493 } 494 if (event.action == MouseAction.FocusOut || event.action == MouseAction.Cancel) { 495 resetState(State.Pressed); 496 resetState(State.Hovered); 497 return true; 498 } 499 if (event.action == MouseAction.FocusIn) { 500 setState(State.Pressed); 501 return true; 502 } 503 } 504 if (focusable && event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) { 505 setFocus(); 506 } 507 if (trackHover) { 508 if (event.action == MouseAction.FocusOut || event.action == MouseAction.Cancel) { 509 if ((state & State.Hovered)) { 510 Log.d("Hover off ", id); 511 resetState(State.Hovered); 512 } 513 return true; 514 } 515 if (event.action == MouseAction.Move) { 516 if (!(state & State.Hovered)) { 517 Log.d("Hover ", id); 518 setState(State.Hovered); 519 } 520 return true; 521 } 522 if (event.action == MouseAction.Leave) { 523 Log.d("Leave ", id); 524 resetState(State.Hovered); 525 return true; 526 } 527 } 528 return false; 529 } 530 531 // ======================================================= 532 // Signals 533 534 /// on click event listener (bool delegate(Widget)) 535 Signal!OnClickHandler onClickListener; 536 /// checked state change event listener (bool delegate(Widget, bool)) 537 Signal!OnCheckHandler onCheckChangeListener; 538 /// focus state change event listener (bool delegate(Widget, bool)) 539 Signal!OnFocusHandler onFocusChangeListener; 540 541 // ======================================================= 542 // Layout and measurement methods 543 544 /// request relayout of widget and its children 545 void requestLayout() { 546 _needLayout = true; 547 } 548 /// request redraw 549 void invalidate() { 550 _needDraw = true; 551 } 552 553 /// helper function for implement measure() when widget's content dimensions are known 554 protected void measuredContent(int parentWidth, int parentHeight, int contentWidth, int contentHeight) { 555 if (visibility == Visibility.Gone) { 556 _measuredWidth = _measuredHeight = 0; 557 return; 558 } 559 Rect m = margins; 560 Rect p = padding; 561 // summarize margins, padding, and content size 562 int dx = m.left + m.right + p.left + p.right + contentWidth; 563 int dy = m.top + m.bottom + p.top + p.bottom + contentHeight; 564 // apply min/max width and height constraints 565 int minw = minWidth; 566 int maxw = maxWidth; 567 int minh = minHeight; 568 int maxh = maxHeight; 569 if (dx < minw) 570 dx = minw; 571 if (dy < minh) 572 dy = minh; 573 if (maxw != SIZE_UNSPECIFIED && dx > maxw) 574 dx = maxw; 575 if (maxh != SIZE_UNSPECIFIED && dy > maxh) 576 dy = maxh; 577 // apply max parent size constraint 578 if (parentWidth != SIZE_UNSPECIFIED && dx > parentWidth) 579 dx = parentWidth; 580 if (parentHeight != SIZE_UNSPECIFIED && dy > parentHeight) 581 dy = parentHeight; 582 _measuredWidth = dx; 583 _measuredHeight = dy; 584 } 585 586 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 587 void measure(int parentWidth, int parentHeight) { 588 measuredContent(parentWidth, parentHeight, 0, 0); 589 } 590 591 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 592 void layout(Rect rc) { 593 if (visibility == Visibility.Gone) { 594 return; 595 } 596 _pos = rc; 597 _needLayout = false; 598 } 599 /// Draw widget at its position to buffer 600 void onDraw(DrawBuf buf) { 601 if (visibility != Visibility.Visible) 602 return; 603 Rect rc = _pos; 604 applyMargins(rc); 605 DrawableRef bg = stateStyle.backgroundDrawable; 606 if (!bg.isNull) { 607 bg.drawTo(buf, rc, state); 608 } 609 applyPadding(rc); 610 _needDraw = false; 611 } 612 613 /// Helper function: applies margins to rectangle 614 void applyMargins(ref Rect rc) { 615 Rect m = margins; 616 rc.left += m.left; 617 rc.top += m.top; 618 rc.bottom -= m.bottom; 619 rc.right -= m.right; 620 } 621 /// Helper function: applies padding to rectangle 622 void applyPadding(ref Rect rc) { 623 Rect m = padding; 624 rc.left += m.left; 625 rc.top += m.top; 626 rc.bottom -= m.bottom; 627 rc.right -= m.right; 628 } 629 /// Applies alignment for content of size sz - set rectangle rc to aligned value of content inside of initial value of rc. 630 void applyAlign(ref Rect rc, Point sz) { 631 Align va = valign; 632 Align ha = halign; 633 if (va == Align.Bottom) { 634 rc.top = rc.bottom - sz.y; 635 } else if (va == Align.VCenter) { 636 int dy = (rc.height - sz.y) / 2; 637 rc.top += dy; 638 rc.bottom = rc.top + sz.y; 639 } else { 640 rc.bottom = rc.top + sz.y; 641 } 642 if (ha == Align.Right) { 643 rc.left = rc.right - sz.x; 644 } else if (ha == Align.HCenter) { 645 int dx = (rc.width - sz.x) / 2; 646 rc.left += dx; 647 rc.right = rc.left + sz.x; 648 } else { 649 rc.right = rc.left + sz.x; 650 } 651 } 652 653 // =========================================================== 654 // Widget hierarhy methods 655 656 /// returns number of children of this widget 657 @property int childCount() { return 0; } 658 /// returns child by index 659 Widget child(int index) { return null; } 660 /// adds child, returns added item 661 Widget addChild(Widget item) { assert(false, "addChild: children not suported for this widget type"); } 662 /// removes child, returns removed item 663 Widget removeChild(int index) { assert(false, "removeChild: children not suported for this widget type"); } 664 /// removes child by ID, returns removed item 665 Widget removeChild(string id) { assert(false, "removeChild: children not suported for this widget type"); } 666 /// returns index of widget in child list, -1 if passed widget is not a child of this widget 667 int childIndex(Widget item) { return -1; } 668 669 670 /// returns true if item is child of this widget (when deepSearch == true - returns true if item is this widget or one of children inside children tree). 671 bool isChild(Widget item, bool deepSearch = true) { 672 if (deepSearch) { 673 // this widget or some widget inside children tree 674 if (item is this) 675 return true; 676 for (int i = 0; i < childCount; i++) { 677 if (child(i).isChild(item)) 678 return true; 679 } 680 } else { 681 // only one of children 682 for (int i = 0; i < childCount; i++) { 683 if (item is child(i)) 684 return true; 685 } 686 } 687 return false; 688 } 689 690 /// find child by id, returns null if not found 691 Widget childById(string id, bool deepSearch = true) { 692 if (deepSearch) { 693 // search everywhere inside child tree 694 if (compareId(id)) 695 return this; 696 // lookup children 697 for (int i = childCount - 1; i >= 0; i--) { 698 Widget res = child(i).childById(id); 699 if (res !is null) 700 return res; 701 } 702 } else { 703 // search only across children of this widget 704 for (int i = childCount - 1; i >= 0; i--) 705 if (id.equal(child(i).id)) 706 return child(i); 707 } 708 // not found 709 return null; 710 } 711 712 /// returns parent widget, null for top level widget 713 @property Widget parent() { return _parent; } 714 /// sets parent for widget 715 @property Widget parent(Widget parent) { _parent = parent; return this; } 716 /// returns window (if widget or its parent is attached to window) 717 @property Window window() { 718 Widget p = this; 719 while (p !is null) { 720 if (p._window !is null) 721 return p._window; 722 p = p.parent; 723 } 724 return null; 725 } 726 /// sets window (to be used for top level widget from Window implementation). TODO: hide it from API? 727 @property void window(Window window) { _window = window; } 728 729 730 } 731 732 /// widget list holder 733 struct WidgetList { 734 protected Widget[] _list; 735 protected int _count; 736 /// returns count of items 737 @property int count() const { return _count; } 738 /// get item by index 739 Widget get(int index) { 740 assert(index >= 0 && index < _count, "child index out of range"); 741 return _list[index]; 742 } 743 /// add item to list 744 Widget add(Widget item) { 745 if (_list.length <= _count) // resize 746 _list.length = _list.length < 4 ? 4 : _list.length * 2; 747 _list[_count++] = item; 748 return item; 749 } 750 /// add item to list 751 Widget insert(Widget item, int index = -1) { 752 if (index > _count || index < 0) 753 index = _count; 754 if (_list.length <= _count) // resize 755 _list.length = _list.length < 4 ? 4 : _list.length * 2; 756 for (int i = _count; i > index; i--) 757 _list[i] = _list[i - 1]; 758 _list[index] = item; 759 _count++; 760 return item; 761 } 762 /// find child index for item, return -1 if not found 763 int indexOf(Widget item) { 764 for (int i = 0; i < _count; i++) 765 if (_list[i] == item) 766 return i; 767 return -1; 768 } 769 /// find child index for item by id, return -1 if not found 770 int indexOf(string id) { 771 for (int i = 0; i < _count; i++) 772 if (_list[i].compareId(id)) 773 return i; 774 return -1; 775 } 776 /// remove item from list, return removed item 777 Widget remove(int index) { 778 assert(index >= 0 && index < _count, "child index out of range"); 779 Widget item = _list[index]; 780 for (int i = index; i < _count - 1; i++) 781 _list[i] = _list[i + 1]; 782 _count--; 783 return item; 784 } 785 /// remove and destroy all items 786 void clear() { 787 for (int i = 0; i < _count; i++) { 788 destroy(_list[i]); 789 _list[i] = null; 790 } 791 _count = 0; 792 } 793 ~this() { 794 clear(); 795 } 796 } 797 798 /// base class for widgets which have children 799 class WidgetGroup : Widget { 800 801 this(string ID = null) { 802 super(ID); 803 } 804 805 protected WidgetList _children; 806 807 /// returns number of children of this widget 808 @property override int childCount() { return _children.count; } 809 /// returns child by index 810 override Widget child(int index) { return _children.get(index); } 811 /// adds child, returns added item 812 override Widget addChild(Widget item) { return _children.add(item).parent(this); } 813 /// removes child, returns removed item 814 override Widget removeChild(int index) { 815 Widget res = _children.remove(index); 816 if (res !is null) 817 res.parent = null; 818 return res; 819 } 820 /// removes child by ID, returns removed item 821 override Widget removeChild(string ID) { 822 Widget res = null; 823 int index = _children.indexOf(ID); 824 if (index < 0) 825 return null; 826 res = _children.remove(index); 827 if (res !is null) 828 res.parent = null; 829 return res; 830 } 831 /// returns index of widget in child list, -1 if passed widget is not a child of this widget 832 override int childIndex(Widget item) { return _children.indexOf(item); } 833 }