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() const { 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.Visible) {
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 item.requestLayout();
661 foundWidget = item;
662 found = true;
663 } else {
664 item.visibility = otherChildrenVisibility;
665 }
666 }
667 if (foundWidget !is null && updateFocus)
668 foundWidget.setFocus();
669 return found;
670 }
671 }
672
673 /// layout children as table with rows and columns
674 class TableLayout : WidgetGroupDefaultDrawing {
675
676 this(string ID = null) {
677 super(ID);
678 }
679
680 this() {
681 this(null);
682 }
683
684 protected static struct TableLayoutCell {
685 int col;
686 int row;
687 Widget widget;
688 @property bool layoutWidthFill() { return widget ? widget.layoutWidth == FILL_PARENT : false; }
689 @property bool layoutHeightFill() { return widget ? widget.layoutHeight == FILL_PARENT : false; }
690 @property int measuredWidth() { return widget ? widget.measuredWidth : 0; }
691 @property int measuredHeight() { return widget ? widget.measuredHeight : 0; }
692 @property int layoutWidth() { return widget ? widget.layoutWidth : 0; }
693 @property int layoutHeight() { return widget ? widget.layoutHeight : 0; }
694 @property int minWidth() { return widget ? widget.minWidth : 0; }
695 @property int maxWidth() { return widget ? widget.maxWidth : 0; }
696 @property int minHeight() { return widget ? widget.minHeight : 0; }
697 @property int maxHeight() { return widget ? widget.maxHeight : 0; }
698 void clear(int col, int row) {
699 this.col = col;
700 this.row = row;
701 widget = null;
702 }
703 void measure(Widget w, int pwidth, int pheight) {
704 widget = w;
705 if (widget)
706 widget.measure(pwidth, pheight);
707 }
708 }
709
710 protected static struct TableLayoutGroup {
711 int index;
712 int measuredSize;
713 int layoutSize;
714 int minSize;
715 int maxSize;
716 int size;
717 bool fill;
718 void initialize(int index) {
719 measuredSize = minSize = maxSize = layoutSize = size = 0;
720 fill = false;
721 this.index = index;
722 }
723 void rowCellMeasured(ref TableLayoutCell cell) {
724 if (cell.layoutHeightFill)
725 fill = true;
726 if (measuredSize < cell.measuredHeight)
727 measuredSize = cell.measuredHeight;
728 if (minSize < cell.minHeight)
729 minSize = cell.minHeight;
730 if (cell.layoutHeight == FILL_PARENT)
731 layoutSize = FILL_PARENT;
732 size = measuredSize;
733 }
734 void colCellMeasured(ref TableLayoutCell cell) {
735 if (cell.layoutWidthFill)
736 fill = true;
737 if (measuredSize < cell.measuredWidth)
738 measuredSize = cell.measuredWidth;
739 if (minSize < cell.minWidth)
740 minSize = cell.minWidth;
741 if (cell.layoutWidth == FILL_PARENT)
742 layoutSize = FILL_PARENT;
743 size = measuredSize;
744 }
745 }
746
747 protected static struct TableLayoutHelper {
748 protected TableLayoutGroup[] _cols;
749 protected TableLayoutGroup[] _rows;
750 protected TableLayoutCell[] _cells;
751 protected int colCount;
752 protected int rowCount;
753 protected bool layoutWidthFill;
754 protected bool layoutHeightFill;
755
756 void initialize(int cols, int rows, bool layoutWidthFill, bool layoutHeightFill) {
757 colCount = cols;
758 rowCount = rows;
759 this.layoutWidthFill = layoutWidthFill;
760 this.layoutHeightFill = layoutHeightFill;
761 _cells.length = cols * rows;
762 _rows.length = rows;
763 _cols.length = cols;
764 for(int i = 0; i < rows; i++)
765 _rows[i].initialize(i);
766 for(int i = 0; i < cols; i++)
767 _cols[i].initialize(i);
768 for (int y = 0; y < rows; y++) {
769 for (int x = 0; x < cols; x++) {
770 cell(x, y).clear(x, y);
771 }
772 }
773 }
774
775 ref TableLayoutCell cell(int col, int row) {
776 return _cells[row * colCount + col];
777 }
778
779 ref TableLayoutGroup col(int c) {
780 return _cols[c];
781 }
782
783 ref TableLayoutGroup row(int r) {
784 return _rows[r];
785 }
786
787 Point measure(Widget parent, int cc, int rc, int pwidth, int pheight, bool layoutWidthFill, bool layoutHeightFill) {
788 //Log.d("grid measure ", parent.id, " pw=", pwidth, " ph=", pheight);
789 initialize(cc, rc, layoutWidthFill, layoutHeightFill);
790 for (int y = 0; y < rc; y++) {
791 for (int x = 0; x < cc; x++) {
792 int index = y * cc + x;
793 Widget child = index < parent.childCount ? parent.child(index) : null;
794 cell(x, y).measure(child, pwidth, pheight);
795 //if (child)
796 // Log.d("cell ", x, ",", y, " child=", child.id, " measuredWidth=", child.measuredWidth, " minWidth=", child.minWidth);
797 }
798 }
799 // calc total row size
800 int totalHeight = 0;
801 for (int y = 0; y < rc; y++) {
802 for (int x = 0; x < cc; x++) {
803 row(y).rowCellMeasured(cell(x,y));
804 }
805 totalHeight += row(y).measuredSize;
806 }
807 // calc total col size
808 int totalWidth = 0;
809 for (int x = 0; x < cc; x++) {
810 for (int y = 0; y < rc; y++) {
811 col(x).colCellMeasured(cell(x,y));
812 }
813 totalWidth += col(x).measuredSize;
814 }
815 //Log.d(" ", parent.id, " w=", totalWidth, " h=", totalHeight);
816 return Point(totalWidth, totalHeight);
817 }
818
819 void layoutRows(int parentSize) {
820 if (layoutHeightFill && rowCount) {
821 int totalSize = 0;
822 int fillCount = 0;
823 for (int y = 0; y < rowCount; y++) {
824 totalSize += row(y).size;
825 if (row(y).fill)
826 fillCount++;
827 }
828 int extraSize = parentSize - totalSize;
829 int resizeCount = fillCount > 0 ? fillCount : rowCount;
830 int delta = extraSize / resizeCount;
831 int delta0 = extraSize % resizeCount;
832
833 if (extraSize > 0) {
834 for (int y = 0; y < rowCount; y++) {
835 if (fillCount == 0 || row(y).fill) {
836 row(y).size += delta + delta0;
837 delta0 = 0;
838 }
839 }
840 }
841 }
842 }
843 void layoutCols(int parentSize) {
844 if (layoutWidthFill) {
845 int totalSize = 0;
846 int fillCount = 0;
847 for (int x = 0; x < colCount; x++) {
848 totalSize += col(x).size;
849 if (col(x).fill)
850 fillCount++;
851 }
852 int extraSize = parentSize - totalSize;
853 int resizeCount = fillCount > 0 ? fillCount : colCount;
854 int delta = extraSize / resizeCount;
855 int delta0 = extraSize % resizeCount;
856
857 if (extraSize > 0) {
858 for (int x = 0; x < colCount; x++) {
859 if (fillCount == 0 || col(x).fill) {
860 col(x).size += delta + delta0;
861 delta0 = 0;
862 }
863 }
864 } else if (extraSize < 0) {
865 for (int x = 0; x < colCount; x++) {
866 if (fillCount == 0 || col(x).fill) {
867 col(x).size += delta + delta0;
868 delta0 = 0;
869 }
870 }
871 }
872 }
873 }
874
875 void layout(Rect rc) {
876 layoutRows(rc.height);
877 layoutCols(rc.width);
878 int y0 = 0;
879 for (int y = 0; y < rowCount; y++) {
880 int x0 = 0;
881 for (int x = 0; x < colCount; x++) {
882 int index = y * colCount + x;
883 Rect r;
884 r.left = rc.left + x0;
885 r.top = rc.top + y0;
886 r.right = r.left + col(x).size;
887 r.bottom = r.top + row(y).size;
888 if (cell(x, y).widget)
889 cell(x, y).widget.layout(r);
890 x0 += col(x).size;
891 }
892 y0 += row(y).size;
893 }
894 }
895 }
896 protected TableLayoutHelper _cells;
897
898 protected int _colCount = 1;
899 /// number of columns
900 @property int colCount() { return _colCount; }
901 @property TableLayout colCount(int count) { if (_colCount != count) requestLayout(); _colCount = count; return this; }
902 @property int rowCount() {
903 return (childCount + (_colCount - 1)) / _colCount * _colCount;
904 }
905
906 /// set int property value, for ML loaders
907 mixin(generatePropertySettersMethodOverride("setIntProperty", "int",
908 "colCount"));
909
910 /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout).
911 override void measure(int parentWidth, int parentHeight) {
912 Rect m = margins;
913 Rect p = padding;
914 // calc size constraints for children
915 int pwidth = parentWidth;
916 int pheight = parentHeight;
917 if (parentWidth != SIZE_UNSPECIFIED)
918 pwidth -= m.left + m.right + p.left + p.right;
919 if (parentHeight != SIZE_UNSPECIFIED)
920 pheight -= m.top + m.bottom + p.top + p.bottom;
921
922 int rc = rowCount;
923 Point sz = _cells.measure(this, colCount, rc, pwidth, pheight, layoutWidth == FILL_PARENT, layoutHeight == FILL_PARENT);
924 measuredContent(parentWidth, parentHeight, sz.x, sz.y);
925 }
926
927 /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout).
928 override void layout(Rect rc) {
929 _needLayout = false;
930 if (visibility == Visibility.Gone) {
931 return;
932 }
933 _pos = rc;
934 applyMargins(rc);
935 applyPadding(rc);
936 _cells.layout(rc);
937 }
938
939 }
940
941 //import dlangui.widgets.metadata;
942 //mixin(registerWidgets!(VerticalLayout, HorizontalLayout, TableLayout, FrameLayout)());