1 // Written in the D programming language. 2 3 /** 4 This module contains common layouts implementations. 5 6 Layouts are similar to the same in Android. 7 8 LinearLayout - either VerticalLayout or HorizontalLayout. 9 VerticalLayout - just LinearLayout with orientation=Orientation.Vertical 10 HorizontalLayout - just LinearLayout with orientation=Orientation.Horizontal 11 FrameLayout - children occupy the same place, usually one one is visible at a time 12 TableLayout - children aligned into rows and columns 13 ResizerWidget - widget to resize sibling widgets 14 15 Synopsis: 16 17 ---- 18 import dlangui.widgets.layouts; 19 20 ---- 21 22 Copyright: Vadim Lopatin, 2014 23 License: Boost License 1.0 24 Authors: Vadim Lopatin, coolreader.org@gmail.com 25 */ 26 module dlangui.widgets.layouts; 27 28 public import dlangui.widgets.widget; 29 import std.conv; 30 31 /// helper for layouts 32 struct LayoutItem { 33 Widget _widget; 34 Orientation _orientation; 35 int _measuredSize; // primary size for orientation 36 int _secondarySize; // other measured size 37 int _layoutSize; // layout size for primary dimension 38 int _minSize; // min size for primary dimension 39 int _maxSize; // max size for primary dimension 40 int _weight; // weight 41 bool _fillParent; 42 bool _isResizer; 43 int _resizerDelta; 44 @property bool canExtend() { return !_isResizer; } 45 @property int measuredSize() { return _measuredSize; } 46 @property int minSize() { return _measuredSize; } 47 @property int maxSize() { return _maxSize; } 48 @property int layoutSize() { return _layoutSize; } 49 @property int secondarySize() { return _layoutSize; } 50 @property bool fillParent() { return _fillParent; } 51 @property int weight() { return _weight; } 52 // just to help GC 53 void clear() { 54 _widget = null; 55 } 56 /// sets item for widget 57 void set(Widget widget, Orientation orientation) { 58 _widget = widget; 59 _orientation = orientation; 60 if (cast(ResizerWidget)widget) { 61 _isResizer = true; 62 _resizerDelta = (cast(ResizerWidget)widget).delta; 63 } 64 } 65 /// set item and measure it 66 void measure(int parentWidth, int parentHeight) { 67 _widget.measure(parentWidth, parentHeight); 68 _weight = _widget.layoutWeight; 69 if (_orientation == Orientation.Horizontal) { 70 _secondarySize = _widget.measuredHeight; 71 _measuredSize = _widget.measuredWidth; 72 _minSize = _widget.minWidth; 73 _maxSize = _widget.maxWidth; 74 _layoutSize = _widget.layoutWidth; 75 } else { 76 _secondarySize = _widget.measuredWidth; 77 _measuredSize = _widget.measuredHeight; 78 _minSize = _widget.minHeight; 79 _maxSize = _widget.maxHeight; 80 _layoutSize = _widget.layoutHeight; 81 } 82 _fillParent = _layoutSize == FILL_PARENT; 83 } 84 void layout(ref Rect rc) { 85 _widget.layout(rc); 86 } 87 } 88 89 /// helper class for layouts 90 class LayoutItems { 91 Orientation _orientation; 92 LayoutItem[] _list; 93 int _count; 94 int _totalSize; 95 int _maxSecondarySize; 96 Point _measureParentSize; 97 98 int _layoutWidth; 99 int _layoutHeight; 100 101 void setLayoutParams(Orientation orientation, int layoutWidth, int layoutHeight) { 102 _orientation = orientation; 103 _layoutWidth = layoutWidth; 104 _layoutHeight = layoutHeight; 105 } 106 107 /// fill widget layout list with Visible or Invisible items, measure them 108 Point measure(int parentWidth, int parentHeight) { 109 _totalSize = 0; 110 _maxSecondarySize = 0; 111 _measureParentSize.x = parentWidth; 112 _measureParentSize.y = parentHeight; 113 bool hasPercentSizeWidget = false; 114 size_t percenSizeWidgetIndex; 115 // measure 116 for (int i = 0; i < _count; i++) { 117 LayoutItem * item = &_list[i]; 118 119 item.measure(parentWidth, parentHeight); 120 121 if (isPercentSize(item._layoutSize)) { 122 if (!hasPercentSizeWidget) { 123 percenSizeWidgetIndex = i; 124 hasPercentSizeWidget = true; 125 } 126 } 127 else 128 _totalSize += item._measuredSize; 129 130 if (_maxSecondarySize < item._secondarySize) 131 _maxSecondarySize = item._secondarySize; 132 } 133 if (hasPercentSizeWidget) { 134 LayoutItem * item = &_list[percenSizeWidgetIndex]; 135 if (_totalSize > 0) 136 item._measuredSize = to!int(_totalSize * ((1 / (1 - cast (double) (fromPercentSize(item._layoutSize, 100))/100)) - 1)); 137 _totalSize += item._measuredSize; 138 } 139 return _orientation == Orientation.Horizontal ? Point(_totalSize, _maxSecondarySize) : Point(_maxSecondarySize, _totalSize); 140 } 141 142 /// fill widget layout list with Visible or Invisible items, measure them 143 void setWidgets(ref WidgetList widgets) { 144 // remove old items, if any 145 clear(); 146 // reserve space 147 if (_list.length < widgets.count) 148 _list.length = widgets.count; 149 // copy 150 for (int i = 0; i < widgets.count; i++) { 151 Widget item = widgets.get(i); 152 if (item.visibility == Visibility.Gone) 153 continue; 154 _list[_count++].set(item, _orientation); 155 } 156 } 157 158 void layout(Rect rc) { 159 // measure again - available area could be changed 160 if (_measureParentSize.x != rc.width || _measureParentSize.y != rc.height) 161 measure(rc.width, rc.height); 162 int contentSecondarySize = 0; 163 int contentHeight = 0; 164 int totalSize = 0; 165 int delta = 0; 166 int resizableSize = 0; 167 int resizableWeight = 0; 168 int nonresizableSize = 0; 169 int nonresizableWeight = 0; 170 int maxItem = 0; // max item dimention 171 // calc total size 172 int visibleCount = cast(int)_list.length; 173 int resizersSize = 0; 174 for (int i = 0; i < _count; i++) { 175 LayoutItem * item = &_list[i]; 176 int weight = item.weight; 177 int size = item.measuredSize; 178 totalSize += size; 179 if (maxItem < item.secondarySize) 180 maxItem = item.secondarySize; 181 if (item._isResizer) { 182 resizersSize += size; 183 } else if (item.fillParent || isPercentSize(item.layoutSize)) { 184 resizableWeight += weight; 185 resizableSize += size * weight; 186 } else { 187 nonresizableWeight += weight; 188 nonresizableSize += size * weight; 189 } 190 } 191 if (_orientation == Orientation.Vertical) { 192 if (_layoutWidth == WRAP_CONTENT && maxItem < rc.width) 193 contentSecondarySize = maxItem; 194 else 195 contentSecondarySize = rc.width; 196 if ((_layoutHeight == FILL_PARENT || isPercentSize(_layoutHeight)) && totalSize < rc.height && resizableSize > 0) { 197 delta = rc.height - totalSize; // total space to add to fit 198 } else if (totalSize > rc.height) { 199 delta = rc.height - totalSize; // total space to reduce to fit 200 } 201 } else { 202 if (_layoutHeight == WRAP_CONTENT && maxItem < rc.height) 203 contentSecondarySize = maxItem; 204 else 205 contentSecondarySize = rc.height; 206 if ((_layoutWidth == FILL_PARENT || isPercentSize(_layoutWidth)) && totalSize < rc.width && resizableSize > 0) 207 delta = rc.width - totalSize; // total space to add to fit 208 else if (totalSize > rc.width) 209 delta = rc.width - totalSize; // total space to reduce to fit 210 } 211 // calculate resize options and scale 212 bool needForceResize = false; 213 bool needResize = false; 214 int scaleFactor = 10000; // per weight unit 215 if (delta != 0 && visibleCount > 0) { 216 if (delta < 0) 217 nonresizableSize += resizersSize; // allow to shrink resizers 218 // need resize of some children 219 needResize = true; 220 // resize all if need to shrink or only resizable are too small to correct delta 221 needForceResize = /*delta < 0 || */ resizableWeight == 0; // || resizableSize * 2 / 3 < delta; // do we need resize non-FILL_PARENT items? 222 // calculate scale factor: weight / delta * 10000 223 if (needForceResize && nonresizableSize + resizableSize > 0) 224 scaleFactor = 10000 * delta / (nonresizableSize + resizableSize); 225 else if (resizableSize > 0) 226 scaleFactor = 10000 * delta / resizableSize; 227 else 228 scaleFactor = 0; 229 } 230 //Log.d("VerticalLayout delta=", delta, ", nonres=", nonresizableWeight, ", res=", resizableWeight, ", scale=", scaleFactor); 231 // find last resized - to allow fill space 1 pixel accurate 232 int lastResized = -1; 233 ResizerWidget resizer = null; 234 int resizerIndex = -1; 235 int resizerDelta = 0; 236 for (int i = 0; i < _count; i++) { 237 LayoutItem * item = &_list[i]; 238 if ((item.fillParent || isPercentSize(item.layoutSize) || needForceResize) && (delta < 0 || item.canExtend)) { 239 lastResized = i; 240 } 241 if (item._isResizer) { 242 resizerIndex = i; 243 resizerDelta = item._resizerDelta; 244 } 245 } 246 // final resize and layout of children 247 int position = 0; 248 int deltaTotal = 0; 249 for (int i = 0; i < _count; i++) { 250 LayoutItem * item = &_list[i]; 251 int layoutSize = item.layoutSize; 252 int weight = item.weight; 253 int size = item.measuredSize; 254 if (needResize && (layoutSize == FILL_PARENT || isPercentSize(layoutSize) || needForceResize)) { 255 // do resize 256 int correction = (delta < 0 || item.canExtend) ? scaleFactor * weight * size / 10000 : 0; 257 deltaTotal += correction; 258 // for last resized, apply additional correction to resolve calculation inaccuracy 259 if (i == lastResized) { 260 correction += delta - deltaTotal; 261 } 262 size += correction; 263 } 264 // apply size 265 Rect childRect = rc; 266 if (_orientation == Orientation.Vertical) { 267 // Vertical 268 childRect.top += position; 269 childRect.bottom = childRect.top + size; 270 childRect.right = childRect.left + contentSecondarySize; 271 item.layout(childRect); 272 } else { 273 // Horizontal 274 childRect.left += position; 275 childRect.right = childRect.left + size; 276 childRect.bottom = childRect.top + contentSecondarySize; 277 item.layout(childRect); 278 } 279 position += size; 280 } 281 } 282 283 void clear() { 284 for (int i = 0; i < _count; i++) 285 _list[i].clear(); 286 _count = 0; 287 } 288 ~this() { 289 clear(); 290 } 291 } 292 293 enum ResizerEventType : int { 294 StartDragging, 295 Dragging, 296 EndDragging 297 } 298 299 interface ResizeHandler { 300 void onResize(ResizerWidget source, ResizerEventType event, int currentPosition); 301 } 302 303 /** 304 * Resizer control. 305 * Put it between other items in LinearLayout to allow resizing its siblings. 306 * While dragging, it will resize previous and next children in layout. 307 */ 308 class ResizerWidget : Widget { 309 protected Orientation _orientation; 310 protected Widget _previousWidget; 311 protected Widget _nextWidget; 312 protected string _styleVertical; 313 protected string _styleHorizontal; 314 Signal!ResizeHandler resizeEvent; 315 316 /// Controls the minimum width of the previous item 317 size_t minPreviousItemWidth = 100; 318 319 /// Controls the minimum width of the next item 320 size_t minNextItemWidth = 100; 321 322 /// Orientation: Vertical to resize vertically, Horizontal - to resize horizontally 323 @property Orientation orientation() { return _orientation; } 324 /// empty parameter list constructor - for usage by factory 325 this() { 326 this(null); 327 minWidth = 7; 328 } 329 /// create with ID parameter 330 this(string ID, Orientation orient = Orientation.Vertical) { 331 super(ID); 332 _styleVertical = "RESIZER_VERTICAL"; 333 _styleHorizontal = "RESIZER_HORIZONTAL"; 334 _orientation = orient; 335 trackHover = true; 336 } 337 338 @property bool validProps() { 339 return _previousWidget && _nextWidget; 340 } 341 342 /// returns mouse cursor type for widget 343 override uint getCursorType(int x, int y) { 344 if (_orientation == Orientation.Vertical) { 345 return CursorType.SizeNS; 346 } else { 347 return CursorType.SizeWE; 348 } 349 } 350 351 protected void updateProps() { 352 _previousWidget = null; 353 _nextWidget = null; 354 LinearLayout parentLayout = cast(LinearLayout)_parent; 355 if (parentLayout) { 356 _orientation = parentLayout.orientation; 357 int index = parentLayout.childIndex(this); 358 _previousWidget = parentLayout.child(index - 1); 359 _nextWidget = parentLayout.child(index + 1); 360 } 361 if (validProps) { 362 if (_orientation == Orientation.Vertical) { 363 styleId = _styleVertical; 364 } else { 365 styleId = _styleHorizontal; 366 } 367 } else { 368 _previousWidget = null; 369 _nextWidget = null; 370 } 371 } 372 373 /** 374 Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 375 376 */ 377 override void measure(int parentWidth, int parentHeight) { 378 updateProps(); 379 if (_orientation == Orientation.Vertical) { 380 381 } 382 measuredContent(parentWidth, parentHeight, 7, 7); 383 } 384 385 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 386 override void layout(Rect rc) { 387 updateProps(); 388 if (visibility == Visibility.Gone) { 389 return; 390 } 391 _pos = rc; 392 _needLayout = false; 393 } 394 395 protected int _delta; 396 protected int _minDragDelta; 397 protected int _maxDragDelta; 398 protected bool _dragging; 399 protected int _dragStartPosition; // drag start delta 400 protected Point _dragStart; 401 protected Rect _dragStartRect; 402 protected Rect _scrollArea; 403 404 @property int delta() { return _delta; } 405 406 /// process mouse event; return true if event is processed by widget. 407 override bool onMouseEvent(MouseEvent event) { 408 // support onClick 409 immutable newWidth = _orientation == Orientation.Vertical ? event.y : event.x; 410 if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) { 411 setState(State.Pressed); 412 _dragging = true; 413 _dragStart.x = event.x; 414 _dragStart.y = event.y; 415 _dragStartPosition = _delta; 416 _dragStartRect = _pos; 417 _scrollArea = _pos; 418 _minDragDelta = 0; 419 _maxDragDelta = 0; 420 if (validProps) { 421 Rect r1 = _previousWidget.pos; 422 Rect r2 = _nextWidget.pos; 423 _scrollArea.left = r1.left; 424 _scrollArea.right = r2.right; 425 _scrollArea.top = r1.top; 426 _scrollArea.bottom = r2.bottom; 427 if (_orientation == Orientation.Vertical) { 428 _minDragDelta = _scrollArea.top - _dragStartRect.top; 429 _maxDragDelta = _scrollArea.bottom - _dragStartRect.bottom; 430 } 431 if (_delta < _minDragDelta) 432 _delta = _minDragDelta; 433 if (_delta > _maxDragDelta) 434 _delta = _maxDragDelta; 435 } else { 436 resizeAndFireEvent(newWidth, ResizerEventType.StartDragging); 437 } 438 return true; 439 } 440 if (event.action == MouseAction.FocusOut && _dragging) { 441 return true; 442 } 443 if ((event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) || (!event.lbutton.isDown && _dragging)) { 444 resetState(State.Pressed); 445 if (_dragging) { 446 //sendScrollEvent(ScrollAction.SliderReleased, _position); 447 _dragging = false; 448 resizeAndFireEvent(newWidth, ResizerEventType.EndDragging); 449 } 450 return true; 451 } 452 if (event.action == MouseAction.Move && _dragging) { 453 int delta = _orientation == Orientation.Vertical ? event.y - _dragStart.y : event.x - _dragStart.x; 454 resizeAndFireEvent(newWidth, ResizerEventType.Dragging); 455 _delta = _dragStartPosition + delta; 456 if (_delta < _minDragDelta) 457 _delta = _minDragDelta; 458 if (_delta > _maxDragDelta) 459 _delta = _maxDragDelta; 460 Rect rc = _dragStartRect; 461 int offset; 462 int space; 463 if (_orientation == Orientation.Vertical) { 464 rc.top += delta; 465 rc.bottom += delta; 466 if (rc.top < _scrollArea.top) { 467 rc.top = _scrollArea.top; 468 rc.bottom = _scrollArea.top + _dragStartRect.height; 469 } else if (rc.bottom > _scrollArea.bottom) { 470 rc.top = _scrollArea.bottom - _dragStartRect.height; 471 rc.bottom = _scrollArea.bottom; 472 } 473 offset = rc.top - _scrollArea.top; 474 space = _scrollArea.height - rc.height; 475 } else { 476 rc.left += delta; 477 rc.right += delta; 478 if (rc.left < _scrollArea.left) { 479 rc.left = _scrollArea.left; 480 rc.right = _scrollArea.left + _dragStartRect.width; 481 } else if (rc.right > _scrollArea.right) { 482 rc.left = _scrollArea.right - _dragStartRect.width; 483 rc.right = _scrollArea.right; 484 } 485 offset = rc.left - _scrollArea.left; 486 space = _scrollArea.width - rc.width; 487 } 488 //_pos = rc; 489 //int position = space > 0 ? _minValue + offset * (_maxValue - _minValue - _pageSize) / space : 0; 490 requestLayout(); 491 invalidate(); 492 //onIndicatorDragging(_dragStartPosition, position); 493 return true; 494 } 495 if (event.action == MouseAction.Move && trackHover) { 496 if (!(state & State.Hovered)) { 497 //Log.d("Hover ", id); 498 setState(State.Hovered); 499 } 500 return true; 501 } 502 if ((event.action == MouseAction.Leave || event.action == MouseAction.Cancel) && trackHover) { 503 //Log.d("Leave ", id); 504 resetState(State.Hovered); 505 return true; 506 } 507 if (event.action == MouseAction.Cancel) { 508 //Log.d("SliderButton.onMouseEvent event.action == MouseAction.Cancel"); 509 if (_dragging) { 510 resetState(State.Pressed); 511 _dragging = false; 512 resizeAndFireEvent(newWidth, ResizerEventType.EndDragging); 513 } 514 return true; 515 } 516 return false; 517 } 518 519 private void resizeAndFireEvent(short newWidth, ResizerEventType type) 520 { 521 // Respect the dimensions 522 if( (newWidth > minPreviousItemWidth) && (newWidth < (parent.width - minWidth - minNextItemWidth)) && (_previousWidget is null || 523 ( (newWidth > _previousWidget.minWidth) && 524 (newWidth < (parent.width - minWidth - _nextWidget.minWidth)) 525 ))) 526 { 527 if (_previousWidget !is null) { 528 _previousWidget.layoutWidth = newWidth; 529 } 530 if (resizeEvent.assigned) 531 { 532 resizeEvent(this, type, newWidth); 533 } 534 } 535 } 536 } 537 538 539 /// Arranges items either vertically or horizontally 540 class LinearLayout : WidgetGroupDefaultDrawing { 541 protected Orientation _orientation = Orientation.Vertical; 542 /// returns linear layout orientation (Vertical, Horizontal) 543 @property Orientation orientation() const { return _orientation; } 544 /// sets linear layout orientation 545 @property LinearLayout orientation(Orientation value) { _orientation = value; requestLayout(); return this; } 546 547 /// empty parameter list constructor - for usage by factory 548 this() { 549 this(null); 550 } 551 /// create with ID parameter and orientation 552 this(string ID, Orientation orientation = Orientation.Vertical) { 553 super(ID); 554 _layoutItems = new LayoutItems(); 555 _orientation = orientation; 556 } 557 558 LayoutItems _layoutItems; 559 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 560 override void measure(int parentWidth, int parentHeight) { 561 Rect m = margins; 562 Rect p = padding; 563 // calc size constraints for children 564 int pwidth = parentWidth; 565 int pheight = parentHeight; 566 if (parentWidth != SIZE_UNSPECIFIED) 567 pwidth -= m.left + m.right + p.left + p.right; 568 if (parentHeight != SIZE_UNSPECIFIED) 569 pheight -= m.top + m.bottom + p.top + p.bottom; 570 // measure children 571 _layoutItems.setLayoutParams(orientation, layoutWidth, layoutHeight); 572 _layoutItems.setWidgets(_children); 573 Point sz = _layoutItems.measure(pwidth, pheight); 574 measuredContent(parentWidth, parentHeight, sz.x, sz.y); 575 } 576 577 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 578 override void layout(Rect rc) { 579 _needLayout = false; 580 if (visibility == Visibility.Gone) { 581 return; 582 } 583 _pos = rc; 584 applyMargins(rc); 585 applyPadding(rc); 586 //debug Log.d("LinearLayout.layout id=", _id, " rc=", rc, " fillHoriz=", layoutWidth == FILL_PARENT); 587 _layoutItems.layout(rc); 588 } 589 590 } 591 592 /// Arranges children vertically 593 class VerticalLayout : LinearLayout { 594 /// empty parameter list constructor - for usage by factory 595 this() { 596 this(null); 597 } 598 /// create with ID parameter 599 this(string ID) { 600 super(ID); 601 orientation = Orientation.Vertical; 602 } 603 } 604 605 /// Arranges children horizontally 606 class HorizontalLayout : LinearLayout { 607 /// empty parameter list constructor - for usage by factory 608 this() { 609 this(null); 610 } 611 /// create with ID parameter 612 this(string ID) { 613 super(ID); 614 orientation = Orientation.Horizontal; 615 } 616 } 617 618 /// place all children into same place (usually, only one child should be visible at a time) 619 class FrameLayout : WidgetGroupDefaultDrawing { 620 /// empty parameter list constructor - for usage by factory 621 this() { 622 this(null); 623 } 624 /// create with ID parameter 625 this(string ID) { 626 super(ID); 627 } 628 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 629 override void measure(int parentWidth, int parentHeight) { 630 Rect m = margins; 631 Rect p = padding; 632 // calc size constraints for children 633 int pwidth = parentWidth; 634 int pheight = parentHeight; 635 if (parentWidth != SIZE_UNSPECIFIED) 636 pwidth -= m.left + m.right + p.left + p.right; 637 if (parentHeight != SIZE_UNSPECIFIED) 638 pheight -= m.top + m.bottom + p.top + p.bottom; 639 // measure children 640 Point sz; 641 for (int i = 0; i < _children.count; i++) { 642 Widget item = _children.get(i); 643 if (item.visibility != Visibility.Gone) { 644 item.measure(pwidth, pheight); 645 if (sz.x < item.measuredWidth) 646 sz.x = item.measuredWidth; 647 if (sz.y < item.measuredHeight) 648 sz.y = item.measuredHeight; 649 } 650 } 651 measuredContent(parentWidth, parentHeight, sz.x, sz.y); 652 } 653 654 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 655 override void layout(Rect rc) { 656 _needLayout = false; 657 if (visibility == Visibility.Gone) { 658 return; 659 } 660 _pos = rc; 661 applyMargins(rc); 662 applyPadding(rc); 663 for (int i = 0; i < _children.count; i++) { 664 Widget item = _children.get(i); 665 if (item.visibility == Visibility.Visible) { 666 item.layout(rc); 667 } 668 } 669 } 670 671 /// make one of children (with specified ID) visible, for the rest, set visibility to otherChildrenVisibility 672 bool showChild(string ID, Visibility otherChildrenVisibility = Visibility.Invisible, bool updateFocus = false) { 673 bool found = false; 674 Widget foundWidget = null; 675 for (int i = 0; i < _children.count; i++) { 676 Widget item = _children.get(i); 677 if (item.compareId(ID)) { 678 item.visibility = Visibility.Visible; 679 item.requestLayout(); 680 foundWidget = item; 681 found = true; 682 } else { 683 item.visibility = otherChildrenVisibility; 684 } 685 } 686 if (foundWidget !is null && updateFocus) 687 foundWidget.setFocus(); 688 return found; 689 } 690 } 691 692 /// layout children as table with rows and columns 693 class TableLayout : WidgetGroupDefaultDrawing { 694 695 this(string ID = null) { 696 super(ID); 697 } 698 699 this() { 700 this(null); 701 } 702 703 protected static struct TableLayoutCell { 704 int col; 705 int row; 706 Widget widget; 707 @property bool layoutWidthFill() { return widget ? widget.layoutWidth == FILL_PARENT : false; } 708 @property bool layoutHeightFill() { return widget ? widget.layoutHeight == FILL_PARENT : false; } 709 @property int measuredWidth() { return widget ? widget.measuredWidth : 0; } 710 @property int measuredHeight() { return widget ? widget.measuredHeight : 0; } 711 @property int layoutWidth() { return widget ? widget.layoutWidth : 0; } 712 @property int layoutHeight() { return widget ? widget.layoutHeight : 0; } 713 @property int minWidth() { return widget ? widget.minWidth : 0; } 714 @property int maxWidth() { return widget ? widget.maxWidth : 0; } 715 @property int minHeight() { return widget ? widget.minHeight : 0; } 716 @property int maxHeight() { return widget ? widget.maxHeight : 0; } 717 void clear(int col, int row) { 718 this.col = col; 719 this.row = row; 720 widget = null; 721 } 722 void measure(Widget w, int pwidth, int pheight) { 723 widget = w; 724 if (widget) 725 widget.measure(pwidth, pheight); 726 } 727 } 728 729 protected static struct TableLayoutGroup { 730 int index; 731 int measuredSize; 732 int layoutSize; 733 int minSize; 734 int maxSize; 735 int size; 736 bool fill; 737 void initialize(int index) { 738 measuredSize = minSize = maxSize = layoutSize = size = 0; 739 fill = false; 740 this.index = index; 741 } 742 void rowCellMeasured(ref TableLayoutCell cell) { 743 if (cell.layoutHeightFill) 744 fill = true; 745 if (measuredSize < cell.measuredHeight) 746 measuredSize = cell.measuredHeight; 747 if (minSize < cell.minHeight) 748 minSize = cell.minHeight; 749 if (cell.layoutHeight == FILL_PARENT) 750 layoutSize = FILL_PARENT; 751 size = measuredSize; 752 } 753 void colCellMeasured(ref TableLayoutCell cell) { 754 if (cell.layoutWidthFill) 755 fill = true; 756 if (measuredSize < cell.measuredWidth) 757 measuredSize = cell.measuredWidth; 758 if (minSize < cell.minWidth) 759 minSize = cell.minWidth; 760 if (cell.layoutWidth == FILL_PARENT) 761 layoutSize = FILL_PARENT; 762 size = measuredSize; 763 } 764 } 765 766 protected static struct TableLayoutHelper { 767 protected TableLayoutGroup[] _cols; 768 protected TableLayoutGroup[] _rows; 769 protected TableLayoutCell[] _cells; 770 protected int colCount; 771 protected int rowCount; 772 protected bool layoutWidthFill; 773 protected bool layoutHeightFill; 774 775 void initialize(int cols, int rows, bool layoutWidthFill, bool layoutHeightFill) { 776 colCount = cols; 777 rowCount = rows; 778 this.layoutWidthFill = layoutWidthFill; 779 this.layoutHeightFill = layoutHeightFill; 780 _cells.length = cols * rows; 781 _rows.length = rows; 782 _cols.length = cols; 783 for(int i = 0; i < rows; i++) 784 _rows[i].initialize(i); 785 for(int i = 0; i < cols; i++) 786 _cols[i].initialize(i); 787 for (int y = 0; y < rows; y++) { 788 for (int x = 0; x < cols; x++) { 789 cell(x, y).clear(x, y); 790 } 791 } 792 } 793 794 ref TableLayoutCell cell(int col, int row) { 795 return _cells[row * colCount + col]; 796 } 797 798 ref TableLayoutGroup col(int c) { 799 return _cols[c]; 800 } 801 802 ref TableLayoutGroup row(int r) { 803 return _rows[r]; 804 } 805 806 Point measure(Widget parent, int cc, int rc, int pwidth, int pheight, bool layoutWidthFill, bool layoutHeightFill) { 807 //Log.d("grid measure ", parent.id, " pw=", pwidth, " ph=", pheight); 808 initialize(cc, rc, layoutWidthFill, layoutHeightFill); 809 for (int y = 0; y < rc; y++) { 810 for (int x = 0; x < cc; x++) { 811 int index = y * cc + x; 812 Widget child = index < parent.childCount ? parent.child(index) : null; 813 cell(x, y).measure(child, pwidth, pheight); 814 //if (child) 815 // Log.d("cell ", x, ",", y, " child=", child.id, " measuredWidth=", child.measuredWidth, " minWidth=", child.minWidth); 816 } 817 } 818 // calc total row size 819 int totalHeight = 0; 820 for (int y = 0; y < rc; y++) { 821 for (int x = 0; x < cc; x++) { 822 row(y).rowCellMeasured(cell(x,y)); 823 } 824 totalHeight += row(y).measuredSize; 825 } 826 // calc total col size 827 int totalWidth = 0; 828 for (int x = 0; x < cc; x++) { 829 for (int y = 0; y < rc; y++) { 830 col(x).colCellMeasured(cell(x,y)); 831 } 832 totalWidth += col(x).measuredSize; 833 } 834 //Log.d(" ", parent.id, " w=", totalWidth, " h=", totalHeight); 835 return Point(totalWidth, totalHeight); 836 } 837 838 void layoutRows(int parentSize) { 839 if (layoutHeightFill && rowCount) { 840 int totalSize = 0; 841 int fillCount = 0; 842 for (int y = 0; y < rowCount; y++) { 843 totalSize += row(y).size; 844 if (row(y).fill) 845 fillCount++; 846 } 847 int extraSize = parentSize - totalSize; 848 int resizeCount = fillCount > 0 ? fillCount : rowCount; 849 int delta = extraSize / resizeCount; 850 int delta0 = extraSize % resizeCount; 851 852 if (extraSize > 0) { 853 for (int y = 0; y < rowCount; y++) { 854 if (fillCount == 0 || row(y).fill) { 855 row(y).size += delta + delta0; 856 delta0 = 0; 857 } 858 } 859 } 860 } 861 } 862 void layoutCols(int parentSize) { 863 if (layoutWidthFill) { 864 int totalSize = 0; 865 int fillCount = 0; 866 for (int x = 0; x < colCount; x++) { 867 totalSize += col(x).size; 868 if (col(x).fill) 869 fillCount++; 870 } 871 int extraSize = parentSize - totalSize; 872 int resizeCount = fillCount > 0 ? fillCount : colCount; 873 int delta = extraSize / resizeCount; 874 int delta0 = extraSize % resizeCount; 875 876 if (extraSize > 0) { 877 for (int x = 0; x < colCount; x++) { 878 if (fillCount == 0 || col(x).fill) { 879 col(x).size += delta + delta0; 880 delta0 = 0; 881 } 882 } 883 } else if (extraSize < 0) { 884 for (int x = 0; x < colCount; x++) { 885 if (fillCount == 0 || col(x).fill) { 886 col(x).size += delta + delta0; 887 delta0 = 0; 888 } 889 } 890 } 891 } 892 } 893 894 void layout(Rect rc) { 895 layoutRows(rc.height); 896 layoutCols(rc.width); 897 int y0 = 0; 898 for (int y = 0; y < rowCount; y++) { 899 int x0 = 0; 900 for (int x = 0; x < colCount; x++) { 901 int index = y * colCount + x; 902 Rect r; 903 r.left = rc.left + x0; 904 r.top = rc.top + y0; 905 r.right = r.left + col(x).size; 906 r.bottom = r.top + row(y).size; 907 if (cell(x, y).widget) 908 cell(x, y).widget.layout(r); 909 x0 += col(x).size; 910 } 911 y0 += row(y).size; 912 } 913 } 914 } 915 protected TableLayoutHelper _cells; 916 917 protected int _colCount = 1; 918 /// number of columns 919 @property int colCount() { return _colCount; } 920 @property TableLayout colCount(int count) { if (_colCount != count) requestLayout(); _colCount = count; return this; } 921 @property int rowCount() { 922 return (childCount + (_colCount - 1)) / _colCount * _colCount; 923 } 924 925 /// set int property value, for ML loaders 926 mixin(generatePropertySettersMethodOverride("setIntProperty", "int", 927 "colCount")); 928 929 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 930 override void measure(int parentWidth, int parentHeight) { 931 Rect m = margins; 932 Rect p = padding; 933 // calc size constraints for children 934 int pwidth = parentWidth; 935 int pheight = parentHeight; 936 if (parentWidth != SIZE_UNSPECIFIED) 937 pwidth -= m.left + m.right + p.left + p.right; 938 if (parentHeight != SIZE_UNSPECIFIED) 939 pheight -= m.top + m.bottom + p.top + p.bottom; 940 941 int rc = rowCount; 942 Point sz = _cells.measure(this, colCount, rc, pwidth, pheight, layoutWidth == FILL_PARENT, layoutHeight == FILL_PARENT); 943 measuredContent(parentWidth, parentHeight, sz.x, sz.y); 944 } 945 946 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 947 override void layout(Rect rc) { 948 _needLayout = false; 949 if (visibility == Visibility.Gone) { 950 return; 951 } 952 _pos = rc; 953 applyMargins(rc); 954 applyPadding(rc); 955 _cells.layout(rc); 956 } 957 958 } 959 960 //import dlangui.widgets.metadata; 961 //mixin(registerWidgets!(VerticalLayout, HorizontalLayout, TableLayout, FrameLayout)());