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 /// Orientation: Vertical to resize vertically, Horizontal - to resize horizontally 317 @property Orientation orientation() { return _orientation; } 318 /// empty parameter list constructor - for usage by factory 319 this() { 320 this(null); 321 } 322 /// create with ID parameter 323 this(string ID, Orientation orient = Orientation.Vertical) { 324 super(ID); 325 _styleVertical = "RESIZER_VERTICAL"; 326 _styleHorizontal = "RESIZER_HORIZONTAL"; 327 _orientation = orient; 328 trackHover = true; 329 } 330 331 @property bool validProps() { 332 return _previousWidget && _nextWidget; 333 } 334 335 /// returns mouse cursor type for widget 336 override uint getCursorType(int x, int y) { 337 if (_orientation == Orientation.Vertical) { 338 return CursorType.SizeNS; 339 } else { 340 return CursorType.SizeWE; 341 } 342 } 343 344 protected void updateProps() { 345 _previousWidget = null; 346 _nextWidget = null; 347 LinearLayout parentLayout = cast(LinearLayout)_parent; 348 if (parentLayout) { 349 _orientation = parentLayout.orientation; 350 int index = parentLayout.childIndex(this); 351 _previousWidget = parentLayout.child(index - 1); 352 _nextWidget = parentLayout.child(index + 1); 353 } 354 if (validProps) { 355 if (_orientation == Orientation.Vertical) { 356 styleId = _styleVertical; 357 } else { 358 styleId = _styleHorizontal; 359 } 360 } else { 361 _previousWidget = null; 362 _nextWidget = null; 363 } 364 } 365 366 /** 367 Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 368 369 */ 370 override void measure(int parentWidth, int parentHeight) { 371 updateProps(); 372 if (_orientation == Orientation.Vertical) { 373 374 } 375 measuredContent(parentWidth, parentHeight, 7, 7); 376 } 377 378 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 379 override void layout(Rect rc) { 380 updateProps(); 381 if (visibility == Visibility.Gone) { 382 return; 383 } 384 _pos = rc; 385 _needLayout = false; 386 } 387 388 protected int _delta; 389 protected int _minDragDelta; 390 protected int _maxDragDelta; 391 protected bool _dragging; 392 protected int _dragStartPosition; // drag start delta 393 protected Point _dragStart; 394 protected Rect _dragStartRect; 395 protected Rect _scrollArea; 396 397 @property int delta() { return _delta; } 398 399 /// process mouse event; return true if event is processed by widget. 400 override bool onMouseEvent(MouseEvent event) { 401 // support onClick 402 if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) { 403 setState(State.Pressed); 404 _dragging = true; 405 _dragStart.x = event.x; 406 _dragStart.y = event.y; 407 _dragStartPosition = _delta; 408 _dragStartRect = _pos; 409 _scrollArea = _pos; 410 _minDragDelta = 0; 411 _maxDragDelta = 0; 412 if (validProps) { 413 Rect r1 = _previousWidget.pos; 414 Rect r2 = _nextWidget.pos; 415 _scrollArea.left = r1.left; 416 _scrollArea.right = r2.right; 417 _scrollArea.top = r1.top; 418 _scrollArea.bottom = r2.bottom; 419 if (_orientation == Orientation.Vertical) { 420 _minDragDelta = _scrollArea.top - _dragStartRect.top; 421 _maxDragDelta = _scrollArea.bottom - _dragStartRect.bottom; 422 } 423 if (_delta < _minDragDelta) 424 _delta = _minDragDelta; 425 if (_delta > _maxDragDelta) 426 _delta = _maxDragDelta; 427 } else if (resizeEvent.assigned) { 428 resizeEvent(this, ResizerEventType.StartDragging, _orientation == Orientation.Vertical ? event.y : event.x); 429 } 430 return true; 431 } 432 if (event.action == MouseAction.FocusOut && _dragging) { 433 return true; 434 } 435 if ((event.action == MouseAction.ButtonUp && event.button == MouseButton.Left) || (!event.lbutton.isDown && _dragging)) { 436 resetState(State.Pressed); 437 if (_dragging) { 438 //sendScrollEvent(ScrollAction.SliderReleased, _position); 439 _dragging = false; 440 if (resizeEvent.assigned) { 441 resizeEvent(this, ResizerEventType.EndDragging, _orientation == Orientation.Vertical ? event.y : event.x); 442 } 443 } 444 return true; 445 } 446 if (event.action == MouseAction.Move && _dragging) { 447 int delta = _orientation == Orientation.Vertical ? event.y - _dragStart.y : event.x - _dragStart.x; 448 if (resizeEvent.assigned) { 449 resizeEvent(this, ResizerEventType.Dragging, _orientation == Orientation.Vertical ? event.y : event.x); 450 return true; 451 } 452 _delta = _dragStartPosition + delta; 453 if (_delta < _minDragDelta) 454 _delta = _minDragDelta; 455 if (_delta > _maxDragDelta) 456 _delta = _maxDragDelta; 457 Rect rc = _dragStartRect; 458 int offset; 459 int space; 460 if (_orientation == Orientation.Vertical) { 461 rc.top += delta; 462 rc.bottom += delta; 463 if (rc.top < _scrollArea.top) { 464 rc.top = _scrollArea.top; 465 rc.bottom = _scrollArea.top + _dragStartRect.height; 466 } else if (rc.bottom > _scrollArea.bottom) { 467 rc.top = _scrollArea.bottom - _dragStartRect.height; 468 rc.bottom = _scrollArea.bottom; 469 } 470 offset = rc.top - _scrollArea.top; 471 space = _scrollArea.height - rc.height; 472 } else { 473 rc.left += delta; 474 rc.right += delta; 475 if (rc.left < _scrollArea.left) { 476 rc.left = _scrollArea.left; 477 rc.right = _scrollArea.left + _dragStartRect.width; 478 } else if (rc.right > _scrollArea.right) { 479 rc.left = _scrollArea.right - _dragStartRect.width; 480 rc.right = _scrollArea.right; 481 } 482 offset = rc.left - _scrollArea.left; 483 space = _scrollArea.width - rc.width; 484 } 485 //_pos = rc; 486 //int position = space > 0 ? _minValue + offset * (_maxValue - _minValue - _pageSize) / space : 0; 487 requestLayout(); 488 invalidate(); 489 //onIndicatorDragging(_dragStartPosition, position); 490 return true; 491 } 492 if (event.action == MouseAction.Move && trackHover) { 493 if (!(state & State.Hovered)) { 494 //Log.d("Hover ", id); 495 setState(State.Hovered); 496 } 497 return true; 498 } 499 if ((event.action == MouseAction.Leave || event.action == MouseAction.Cancel) && trackHover) { 500 //Log.d("Leave ", id); 501 resetState(State.Hovered); 502 return true; 503 } 504 if (event.action == MouseAction.Cancel) { 505 //Log.d("SliderButton.onMouseEvent event.action == MouseAction.Cancel"); 506 if (_dragging) { 507 resetState(State.Pressed); 508 _dragging = false; 509 if (resizeEvent.assigned) { 510 resizeEvent(this, ResizerEventType.EndDragging, _orientation == Orientation.Vertical ? event.y : event.x); 511 } 512 } 513 return true; 514 } 515 return false; 516 } 517 } 518 519 520 /// Arranges items either vertically or horizontally 521 class LinearLayout : WidgetGroupDefaultDrawing { 522 protected Orientation _orientation = Orientation.Vertical; 523 /// returns linear layout orientation (Vertical, Horizontal) 524 @property Orientation orientation() { return _orientation; } 525 /// sets linear layout orientation 526 @property LinearLayout orientation(Orientation value) { _orientation = value; requestLayout(); return this; } 527 528 /// empty parameter list constructor - for usage by factory 529 this() { 530 this(null); 531 } 532 /// create with ID parameter and orientation 533 this(string ID, Orientation orientation = Orientation.Vertical) { 534 super(ID); 535 _layoutItems = new LayoutItems(); 536 _orientation = orientation; 537 } 538 539 LayoutItems _layoutItems; 540 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 541 override void measure(int parentWidth, int parentHeight) { 542 Rect m = margins; 543 Rect p = padding; 544 // calc size constraints for children 545 int pwidth = parentWidth; 546 int pheight = parentHeight; 547 if (parentWidth != SIZE_UNSPECIFIED) 548 pwidth -= m.left + m.right + p.left + p.right; 549 if (parentHeight != SIZE_UNSPECIFIED) 550 pheight -= m.top + m.bottom + p.top + p.bottom; 551 // measure children 552 _layoutItems.setLayoutParams(orientation, layoutWidth, layoutHeight); 553 _layoutItems.setWidgets(_children); 554 Point sz = _layoutItems.measure(pwidth, pheight); 555 measuredContent(parentWidth, parentHeight, sz.x, sz.y); 556 } 557 558 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 559 override void layout(Rect rc) { 560 _needLayout = false; 561 if (visibility == Visibility.Gone) { 562 return; 563 } 564 _pos = rc; 565 applyMargins(rc); 566 applyPadding(rc); 567 //debug Log.d("LinearLayout.layout id=", _id, " rc=", rc, " fillHoriz=", layoutWidth == FILL_PARENT); 568 _layoutItems.layout(rc); 569 } 570 571 } 572 573 /// Arranges children vertically 574 class VerticalLayout : LinearLayout { 575 /// empty parameter list constructor - for usage by factory 576 this() { 577 this(null); 578 } 579 /// create with ID parameter 580 this(string ID) { 581 super(ID); 582 orientation = Orientation.Vertical; 583 } 584 } 585 586 /// Arranges children horizontally 587 class HorizontalLayout : LinearLayout { 588 /// empty parameter list constructor - for usage by factory 589 this() { 590 this(null); 591 } 592 /// create with ID parameter 593 this(string ID) { 594 super(ID); 595 orientation = Orientation.Horizontal; 596 } 597 } 598 599 /// place all children into same place (usually, only one child should be visible at a time) 600 class FrameLayout : WidgetGroupDefaultDrawing { 601 /// empty parameter list constructor - for usage by factory 602 this() { 603 this(null); 604 } 605 /// create with ID parameter 606 this(string ID) { 607 super(ID); 608 } 609 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 610 override void measure(int parentWidth, int parentHeight) { 611 Rect m = margins; 612 Rect p = padding; 613 // calc size constraints for children 614 int pwidth = parentWidth; 615 int pheight = parentHeight; 616 if (parentWidth != SIZE_UNSPECIFIED) 617 pwidth -= m.left + m.right + p.left + p.right; 618 if (parentHeight != SIZE_UNSPECIFIED) 619 pheight -= m.top + m.bottom + p.top + p.bottom; 620 // measure children 621 Point sz; 622 for (int i = 0; i < _children.count; i++) { 623 Widget item = _children.get(i); 624 if (item.visibility != Visibility.Gone) { 625 item.measure(pwidth, pheight); 626 if (sz.x < item.measuredWidth) 627 sz.x = item.measuredWidth; 628 if (sz.y < item.measuredHeight) 629 sz.y = item.measuredHeight; 630 } 631 } 632 measuredContent(parentWidth, parentHeight, sz.x, sz.y); 633 } 634 635 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 636 override void layout(Rect rc) { 637 _needLayout = false; 638 if (visibility == Visibility.Gone) { 639 return; 640 } 641 _pos = rc; 642 applyMargins(rc); 643 applyPadding(rc); 644 for (int i = 0; i < _children.count; i++) { 645 Widget item = _children.get(i); 646 if (item.visibility != Visibility.Gone) { 647 item.layout(rc); 648 } 649 } 650 } 651 652 /// make one of children (with specified ID) visible, for the rest, set visibility to otherChildrenVisibility 653 bool showChild(string ID, Visibility otherChildrenVisibility = Visibility.Invisible, bool updateFocus = false) { 654 bool found = false; 655 Widget foundWidget = null; 656 for (int i = 0; i < _children.count; i++) { 657 Widget item = _children.get(i); 658 if (item.compareId(ID)) { 659 item.visibility = Visibility.Visible; 660 foundWidget = item; 661 found = true; 662 } else { 663 item.visibility = otherChildrenVisibility; 664 } 665 } 666 if (foundWidget !is null && updateFocus) 667 foundWidget.setFocus(); 668 return found; 669 } 670 } 671 672 /// layout children as table with rows and columns 673 class TableLayout : WidgetGroupDefaultDrawing { 674 675 this(string ID = null) { 676 super(ID); 677 } 678 679 this() { 680 this(null); 681 } 682 683 protected static struct TableLayoutCell { 684 int col; 685 int row; 686 Widget widget; 687 @property bool layoutWidthFill() { return widget ? widget.layoutWidth == FILL_PARENT : false; } 688 @property bool layoutHeightFill() { return widget ? widget.layoutHeight == FILL_PARENT : false; } 689 @property int measuredWidth() { return widget ? widget.measuredWidth : 0; } 690 @property int measuredHeight() { return widget ? widget.measuredHeight : 0; } 691 @property int layoutWidth() { return widget ? widget.layoutWidth : 0; } 692 @property int layoutHeight() { return widget ? widget.layoutHeight : 0; } 693 @property int minWidth() { return widget ? widget.minWidth : 0; } 694 @property int maxWidth() { return widget ? widget.maxWidth : 0; } 695 @property int minHeight() { return widget ? widget.minHeight : 0; } 696 @property int maxHeight() { return widget ? widget.maxHeight : 0; } 697 void clear(int col, int row) { 698 this.col = col; 699 this.row = row; 700 widget = null; 701 } 702 void measure(Widget w, int pwidth, int pheight) { 703 widget = w; 704 if (widget) 705 widget.measure(pwidth, pheight); 706 } 707 } 708 709 protected static struct TableLayoutGroup { 710 int index; 711 int measuredSize; 712 int layoutSize; 713 int minSize; 714 int maxSize; 715 int size; 716 bool fill; 717 void initialize(int index) { 718 measuredSize = minSize = maxSize = layoutSize = size = 0; 719 fill = false; 720 this.index = index; 721 } 722 void rowCellMeasured(ref TableLayoutCell cell) { 723 if (cell.layoutHeightFill) 724 fill = true; 725 if (measuredSize < cell.measuredHeight) 726 measuredSize = cell.measuredHeight; 727 if (minSize < cell.minHeight) 728 minSize = cell.minHeight; 729 if (cell.layoutHeight == FILL_PARENT) 730 layoutSize = FILL_PARENT; 731 size = measuredSize; 732 } 733 void colCellMeasured(ref TableLayoutCell cell) { 734 if (cell.layoutWidthFill) 735 fill = true; 736 if (measuredSize < cell.measuredWidth) 737 measuredSize = cell.measuredWidth; 738 if (minSize < cell.minWidth) 739 minSize = cell.minWidth; 740 if (cell.layoutWidth == FILL_PARENT) 741 layoutSize = FILL_PARENT; 742 size = measuredSize; 743 } 744 } 745 746 protected static struct TableLayoutHelper { 747 protected TableLayoutGroup[] _cols; 748 protected TableLayoutGroup[] _rows; 749 protected TableLayoutCell[] _cells; 750 protected int colCount; 751 protected int rowCount; 752 protected bool layoutWidthFill; 753 protected bool layoutHeightFill; 754 755 void initialize(int cols, int rows, bool layoutWidthFill, bool layoutHeightFill) { 756 colCount = cols; 757 rowCount = rows; 758 this.layoutWidthFill = layoutWidthFill; 759 this.layoutHeightFill = layoutHeightFill; 760 _cells.length = cols * rows; 761 _rows.length = rows; 762 _cols.length = cols; 763 for(int i = 0; i < rows; i++) 764 _rows[i].initialize(i); 765 for(int i = 0; i < cols; i++) 766 _cols[i].initialize(i); 767 for (int y = 0; y < rows; y++) { 768 for (int x = 0; x < cols; x++) { 769 cell(x, y).clear(x, y); 770 } 771 } 772 } 773 774 ref TableLayoutCell cell(int col, int row) { 775 return _cells[row * colCount + col]; 776 } 777 778 ref TableLayoutGroup col(int c) { 779 return _cols[c]; 780 } 781 782 ref TableLayoutGroup row(int r) { 783 return _rows[r]; 784 } 785 786 Point measure(Widget parent, int cc, int rc, int pwidth, int pheight, bool layoutWidthFill, bool layoutHeightFill) { 787 //Log.d("grid measure ", parent.id, " pw=", pwidth, " ph=", pheight); 788 initialize(cc, rc, layoutWidthFill, layoutHeightFill); 789 for (int y = 0; y < rc; y++) { 790 for (int x = 0; x < cc; x++) { 791 int index = y * cc + x; 792 Widget child = index < parent.childCount ? parent.child(index) : null; 793 cell(x, y).measure(child, pwidth, pheight); 794 //if (child) 795 // Log.d("cell ", x, ",", y, " child=", child.id, " measuredWidth=", child.measuredWidth, " minWidth=", child.minWidth); 796 } 797 } 798 // calc total row size 799 int totalHeight = 0; 800 for (int y = 0; y < rc; y++) { 801 for (int x = 0; x < cc; x++) { 802 row(y).rowCellMeasured(cell(x,y)); 803 } 804 totalHeight += row(y).measuredSize; 805 } 806 // calc total col size 807 int totalWidth = 0; 808 for (int x = 0; x < cc; x++) { 809 for (int y = 0; y < rc; y++) { 810 col(x).colCellMeasured(cell(x,y)); 811 } 812 totalWidth += col(x).measuredSize; 813 } 814 //Log.d(" ", parent.id, " w=", totalWidth, " h=", totalHeight); 815 return Point(totalWidth, totalHeight); 816 } 817 818 void layoutRows(int parentSize) { 819 if (layoutHeightFill && rowCount) { 820 int totalSize = 0; 821 int fillCount = 0; 822 for (int y = 0; y < rowCount; y++) { 823 totalSize += row(y).size; 824 if (row(y).fill) 825 fillCount++; 826 } 827 int extraSize = parentSize - totalSize; 828 int resizeCount = fillCount > 0 ? fillCount : rowCount; 829 int delta = extraSize / resizeCount; 830 int delta0 = extraSize % resizeCount; 831 832 if (extraSize > 0) { 833 for (int y = 0; y < rowCount; y++) { 834 if (fillCount == 0 || row(y).fill) { 835 row(y).size += delta + delta0; 836 delta0 = 0; 837 } 838 } 839 } 840 } 841 } 842 void layoutCols(int parentSize) { 843 if (layoutWidthFill) { 844 int totalSize = 0; 845 int fillCount = 0; 846 for (int x = 0; x < colCount; x++) { 847 totalSize += col(x).size; 848 if (col(x).fill) 849 fillCount++; 850 } 851 int extraSize = parentSize - totalSize; 852 int resizeCount = fillCount > 0 ? fillCount : colCount; 853 int delta = extraSize / resizeCount; 854 int delta0 = extraSize % resizeCount; 855 856 if (extraSize > 0) { 857 for (int x = 0; x < colCount; x++) { 858 if (fillCount == 0 || col(x).fill) { 859 col(x).size += delta + delta0; 860 delta0 = 0; 861 } 862 } 863 } else if (extraSize < 0) { 864 for (int x = 0; x < colCount; x++) { 865 if (fillCount == 0 || col(x).fill) { 866 col(x).size += delta + delta0; 867 delta0 = 0; 868 } 869 } 870 } 871 } 872 } 873 874 void layout(Rect rc) { 875 layoutRows(rc.height); 876 layoutCols(rc.width); 877 int y0 = 0; 878 for (int y = 0; y < rowCount; y++) { 879 int x0 = 0; 880 for (int x = 0; x < colCount; x++) { 881 int index = y * colCount + x; 882 Rect r; 883 r.left = rc.left + x0; 884 r.top = rc.top + y0; 885 r.right = r.left + col(x).size; 886 r.bottom = r.top + row(y).size; 887 if (cell(x, y).widget) 888 cell(x, y).widget.layout(r); 889 x0 += col(x).size; 890 } 891 y0 += row(y).size; 892 } 893 } 894 } 895 protected TableLayoutHelper _cells; 896 897 protected int _colCount = 1; 898 /// number of columns 899 @property int colCount() { return _colCount; } 900 @property TableLayout colCount(int count) { if (_colCount != count) requestLayout(); _colCount = count; return this; } 901 @property int rowCount() { 902 return (childCount + (_colCount - 1)) / _colCount * _colCount; 903 } 904 905 /// set int property value, for ML loaders 906 mixin(generatePropertySettersMethodOverride("setIntProperty", "int", 907 "colCount")); 908 909 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout). 910 override void measure(int parentWidth, int parentHeight) { 911 Rect m = margins; 912 Rect p = padding; 913 // calc size constraints for children 914 int pwidth = parentWidth; 915 int pheight = parentHeight; 916 if (parentWidth != SIZE_UNSPECIFIED) 917 pwidth -= m.left + m.right + p.left + p.right; 918 if (parentHeight != SIZE_UNSPECIFIED) 919 pheight -= m.top + m.bottom + p.top + p.bottom; 920 921 int rc = rowCount; 922 Point sz = _cells.measure(this, colCount, rc, pwidth, pheight, layoutWidth == FILL_PARENT, layoutHeight == FILL_PARENT); 923 measuredContent(parentWidth, parentHeight, sz.x, sz.y); 924 } 925 926 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout). 927 override void layout(Rect rc) { 928 _needLayout = false; 929 if (visibility == Visibility.Gone) { 930 return; 931 } 932 _pos = rc; 933 applyMargins(rc); 934 applyPadding(rc); 935 _cells.layout(rc); 936 } 937 938 } 939 940 //import dlangui.widgets.metadata; 941 //mixin(registerWidgets!(VerticalLayout, HorizontalLayout, TableLayout, FrameLayout)());