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