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