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