1 // Written in the D programming language.
2 
3 /**
4 DLANGUI library.
5 
6 This module contains common layouts implementations.
7 
8 Layouts are similar to the same in Android.
9 
10 LinearLayout - either VerticalLayout or HorizontalLayout.
11 FrameLayout - children occupy the same place, usually one one is visible at a time
12 
13 
14 Synopsis:
15 
16 ----
17 import dlangui.widgets.layouts;
18 
19 ----
20 
21 Copyright: Vadim Lopatin, 2014
22 License:   $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
23 Authors:   $(WEB coolreader.org, Vadim Lopatin)
24 */
25 module dlangui.widgets.layouts;
26 
27 public import dlangui.widgets.widget;
28 
29 /// helper for layouts
30 struct LayoutItem {
31 	Widget _widget;
32 	Orientation _orientation;
33 	int _measuredSize; // primary size for orientation
34 	int _secondarySize; // other measured size
35 	int _layoutSize; //  layout size for primary dimension
36 	int _minSize; //  min size for primary dimension
37 	int _maxSize; //  max size for primary dimension
38 	int _weight; // weight
39 	bool _fillParent;
40     @property int measuredSize() { return _measuredSize; }
41     @property int minSize() { return _measuredSize; }
42     @property int maxSize() { return _maxSize; }
43     @property int layoutSize() { return _layoutSize; }
44     @property int secondarySize() { return _layoutSize; }
45     @property bool fillParent() { return _fillParent; }
46     @property int weight() { return _weight; }
47 	// just to help GC
48 	void clear() {
49 		_widget = null;
50 	}
51     /// sets item for widget
52     void set(Widget widget, Orientation orientation) {
53 		_widget = widget;
54 		_orientation = orientation;
55     }
56 	/// set item and measure it
57 	void measure(int parentWidth, int parentHeight) {
58 		_widget.measure(parentWidth, parentHeight);
59         _weight = _widget.layoutWeight;
60 		if (_orientation == Orientation.Horizontal) {
61 			_secondarySize = _widget.measuredHeight;
62 			_measuredSize = _widget.measuredWidth;
63 			_minSize = _widget.minWidth;
64 			_maxSize = _widget.maxWidth;
65 			_layoutSize = _widget.layoutWidth;
66 		} else {
67 			_secondarySize = _widget.measuredWidth;
68 			_measuredSize = _widget.measuredHeight;
69 			_minSize = _widget.minHeight;
70 			_maxSize = _widget.maxHeight;
71 			_layoutSize = _widget.layoutHeight;
72 		}
73 		_fillParent = _layoutSize == FILL_PARENT;
74 	}
75     void layout(ref Rect rc) {
76         _widget.layout(rc);
77     }
78 }
79 
80 /// helper class for layouts
81 class LayoutItems {
82 	Orientation _orientation;
83 	LayoutItem[] _list;
84 	int _count;
85 	int _totalSize;
86 	int _maxSecondarySize;
87     Point _measureParentSize;
88 
89     int _layoutWidth;
90     int _layoutHeight;
91 
92     void setLayoutParams(Orientation orientation, int layoutWidth, int layoutHeight) {
93         _orientation = orientation;
94         _layoutWidth = layoutWidth;
95         _layoutHeight = layoutHeight;
96     }
97 
98 	/// fill widget layout list with Visible or Invisible items, measure them
99 	Point measure(int parentWidth, int parentHeight) {
100 		_totalSize = 0;
101 		_maxSecondarySize = 0;
102         _measureParentSize.x = parentWidth;
103         _measureParentSize.y = parentHeight;
104 		// measure
105 		for (int i = 0; i < _count; i++) {
106 			LayoutItem * item = &_list[i];
107 			item.measure(parentWidth, parentHeight);
108 			if (_maxSecondarySize < item._secondarySize)
109 				_maxSecondarySize = item._secondarySize;
110 			_totalSize += item._measuredSize;
111 		}
112 		return _orientation == Orientation.Horizontal ? Point(_totalSize, _maxSecondarySize) : Point(_maxSecondarySize, _totalSize);
113 	}
114 
115 	/// fill widget layout list with Visible or Invisible items, measure them
116 	void setWidgets(ref WidgetList widgets) {
117 		// remove old items, if any
118 		clear();
119 		// reserve space
120 		if (_list.length < widgets.count)
121 			_list.length = widgets.count;
122 		// copy
123 		for (int i = 0; i < widgets.count; i++) {
124 			Widget item = widgets.get(i);
125 			if (item.visibility == Visibility.Gone)
126 				continue;
127 			_list[_count++].set(item, _orientation);
128 		}
129 	}
130 
131     void layout(Rect rc) {
132         // measure again - available area could be changed
133         if (_measureParentSize.x != rc.width || _measureParentSize.y != rc.height)
134             measure(rc.width, rc.height);
135         int contentSecondarySize = 0;
136         int contentHeight = 0;
137         int totalSize = 0;
138         int delta = 0;
139         int resizableSize = 0;
140         int resizableWeight = 0;
141         int nonresizableSize = 0;
142         int nonresizableWeight = 0;
143         int maxItem = 0; // max item dimention
144         // calc total size
145         int visibleCount = cast(int)_list.length;
146         for (int i = 0; i < _count; i++) {
147 			LayoutItem * item = &_list[i];
148             int weight = item.weight;
149 			int size = item.measuredSize;
150             totalSize += size;
151             if (maxItem < item.secondarySize)
152                 maxItem = item.secondarySize;
153             if (item.fillParent) {
154                 resizableWeight += weight;
155                 resizableSize += size * weight;
156             } else {
157                 nonresizableWeight += weight;
158                 nonresizableSize += size * weight;
159             }
160         }
161         if (_orientation == Orientation.Vertical) {
162             if (_layoutWidth == WRAP_CONTENT && maxItem < rc.width)
163                 contentSecondarySize = maxItem;
164             else
165                 contentSecondarySize = rc.width;
166             if (_layoutHeight == FILL_PARENT || totalSize > rc.height)
167                 delta = rc.height - totalSize; // total space to add to fit
168         } else {
169             if (_layoutHeight == WRAP_CONTENT && maxItem < rc.height)
170                 contentSecondarySize = maxItem;
171             else
172                 contentSecondarySize = rc.height;
173             if (_layoutWidth == FILL_PARENT || totalSize > rc.width)
174                 delta = rc.width - totalSize; // total space to add to fit
175         }
176 		// calculate resize options and scale
177         bool needForceResize = false;
178         bool needResize = false;
179         int scaleFactor = 10000; // per weight unit
180         if (delta != 0 && visibleCount > 0) {
181             // need resize of some children
182             needResize = true;
183 			// resize all if need to shrink or only resizable are too small to correct delta
184             needForceResize = delta < 0 || resizableWeight == 0; // || resizableSize * 2 / 3 < delta; // do we need resize non-FILL_PARENT items?
185 			// calculate scale factor: weight / delta * 10000
186             if (needForceResize && nonresizableSize + resizableSize > 0)
187                 scaleFactor = 10000 * delta / (nonresizableSize + resizableSize);
188             else if (resizableSize > 0)
189                 scaleFactor = 10000 * delta / resizableSize;
190 			else
191 				scaleFactor = 0;
192         }
193 		//Log.d("VerticalLayout delta=", delta, ", nonres=", nonresizableWeight, ", res=", resizableWeight, ", scale=", scaleFactor);
194 		// find last resized - to allow fill space 1 pixel accurate
195 		int lastResized = -1;
196         for (int i = 0; i < _count; i++) {
197 			LayoutItem * item = &_list[i];
198             if (item.fillParent || needForceResize) {
199 				lastResized = i;
200             }
201 		}
202 		// final resize and layout of children
203         int position = 0;
204 		int deltaTotal = 0;
205         for (int i = 0; i < _count; i++) {
206 			LayoutItem * item = &_list[i];
207             int layoutSize = item.layoutSize;
208             int weight = item.weight;
209 			int size = item.measuredSize;
210             if (needResize && (layoutSize == FILL_PARENT || needForceResize)) {
211 				// do resize
212 				int correction = scaleFactor * weight * size / 10000;
213 				deltaTotal += correction;
214 				// for last resized, apply additional correction to resolve calculation inaccuracy
215 				if (i == lastResized) {
216 					correction += delta - deltaTotal;
217 				}
218 				size += correction;
219             }
220 			// apply size
221 			Rect childRect = rc;
222             if (_orientation == Orientation.Vertical) {
223                 // Vertical
224                 childRect.top += position;
225 			    childRect.bottom = childRect.top + size;
226 			    childRect.right = childRect.left + contentSecondarySize;
227 			    item.layout(childRect);
228             } else {
229                 // Horizontal
230                 childRect.left += position;
231 			    childRect.right = childRect.left + size;
232 			    childRect.bottom = childRect.top + contentSecondarySize;
233 			    item.layout(childRect);
234             }
235 			position += size;
236         }
237     }
238 
239 	void clear() {
240 		for (int i = 0; i < _count; i++)
241 			_list[i].clear();
242 		_count = 0;
243 	}
244 	~this() {
245 		clear();
246 	}
247 }
248 
249 class LinearLayout : WidgetGroup {
250     protected Orientation _orientation = Orientation.Vertical;
251     /// returns linear layout orientation (Vertical, Horizontal)
252     @property Orientation orientation() { return _orientation; }
253     /// sets linear layout orientation
254     @property LinearLayout orientation(Orientation value) { _orientation = value; requestLayout(); return this; }
255 
256 	this(string ID = null) {
257 		super(ID);
258 		_layoutItems = new LayoutItems();
259 	}
260 
261 	LayoutItems _layoutItems;
262     /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout).
263     override void measure(int parentWidth, int parentHeight) { 
264         Rect m = margins;
265         Rect p = padding;
266         // calc size constraints for children
267         int pwidth = parentWidth;
268         int pheight = parentHeight;
269         if (parentWidth != SIZE_UNSPECIFIED)
270             pwidth -= m.left + m.right + p.left + p.right;
271         if (parentHeight != SIZE_UNSPECIFIED)
272             pheight -= m.top + m.bottom + p.top + p.bottom;
273         // measure children
274         _layoutItems.setLayoutParams(orientation, layoutWidth, layoutHeight);
275         _layoutItems.setWidgets(_children);
276 		Point sz = _layoutItems.measure(pwidth, pheight);
277         measuredContent(parentWidth, parentHeight, sz.x, sz.y);
278     }
279 
280     /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout).
281     override void layout(Rect rc) {
282         _needLayout = false;
283         if (visibility == Visibility.Gone) {
284             return;
285         }
286         _pos = rc;
287         applyMargins(rc);
288         applyPadding(rc);
289         _layoutItems.layout(rc);
290     }
291     /// Draw widget at its position to buffer
292     override void onDraw(DrawBuf buf) {
293         if (visibility != Visibility.Visible)
294             return;
295         super.onDraw(buf);
296         Rect rc = _pos;
297         applyMargins(rc);
298         applyPadding(rc);
299 		auto saver = ClipRectSaver(buf, rc);
300 		for (int i = 0; i < _children.count; i++) {
301 			Widget item = _children.get(i);
302 			if (item.visibility != Visibility.Visible)
303 				continue;
304 			item.onDraw(buf);
305 		}
306     }
307 
308 }
309 
310 class VerticalLayout : LinearLayout {
311     this(string ID = null) {
312         super(ID);
313         orientation = Orientation.Vertical;
314     }
315 }
316 
317 class HorizontalLayout : LinearLayout {
318     this(string ID = null) {
319         super(ID);
320         orientation = Orientation.Horizontal;
321     }
322 }
323 
324 /// place all children into same place (usually, only one child should be visible at a time)
325 class FrameLayout : WidgetGroup {
326     this(string ID) {
327         super(ID);
328     }
329     /// Measure widget according to desired width and height constraints. (Step 1 of two phase layout).
330     override void measure(int parentWidth, int parentHeight) { 
331         Rect m = margins;
332         Rect p = padding;
333         // calc size constraints for children
334         int pwidth = parentWidth;
335         int pheight = parentHeight;
336         if (parentWidth != SIZE_UNSPECIFIED)
337             pwidth -= m.left + m.right + p.left + p.right;
338         if (parentHeight != SIZE_UNSPECIFIED)
339             pheight -= m.top + m.bottom + p.top + p.bottom;
340         // measure children
341         Point sz;
342         for (int i = 0; i < _children.count; i++) {
343             Widget item = _children.get(i);
344             if (item.visibility != Visibility.Gone) {
345                 item.measure(pwidth, pheight);
346                 if (sz.x < item.measuredWidth)
347                     sz.x = item.measuredWidth;
348                 if (sz.y < item.measuredHeight)
349                     sz.y = item.measuredHeight;
350             }
351         }
352         measuredContent(parentWidth, parentHeight, sz.x, sz.y);
353     }
354 
355     /// Set widget rectangle to specified value and layout widget contents. (Step 2 of two phase layout).
356     override void layout(Rect rc) {
357         _needLayout = false;
358         if (visibility == Visibility.Gone) {
359             return;
360         }
361         _pos = rc;
362         applyMargins(rc);
363         applyPadding(rc);
364         for (int i = 0; i < _children.count; i++) {
365             Widget item = _children.get(i);
366             if (item.visibility != Visibility.Gone) {
367                 item.layout(rc);
368             }
369         }
370     }
371 
372     /// Draw widget at its position to buffer
373     override void onDraw(DrawBuf buf) {
374         if (visibility != Visibility.Visible)
375             return;
376         super.onDraw(buf);
377         Rect rc = _pos;
378         applyMargins(rc);
379         applyPadding(rc);
380         auto saver = ClipRectSaver(buf, rc);
381 		for (int i = 0; i < _children.count; i++) {
382 			Widget item = _children.get(i);
383 			if (item.visibility != Visibility.Visible)
384 				continue;
385 			item.onDraw(buf);
386 		}
387     }
388 
389     /// make one of children (with specified ID) visible, for the rest, set visibility to otherChildrenVisibility
390     bool showChild(string ID, Visibility otherChildrenVisibility = Visibility.Invisible, bool updateFocus = false) {
391         bool found = false;
392         Widget foundWidget = null;
393 		for (int i = 0; i < _children.count; i++) {
394 			Widget item = _children.get(i);
395             if (item.compareId(ID)) {
396                 item.visibility = Visibility.Visible;
397                 foundWidget = item;
398                 found = true;
399             } else {
400                 item.visibility = otherChildrenVisibility;
401             }
402 		}
403         if (foundWidget !is null && updateFocus)
404             foundWidget.setFocus();
405         return found;
406     }
407 }