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)());