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