1 ///
2 module nanogui.layout;
3 
4 import std.container.array : Array;
5 import std.algorithm : max;
6 
7 import nanogui.window : Window;
8 import nanogui.common;
9 import nanogui.widget;
10 
11 /*
12 	NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
13 	The widget drawing code is based on the NanoVG demo application
14 	by Mikko Mononen.
15 
16 	All rights reserved. Use of this source code is governed by a
17 	BSD-style license that can be found in the LICENSE.txt file.
18 */
19 /**
20  * A collection of useful layout managers.  The \ref nanogui.GridLayout
21  *        was contributed by Christian Schueller.
22  */
23 
24 /// The different kinds of alignments a layout can perform.
25 enum Alignment : ubyte
26 {
27 	Minimum, /// Take only as much space as is required.
28 	Middle,  /// Center align.
29 	Maximum, /// Take as much space as is allowed.
30 	Fill     /// Fill according to preferred sizes.
31 }
32 
33 /// The direction of data flow for a layout.
34 ///
35 /// Important: the source is heavily based on assumption that 
36 /// only two orientations are possible. It's true in case of 2D
37 /// layout.
38 enum Orientation
39 {
40 	Horizontal, /// Layout expands on horizontal axis.
41 	Vertical,   /// Layout expands on vertical axis.
42 }
43 
44 auto axisIndex(Orientation o)
45 {
46 	int axis_index = o;
47 	return axis_index;
48 }
49 
50 auto nextAxisIndex(Orientation o)
51 {
52 	import std.traits : EnumMembers;
53 	int idx = (o + 1) % EnumMembers!Orientation.length;
54 	return idx;
55 }
56 
57 /**
58  * interface Layout
59  *
60  * Basic interface of a layout engine.
61  */
62 interface Layout
63 {
64 public:
65 	/**
66 	 * Performs any and all resizing applicable.
67 	 *
68 	 * Params:
69 	 *     ctx    = The `NanoVG` context being used for drawing.
70 	 *     widget = The Widget this layout is controlling sizing for.
71 	 */
72 	void performLayout(NanoContext ctx, Widget widget) const;
73 
74 	/**
75 	 * The preferred size for this layout.
76 	 *
77 	 * Params:
78 	 *     ctx    = The `NanoVG` context being used for drawing.
79 	 *     widget = The Widget this layout's preferred size is considering.
80 	 *
81 	 * Returns:
82 	 *     The preferred size, accounting for things such as spacing, padding
83 	 *     for icons, etc.
84 	 */
85 	Vector2i preferredSize(NanoContext ctx, const Widget widget, const Widget skipped = null) const;
86 
87 	/// The margin of this Layout.
88 	int margin() const;
89 
90 	/// Sets the margin of this Layout.
91 	void margin(int);
92 }
93 
94 /**
95  * Simple horizontal/vertical box layout
96  *
97  * This widget stacks up a bunch of widgets horizontally or vertically. It adds
98  * margins around the entire container and a custom spacing between adjacent
99  * widgets.
100  */
101 class BoxLayout : Layout
102 {
103 public:
104 	/**
105 	 * Construct a box layout which packs widgets in the given `Orientation`
106 	 *
107 	 * Params:
108 	 *     orientation = The Orientation this BoxLayout expands along
109 	 *     alignment   = Widget alignment perpendicular to the chosen orientation
110 	 *     margin      = Margin around the layout container
111 	 *     spacing     = Extra spacing placed between widgets
112 	 */
113 	this(Orientation orientation, Alignment alignment = Alignment.Middle,
114 			  int margin = 0, int spacing = 0)
115 	{
116 		mOrientation = orientation;
117 		mAlignment   = alignment;
118 		mMargin      = margin;
119 		mSpacing     = spacing;
120 	}
121 
122 	/// The Orientation this BoxLayout is using.
123 	final Orientation orientation() const { return mOrientation; }
124 
125 	/// Sets the Orientation of this BoxLayout.
126 	final void setOrientation(Orientation orientation) { mOrientation = orientation; }
127 
128 	/// The Alignment of this BoxLayout.
129 	final Alignment alignment() const { return mAlignment; }
130 
131 	/// Sets the Alignment of this BoxLayout.
132 	final void setAlignment(Alignment alignment) { mAlignment = alignment; }
133 
134 	/// The margin of this BoxLayout.
135 	final int margin() const { return mMargin; }
136 
137 	/// Sets the margin of this BoxLayout.
138 	final void margin(int margin) { mMargin = margin; }
139 
140 	/// The spacing this BoxLayout is using to pad in between widgets.
141 	final int spacing() const { return mSpacing; }
142 
143 	/// Sets the spacing of this BoxLayout.
144 	final void setSpacing(int spacing) { mSpacing = spacing; }
145 
146 	/// Implementation of the layout interface
147 	/// See `Layout.preferredSize`.
148 	override Vector2i preferredSize(NanoContext ctx, const Widget widget, const Widget skipped = null) const
149 	{
150 		Vector2i size = Vector2i(2*mMargin, 2*mMargin);
151 
152 		int yOffset = 0;
153 		auto window = cast(Window) widget;
154 		if (window && window.title().length) {
155 			if (mOrientation == Orientation.Vertical)
156 				size[1] += widget.theme.mWindowHeaderHeight - mMargin/2;
157 			else
158 				yOffset = widget.theme.mWindowHeaderHeight;
159 		}
160 
161 		bool first = true;
162 		int axis1 = cast(int) mOrientation;
163 		int axis2 = (cast(int) mOrientation + 1)%2;
164 		foreach (w; widget.children)
165 		{
166 			if (!w.visible || w is skipped)
167 				continue;
168 			if (first)
169 				first = false;
170 			else
171 				size[axis1] += mSpacing;
172 
173 			Vector2i ps = w.preferredSize(ctx);
174 			Vector2i fs = w.fixedSize();
175 			auto targetSize = Vector2i(
176 				fs[0] ? fs[0] : ps[0],
177 				fs[1] ? fs[1] : ps[1]
178 			);
179 
180 			size[axis1] += targetSize[axis1];
181 			size[axis2] = max(size[axis2], targetSize[axis2] + 2*mMargin);
182 			first = false;
183 		}
184 		return size + Vector2i(0, yOffset);
185 	}
186 
187 	/// See `Layout.performLayout`.
188 	override void performLayout(NanoContext ctx, Widget widget) const
189 	{
190 		Vector2i fs_w = widget.fixedSize();
191 		auto containerSize = Vector2i(
192 			fs_w[0] ? fs_w[0] : widget.width,
193 			fs_w[1] ? fs_w[1] : widget.height
194 		);
195 
196 		int axis1 = cast(int) mOrientation;
197 		int axis2 = (cast(int) mOrientation + 1)%2;
198 		int position = mMargin;
199 		int yOffset = 0;
200 
201 		import nanogui.window : Window;
202 		auto window = cast(const Window)(widget);
203 		if (window && window.title.length)
204 		{
205 			if (mOrientation == Orientation.Vertical)
206 			{
207 				position += widget.theme.mWindowHeaderHeight - mMargin/2;
208 			}
209 			else
210 			{
211 				yOffset = widget.theme.mWindowHeaderHeight;
212 				containerSize[1] -= yOffset;
213 			}
214 		}
215 
216 		bool first = true;
217 		foreach(w; widget.children) {
218 			if (!w.visible)
219 				continue;
220 			if (first)
221 				first = false;
222 			else
223 				position += mSpacing;
224 
225 			Vector2i ps = w.preferredSize(ctx), fs = w.fixedSize();
226 			auto targetSize = Vector2i(
227 				fs[0] ? fs[0] : ps[0],
228 				fs[1] ? fs[1] : ps[1]
229 			);
230 			auto pos = Vector2i(0, yOffset);
231 
232 			pos[axis1] = position;
233 
234 			final switch (mAlignment)
235 			{
236 				case Alignment.Minimum:
237 					pos[axis2] += mMargin;
238 					break;
239 				case Alignment.Middle:
240 					pos[axis2] += (containerSize[axis2] - targetSize[axis2]) / 2;
241 					break;
242 				case Alignment.Maximum:
243 					pos[axis2] += containerSize[axis2] - targetSize[axis2] - mMargin * 2;
244 					break;
245 				case Alignment.Fill:
246 					pos[axis2] += mMargin;
247 					targetSize[axis2] = fs[axis2] ? fs[axis2] : (containerSize[axis2] - mMargin * 2);
248 					break;
249 			}
250 
251 			w.position(pos);
252 			w.size(targetSize);
253 			w.performLayout(ctx);
254 			position += targetSize[axis1];
255 		}
256 	}
257 
258 protected:
259 	/// The Orientation of this BoxLayout.
260 	Orientation mOrientation;
261 
262 	/// The Alignment of this BoxLayout.
263 	Alignment mAlignment;
264 
265 	/// The margin of this BoxLayout.
266 	int mMargin;
267 
268 	/// The spacing between widgets of this BoxLayout.
269 	int mSpacing;
270 }
271 
272 /**
273  * Special layout for widgets grouped by labels.
274  *
275  * This widget resembles a box layout in that it arranges a set of widgets
276  * vertically. All widgets are indented on the horizontal axis except for
277  * `Label` widgets, which are not indented.
278  *
279  * This creates a pleasing layout where a number of widgets are grouped
280  * under some high-level heading.
281  */
282 class GroupLayout : Layout
283 {
284 public:
285 	/**
286 	 * Creates a GroupLayout.
287 	 *
288 	 * Params:
289 	 *     margin       = The margin around the widgets added.
290 	 *     spacing      = The spacing between widgets added.
291 	 *     groupSpacing = The spacing between groups (groups are defined by each Label added).
292 	 *     groupIndent  = The amount to indent widgets in a group (underneath a Label).
293 	 */
294 	this(int margin = 15, int spacing = 6, int groupSpacing = 14,
295 				int groupIndent = 20)
296 	{
297 		mMargin       = margin;
298 		mSpacing      = spacing;
299 		mGroupSpacing = groupSpacing;
300 		mGroupIndent  = groupIndent;
301 	}
302 
303 	/// The margin of this GroupLayout.
304 	final int margin() const { return mMargin; }
305 
306 	/// Sets the margin of this GroupLayout.
307 	final void margin(int margin) { mMargin = margin; }
308 
309 	/// The spacing between widgets of this GroupLayout.
310 	final int spacing() const { return mSpacing; }
311 
312 	/// Sets the spacing between widgets of this GroupLayout.
313 	final void spacing(int spacing) { mSpacing = spacing; }
314 
315 	/// The indent of widgets in a group (underneath a Label) of this GroupLayout.
316 	final int groupIndent() const { return mGroupIndent; }
317 
318 	/// Sets the indent of widgets in a group (underneath a Label) of this GroupLayout.
319 	final void groupIndent(int groupIndent) { mGroupIndent = groupIndent; }
320 
321 	/// The spacing between groups of this GroupLayout.
322 	final int groupSpacing() const { return mGroupSpacing; }
323 
324 	/// Sets the spacing between groups of this GroupLayout.
325 	final void groupSpacing(int groupSpacing) { mGroupSpacing = groupSpacing; }
326 
327 	/// Implementation of the layout interface
328 	/// See `Layout.preferredSize`.
329 	override Vector2i preferredSize(NanoContext ctx, const Widget widget, const Widget skipped = null) const
330 	{
331 		int height = mMargin, width = 2*mMargin;
332 
333 		import nanogui.window : Window;
334 		auto window = cast(const Window) widget;
335 		if (window && window.title.length)
336 			height += widget.theme.mWindowHeaderHeight - mMargin/2;
337 
338 		bool first = true, indent = false;
339 		foreach (c; widget.children) {
340 			if (!c.visible || c is skipped)
341 				continue;
342 			import nanogui.label : Label;
343 			auto label = cast(const Label) c;
344 			if (!first)
345 				height += (label is null) ? mSpacing : mGroupSpacing;
346 			first = false;
347 
348 			Vector2i ps = c.preferredSize(ctx), fs = c.fixedSize();
349 			auto targetSize = Vector2i(
350 				fs[0] ? fs[0] : ps[0],
351 				fs[1] ? fs[1] : ps[1]
352 			);
353 
354 			bool indentCur = indent && label is null;
355 			height += targetSize.y;
356 
357 			width = max(width, targetSize.x + 2*mMargin + (indentCur ? mGroupIndent : 0));
358 
359 			if (label)
360 				indent = label.caption().length != 0;
361 		}
362 		height += mMargin;
363 		return Vector2i(width, height);
364 	}
365 
366 	/// See `Layout.performLayout`.
367 	override void performLayout(NanoContext ctx, Widget widget) const
368 	{
369 		int height = mMargin, availableWidth =
370 			(widget.fixedWidth() ? widget.fixedWidth() : widget.width()) - 2*mMargin;
371 
372 		const Window window = cast(const Window) widget;
373 		if (window && window.title.length)
374 			height += widget.theme.mWindowHeaderHeight - mMargin/2;
375 
376 		bool first = true, indent = false;
377 		foreach (c; widget.children) {
378 			if (!c.visible)
379 				continue;
380 			import nanogui.label : Label;
381 			const Label label = cast(const Label) c;
382 			if (!first)
383 				height += (label is null) ? mSpacing : mGroupSpacing;
384 			first = false;
385 
386 			bool indentCur = indent && label is null;
387 			Vector2i ps = Vector2i(availableWidth - (indentCur ? mGroupIndent : 0),
388 								   c.preferredSize(ctx).y);
389 			Vector2i fs = c.fixedSize();
390 
391 			auto targetSize = Vector2i(
392 				fs[0] ? fs[0] : ps[0],
393 				fs[1] ? fs[1] : ps[1]
394 			);
395 
396 			c.position = Vector2i(mMargin + (indentCur ? mGroupIndent : 0), height);
397 			c.size = targetSize;
398 			c.performLayout(ctx);
399 
400 			height += targetSize.y;
401 
402 			if (label)
403 				indent = label.caption != "";
404 		}
405 	}
406 
407 protected:
408 	/// The margin of this GroupLayout.
409 	int mMargin;
410 
411 	/// The spacing between widgets of this GroupLayout.
412 	int mSpacing;
413 
414 	/// The spacing between groups of this GroupLayout.
415 	int mGroupSpacing;
416 
417 	/// The indent amount of a group under its defining Label of this GroupLayout.
418 	int mGroupIndent;
419 }
420 
421 /**
422  * Grid layout.
423  *
424  * Widgets are arranged in a grid that has a fixed grid resolution `resolution`
425  * along one of the axes. The layout orientation indicates the fixed dimension;
426  * widgets are also appended on this axis. The spacing between items can be
427  * specified per axis. The horizontal/vertical alignment can be specified per
428  * row and column.
429  */
430 class GridLayout : Layout
431 {
432 public:
433 	/**
434 	 * Create a 2-column grid layout by default.
435 	 *
436 	 * Params:
437 	 *     orientation = The fixed dimension of this GridLayout.
438 	 *     resolution  = The number of rows or columns in the grid (depending on the Orientation).
439 	 *     alignment   = How widgets should be aligned within each grid cell.
440 	 *     margin      = The amount of spacing to add around the border of the grid.
441 	 *     spacing     = The amount of spacing between widgets added to the grid.
442 	 */
443 	this(Orientation orientation = Orientation.Horizontal, int resolution = 2,
444 			   Alignment alignment = Alignment.Middle,
445 			   int margin = 0, int spacing = 0)
446 	{
447 		mOrientation = orientation;
448 		mResolution  = resolution;
449 		mMargin      = margin < 0 ? 0 : margin;
450 		mSpacing     = Vector2i(spacing < 0 ? 0 : spacing);
451 
452 		mDefaultAlignment[0] = mDefaultAlignment[1] = alignment;
453 	}
454 
455 	/// The Orientation of this GridLayout.
456 	final Orientation orientation() const { return mOrientation; }
457 
458 	/// Sets the Orientation of this GridLayout.
459 	final void orientation(Orientation orientation) {
460 		mOrientation = orientation;
461 	}
462 
463 	/// The number of rows or columns (depending on the Orientation) of this GridLayout.
464 	final int resolution() const { return mResolution; }
465 
466 	/// Sets the number of rows or columns (depending on the Orientation) of this GridLayout.
467 	final void resolution(int resolution) { mResolution = resolution; }
468 
469 	/// The spacing at the specified axis (row or column number, depending on the Orientation).
470 	final int spacing(int axis) const { return mSpacing[axis]; }
471 
472 	/// Sets the spacing for a specific axis.
473 	final void spacing(int axis, int spacing)
474 	{
475 		if (spacing < 0)
476 			return;
477 		mSpacing[axis] = spacing;
478 	}
479 
480 	/// Sets the spacing for all axes.
481 	final void spacing(int spacing)
482 	{
483 		if (spacing < 0)
484 			return;
485 		mSpacing[0] = mSpacing[1] = spacing;
486 	}
487 
488 	/// The margin around this GridLayout.
489 	final int margin() const { return mMargin; }
490 
491 	/// Sets the margin of this GridLayout.
492 	final void margin(int margin)
493 	{
494 		if (margin < 0)
495 			return;
496 		mMargin = margin;
497 	}
498 
499 	/**
500 	 * The Alignment of the specified axis (row or column number, depending on
501 	 * the Orientation) at the specified index of that row or column.
502 	 */
503 	final Alignment alignment(int axis, int item) const
504 	{
505 		if (item < cast(int) mAlignment[axis].length)
506 			return mAlignment[axis][item];
507 		else
508 			return mDefaultAlignment[axis];
509 	}
510 
511 	/// Sets the Alignment of the columns.
512 	final void colAlignment(Alignment value) { mDefaultAlignment[0] = value; }
513 
514 	/// Sets the Alignment of the rows.
515 	final void rowAlignment(Alignment value) { mDefaultAlignment[1] = value; }
516 
517 	/// Use this to set variable Alignment for columns.
518 	final void colAlignment(Array!Alignment value) { mAlignment[0] = value; }
519 
520 	/// Use this to set variable Alignment for rows.
521 	final void rowAlignment(Array!Alignment value) { mAlignment[1] = value; }
522 
523 	/// Implementation of the layout interface
524 	/// See `Layout.preferredSize`.
525 	override Vector2i preferredSize(NanoContext ctx, const Widget widget, const Widget skipped = null) const
526 	{
527 		/* Compute minimum row / column sizes */
528 		import std.algorithm : sum;
529 		Array!(int)[2] grid;
530 		computeLayout(ctx, widget, grid, skipped);
531 
532 		auto size = Vector2i(
533 			2*mMargin + sum(grid[0][])
534 			 + max(cast(int) grid[0].length - 1, 0) * mSpacing[0],
535 			2*mMargin + sum(grid[1][])
536 			 + max(cast(int) grid[1].length - 1, 0) * mSpacing[1]
537 		);
538 
539 		const Window window = cast(const Window) widget;
540 		if (window && window.title.length)
541 			size[1] += widget.theme.mWindowHeaderHeight - mMargin/2;
542 
543 		return size;
544 	}
545 
546 	/// See `Layout.performLayout`.
547 	override void performLayout(NanoContext ctx, Widget widget) const
548 	{
549 		Vector2i fs_w = widget.fixedSize;
550 		auto containerSize = Vector2i(
551 			fs_w[0] ? fs_w[0] : widget.width,
552 			fs_w[1] ? fs_w[1] : widget.height
553 		);
554 
555 		/* Compute minimum row / column sizes */
556 		Array!(int)[2] grid;
557 		computeLayout(ctx, widget, grid, null);
558 		if (grid[1].length == 0)
559 			return;
560 		int[2] dim = [ cast(int) grid[0].length, cast(int) grid[1].length ];
561 
562 		Vector2i extra;
563 		const Window window = cast(const Window) widget;
564 		if (window && window.title.length)
565 			extra[1] += widget.theme.mWindowHeaderHeight - mMargin / 2;
566 
567 		/* Strech to size provided by \c widget */
568 		foreach (int i; 0..2) // iterate over axes
569 		{
570 			// set margin + header if any
571 			int gridSize = 2 * mMargin + extra[i];
572 			// add widgets size
573 			foreach(s; grid[i])
574 				gridSize += s;
575 			// add spacing between widgets
576 			gridSize += mSpacing[i] * (grid[i].length - 1);
577 
578 			if (gridSize < containerSize[i]) {
579 				/* Re-distribute remaining space evenly */
580 				int gap = containerSize[i] - gridSize;
581 				int g = gap / dim[i];
582 				int rest = gap - g * dim[i];
583 				for (int j = 0; j < dim[i]; ++j)
584 					grid[i][j] += g;
585 				assert(rest < dim[i]);
586 				for (int j = 0; rest > 0 && j < dim[i]; --rest, ++j)
587 					grid[i][j] += 1;
588 			}
589 		}
590 
591 		int axis1 = cast(int) mOrientation, axis2 = (axis1 + 1) % 2;
592 		Vector2i start = Vector2i(mMargin, mMargin) + extra;
593 
594 		size_t numChildren = widget.children.length;
595 		size_t child = 0;
596 
597 		Vector2i pos = start;
598 		for (int i2 = 0; i2 < dim[axis2]; i2++) {
599 			pos[axis1] = start[axis1];
600 			for (int i1 = 0; i1 < dim[axis1]; i1++) {
601 				Widget w;
602 				do {
603 					if (child >= numChildren)
604 						return;
605 					w = widget.children()[child++];
606 				} while (!w.visible());
607 
608 				Vector2i ps = w.preferredSize(ctx);
609 				Vector2i fs = w.fixedSize();
610 				auto targetSize = Vector2i(
611 					fs[0] ? fs[0] : ps[0],
612 					fs[1] ? fs[1] : ps[1]
613 				);
614 
615 				auto itemPos = Vector2i(pos);
616 				for (int j = 0; j < 2; j++) {
617 					int axis = (axis1 + j) % 2;
618 					int item = j == 0 ? i1 : i2;
619 					Alignment algn = alignment(axis, item);
620 
621 					final switch (algn) {
622 						case Alignment.Minimum:
623 							break;
624 						case Alignment.Middle:
625 							itemPos[axis] += (grid[axis][item] - targetSize[axis]) / 2;
626 							break;
627 						case Alignment.Maximum:
628 							itemPos[axis] += grid[axis][item] - targetSize[axis];
629 							break;
630 						case Alignment.Fill:
631 							targetSize[axis] = fs[axis] ? fs[axis] : grid[axis][item];
632 							break;
633 					}
634 				}
635 				w.position(itemPos);
636 				w.size(targetSize);
637 				w.performLayout(ctx);
638 				pos[axis1] += grid[axis1][i1] + mSpacing[axis1];
639 			}
640 			pos[axis2] += grid[axis2][i2] + mSpacing[axis2];
641 		}
642 	}
643 
644 protected:
645 	/// Compute the maximum row and column sizes
646 	void computeLayout(NanoContext ctx, const Widget widget,
647 					   ref Array!(int)[2] grid, const Widget skipped) const
648 	{
649 		int axis1 = cast(int)  mOrientation;
650 		int axis2 = cast(int) !mOrientation;
651 		size_t numChildren = widget.children.length, visibleChildren = 0;
652 		foreach (w; widget.children)
653 			visibleChildren += w.visible ? 1 : 0;
654 
655 		Vector2i dim;
656 		// count of items in main axis
657 		dim[axis1] = mResolution;
658 		// count of items in secondary axis
659 		dim[axis2] = cast(int) ((visibleChildren + mResolution - 1) / mResolution);
660 
661 		grid[axis1].clear(); grid[axis1].length = dim[axis1]; grid[axis1][] = 0;
662 		grid[axis2].clear(); grid[axis2].length = dim[axis2]; grid[axis2][] = 0;
663 
664 		size_t child;
665 		foreach(int i2; 0..dim[axis2])
666 		{
667 			foreach(int i1; 0..dim[axis1])
668 			{
669 				import std.typecons : Rebindable;
670 				Rebindable!(const Widget) w;
671 				do {
672 					if (child >= numChildren)
673 						return;
674 					w = widget.children[child++];
675 				} while (!w.visible || w is skipped);
676 
677 				Vector2i ps = w.preferredSize(ctx);
678 				Vector2i fs = w.fixedSize();
679 				auto targetSize = Vector2i(
680 					fs[0] ? fs[0] : ps[0],
681 					fs[1] ? fs[1] : ps[1]
682 				);
683 
684 				grid[axis1][i1] = max(grid[axis1][i1], targetSize[axis1]);
685 				grid[axis2][i2] = max(grid[axis2][i2], targetSize[axis2]);
686 			}
687 		}
688 	}
689 
690 	/// The Orientation defining this GridLayout.
691 	Orientation mOrientation;
692 
693 	/// The default Alignment for this GridLayout.
694 	Alignment[2] mDefaultAlignment;
695 
696 	/// The actual Alignment being used.
697 	Array!(Alignment)[2] mAlignment;
698 
699 	/// The number of rows or columns before starting a new one, depending on the Orientation.
700 	int mResolution;
701 
702 	/// The spacing used for each dimension.
703 	Vector2i mSpacing;
704 
705 	/// The margin around this GridLayout.
706 	int mMargin;
707 }
708 
709 /**
710  * The is a fancier grid layout with support for items that span multiple rows
711  * or columns, and per-widget alignment flags. Each row and column additionally
712  * stores a stretch factor that controls how additional space is redistributed.
713  * The downside of this flexibility is that a layout anchor data structure must
714  * be provided for each widget.
715  *
716  * An example:
717  *
718  *    Label label = new Label(window, "A label");
719  *    // Add a centered label at grid position (1, 5), which spans two horizontal cells
720  *    layout.setAnchor(label, AdvancedGridLayout.Anchor(1, 5, 2, 1, Alignment.Middle, Alignment.Middle));
721  *
722  * The grid is initialized with user-specified column and row size vectors
723  * (which can be expanded later on if desired). If a size value of zero is
724  * specified for a column or row, the size is set to the maximum preferred size
725  * of any widgets contained in the same row or column. Any remaining space is
726  * redistributed according to the row and column stretch factors.
727  *
728  * The high level usage somewhat resembles the classic HIG layout:
729  *
730  * - https://web.archive.org/web/20070813221705/http://www.autel.cz/dmi/tutorial.html
731  * - https://github.com/jaapgeurts/higlayout
732  */
733 class AdvancedGridLayout : Layout
734 {
735 	/**
736 	 *Helper struct to coordinate anchor points for the layout.
737 	 */
738 	struct Anchor
739 	{
740 		ubyte[2] pos;	 ///< The ``(x, y)`` position.
741 		ubyte[2] size;	 ///< The ``(x, y)`` size.
742 		Alignment[2] algn;///< The ``(x, y)`` Alignment.
743 
744 		/// Creates a ``0`` Anchor.
745 		//this() { }
746 
747 		/// Create an Anchor at position ``(x, y)`` with specified Alignment.
748 		this(int x, int y, Alignment horiz = Alignment.Fill,
749 			 Alignment vert = Alignment.Fill)
750 		{
751 			pos[0] = cast(ubyte) x; pos[1] = cast(ubyte) y;
752 			size[0] = size[1] = 1;
753 			algn[0] = horiz; algn[1] = vert;
754 		}
755 
756 		/// Create an Anchor at position ``(x, y)`` of size ``(w, h)`` with specified alignments.
757 		this(int x, int y, int w, int h,
758 			 Alignment horiz = Alignment.Fill,
759 			 Alignment vert = Alignment.Fill)
760 		{
761 			pos[0] = cast(ubyte) x; pos[1] = cast(ubyte) y;
762 			size[0] = cast(ubyte) w; size[1] = cast(ubyte) h;
763 			algn[0] = horiz; algn[1] = vert;
764 		}
765 
766 		/// Allows for printing out Anchor position, size, and alignment.
767 		string toString() const
768 		{
769 			import std.string : format;
770 			return format("pos=(%d, %d), size=(%d, %d), align=(%d, %d)",
771 						  pos[0], pos[1], size[0], size[1], cast(int) algn[0], cast(int) algn[1]);
772 		}
773 	}
774 
775 	/// Creates an AdvancedGridLayout with specified columns, rows, and margin.
776 	this(int[] cols, int[] rows, int margin = 0)
777 	{
778 		mCols = Array!int(cols);
779 		mRows = Array!int(rows);
780 		mMargin = margin;
781 		mColStretch.length = mCols.length; mColStretch[] = 0;
782 		mRowStretch.length = mRows.length; mRowStretch[] = 0;
783 	}
784 
785 	/// The margin of this AdvancedGridLayout.
786 	final int margin() const { return mMargin; }
787 
788 	/// Sets the margin of this AdvancedGridLayout.
789 	final void margin(int margin) { mMargin = margin; }
790 
791 	/// Return the number of cols
792 	final int colCount() const { return cast(int) mCols.length; }
793 
794 	/// Return the number of rows
795 	final int rowCount() const { return cast(int) mRows.length; }
796 
797 	/// Append a row of the given size (and stretch factor)
798 	final void appendRow(int size, float stretch = 0f) { mRows.insertBack(size); mRowStretch.insertBack(stretch); }
799 
800 	/// Append a column of the given size (and stretch factor)
801 	final void appendCol(int size, float stretch = 0f) { mCols.insertBack(size); mColStretch.insertBack(stretch); }
802 
803 	/// Set the stretch factor of a given row
804 	final void setRowStretch(int index, float stretch) { mRowStretch[index] = stretch; }
805 
806 	/// Set the stretch factor of a given column
807 	final void setColStretch(int index, float stretch) { mColStretch[index] = stretch; }
808 
809 	/// Specify the anchor data structure for a given widget
810 	final void setAnchor(const Widget widget, const Anchor anchor) { mAnchor[widget] = anchor; }
811 
812 	/// Retrieve the anchor data structure for a given widget
813 	Anchor anchor(const Widget widget) const
814 	{
815 		auto it = widget in mAnchor;
816 		if (it is null)
817 			throw new Exception("Widget was not registered with the grid layout!");
818 
819 		return cast(Anchor) *it;
820 	}
821 
822 	/* Implementation of the layout interface */
823 	Vector2i preferredSize(NanoContext ctx, const Widget widget, const Widget skipped = null) const
824 	{
825 		/* Compute minimum row / column sizes */
826 		Array!int[2] grid;
827 		computeLayout(ctx, widget, grid);
828 
829 		import std.algorithm : sum;
830 
831 		Vector2i size = Vector2i(sum(grid[0][]),
832 								 sum(grid[1][]));
833 		Vector2i extra = Vector2i(2 * mMargin, 2 * mMargin);
834 		auto window = cast(const Window) widget;
835 		if (window && window.title.length)
836 			extra[1] += widget.theme().mWindowHeaderHeight - mMargin/2;
837 
838 		return size+extra;
839 	}
840 
841 	void performLayout(NanoContext ctx, Widget widget) const
842 	{
843 		Array!int[2] grid;
844 		computeLayout(ctx, widget, grid);
845 		grid[0].insertBefore(grid[0][0..$], mMargin);
846 		auto window = cast(const Window) widget;
847 		if (window && window.title.length)
848 			grid[1].insertBefore(grid[1][0..$], widget.theme.mWindowHeaderHeight + mMargin/2);
849 		else
850 			grid[1].insertBefore(grid[1][0..$], mMargin);
851 
852 		for (int axis=0; axis<2; ++axis) {
853 			for (size_t i=1; i<grid[axis].length; ++i)
854 				grid[axis][i] += grid[axis][i-1];
855 
856 			foreach (w; widget.children()) {
857 				if (!w.visible())
858 					continue;
859 				Anchor anchor = this.anchor(w);
860 
861 				int itemPos = grid[axis][anchor.pos[axis]];
862 				int cellSize  = grid[axis][anchor.pos[axis] + anchor.size[axis]] - itemPos;
863 				int ps = w.preferredSize(ctx)[axis], fs = w.fixedSize()[axis];
864 				int targetSize = fs ? fs : ps;
865 
866 				final switch (anchor.algn[axis]) {
867 				case Alignment.Minimum:
868 					break;
869 				case Alignment.Middle:
870 					itemPos += (cellSize - targetSize) / 2;
871 					break;
872 				case Alignment.Maximum:
873 					itemPos += cellSize - targetSize;
874 					break;
875 				case Alignment.Fill:
876 					targetSize = fs ? fs : cellSize;
877 					break;
878 				}
879 
880 				Vector2i pos = w.position(), size = w.size();
881 				pos[axis] = itemPos;
882 				size[axis] = targetSize;
883 				w.position = pos;
884 				w.size = size;
885 				w.performLayout(ctx);
886 			}
887 		}
888 	}
889 
890 protected:
891 	/// Computes the layout
892 	void computeLayout(NanoContext ctx, const Widget widget,
893 					   ref Array!(int)[2] _grid) const
894 	{
895 		Vector2i fs_w = widget.fixedSize();
896 		Vector2i containerSize = Vector2i(fs_w[0] ? fs_w[0] : widget.width(), fs_w[1] ? fs_w[1] : widget.height());
897 		Vector2i extra = Vector2i(2 * mMargin, 2 * mMargin);
898 		auto window = cast(const Window) widget;
899 		if (window && window.title.length)
900 			extra[1] += widget.theme().mWindowHeaderHeight - mMargin/2;
901 
902 		containerSize -= extra;
903 
904 		for (int axis=0; axis<2; ++axis) {
905 			const sizes = axis == 0 ? mCols : mRows;
906 			const stretch = axis == 0 ? mColStretch : mRowStretch;
907 
908 			_grid[axis].clear;
909 			_grid[axis].insertBack(sizes[]);
910 
911 			auto grid = _grid[axis];
912 
913 			for (int phase = 0; phase < 2; ++phase) {
914 				foreach (const Widget w, const Anchor anchor; mAnchor) {
915 					if (!w.visible())
916 						continue;
917 					if ((anchor.size[axis] == 1) != (phase == 0))
918 						continue;
919 					int ps = w.preferredSize(ctx)[axis], fs = w.fixedSize()[axis];
920 					int targetSize = fs ? fs : ps;
921 
922 					if (anchor.pos[axis] + anchor.size[axis] > cast(int) grid.length)
923 						throw new Exception("Advanced grid layout: widget is out of bounds: " ~ anchor.toString);
924 
925 					int currentSize = 0;
926 					float totalStretch = 0;
927 
928 					import std.algorithm : max;
929 
930 					for (int i = anchor.pos[axis];
931 						 i < anchor.pos[axis] + anchor.size[axis]; ++i) {
932 						if (sizes[i] == 0 && anchor.size[axis] == 1)
933 							grid[i] = max(grid[i], targetSize);
934 						currentSize += grid[i];
935 						totalStretch += stretch[i];
936 					}
937 					if (targetSize <= currentSize)
938 						continue;
939 					if (totalStretch == 0)
940 						throw new Exception("Advanced grid layout: no space to place widget: ", anchor.toString);
941 					import std.math : round;
942 
943 					float amt = (targetSize - currentSize) / totalStretch;
944 					for (int i = anchor.pos[axis];
945 						 i < anchor.pos[axis] + anchor.size[axis]; ++i) {
946 						grid[i] += cast(int) round(amt * stretch[i]);
947 					}
948 				}
949 			}
950 
951 			import std.algorithm : sum;
952 			int currentSize = sum(grid[]);
953 			float totalStretch;
954 			foreach(e; stretch[])
955 				totalStretch += e;
956 			if (currentSize >= containerSize[axis] || totalStretch == 0)
957 				continue;
958 			float amt = (containerSize[axis] - currentSize) / totalStretch;
959 			import std.math : round;
960 			for (auto i = 0; i<grid.length; ++i)
961 				grid[i] += cast(int) round(amt * stretch[i]);
962 		}
963 	}
964 
965 protected:
966 	/// The columns of this AdvancedGridLayout.
967 	Array!int mCols;
968 
969 	/// The rows of this AdvancedGridLayout.
970 	Array!int mRows;
971 
972 	/// The stretch for each column of this AdvancedGridLayout.
973 	Array!float mColStretch;
974 
975 	/// The stretch for each row of this AdvancedGridLayout.
976 	Array!float mRowStretch;
977 
978 	/// The mapping of widgets to their specified anchor points.
979 	Anchor[const Widget] mAnchor;
980 
981 	/// The margin around this AdvancedGridLayout.
982 	int mMargin;
983 }