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;
370 		int availableWidth = (widget.fixedWidth() ? widget.fixedWidth() : widget.width()) - 2*mMargin;
371 		if (availableWidth < 0) availableWidth = 0;
372 
373 		const Window window = cast(const Window) widget;
374 		if (window && window.title.length)
375 			height += widget.theme.mWindowHeaderHeight - mMargin/2;
376 
377 		bool first = true, indent = false;
378 		foreach (c; widget.children) {
379 			if (!c.visible)
380 				continue;
381 			import nanogui.label : Label;
382 			const Label label = cast(const Label) c;
383 			if (!first)
384 				height += (label is null) ? mSpacing : mGroupSpacing;
385 			first = false;
386 
387 			bool indentCur = indent && label is null;
388 			Vector2i ps = Vector2i(availableWidth - (indentCur ? mGroupIndent : 0),
389 								   c.preferredSize(ctx).y);
390 			Vector2i fs = c.fixedSize();
391 
392 			auto targetSize = Vector2i(
393 				fs[0] ? fs[0] : ps[0],
394 				fs[1] ? fs[1] : ps[1]
395 			);
396 
397 			c.position = Vector2i(mMargin + (indentCur ? mGroupIndent : 0), height);
398 			c.size = targetSize;
399 			c.performLayout(ctx);
400 
401 			height += targetSize.y;
402 
403 			if (label)
404 				indent = label.caption != "";
405 		}
406 	}
407 
408 protected:
409 	/// The margin of this GroupLayout.
410 	int mMargin;
411 
412 	/// The spacing between widgets of this GroupLayout.
413 	int mSpacing;
414 
415 	/// The spacing between groups of this GroupLayout.
416 	int mGroupSpacing;
417 
418 	/// The indent amount of a group under its defining Label of this GroupLayout.
419 	int mGroupIndent;
420 }
421 
422 /**
423  * Grid layout.
424  *
425  * Widgets are arranged in a grid that has a fixed grid resolution `resolution`
426  * along one of the axes. The layout orientation indicates the fixed dimension;
427  * widgets are also appended on this axis. The spacing between items can be
428  * specified per axis. The horizontal/vertical alignment can be specified per
429  * row and column.
430  */
431 class GridLayout : Layout
432 {
433 public:
434 	/**
435 	 * Create a 2-column grid layout by default.
436 	 *
437 	 * Params:
438 	 *     orientation = The fixed dimension of this GridLayout.
439 	 *     resolution  = The number of rows or columns in the grid (depending on the Orientation).
440 	 *     alignment   = How widgets should be aligned within each grid cell.
441 	 *     margin      = The amount of spacing to add around the border of the grid.
442 	 *     spacing     = The amount of spacing between widgets added to the grid.
443 	 */
444 	this(Orientation orientation = Orientation.Horizontal, int resolution = 2,
445 			   Alignment alignment = Alignment.Middle,
446 			   int margin = 0, int spacing = 0)
447 	{
448 		mOrientation = orientation;
449 		mResolution  = resolution;
450 		mMargin      = margin < 0 ? 0 : margin;
451 		mSpacing     = Vector2i(spacing < 0 ? 0 : spacing);
452 
453 		mDefaultAlignment[0] = mDefaultAlignment[1] = alignment;
454 	}
455 
456 	/// The Orientation of this GridLayout.
457 	final Orientation orientation() const { return mOrientation; }
458 
459 	/// Sets the Orientation of this GridLayout.
460 	final void orientation(Orientation orientation) {
461 		mOrientation = orientation;
462 	}
463 
464 	/// The number of rows or columns (depending on the Orientation) of this GridLayout.
465 	final int resolution() const { return mResolution; }
466 
467 	/// Sets the number of rows or columns (depending on the Orientation) of this GridLayout.
468 	final void resolution(int resolution) { mResolution = resolution; }
469 
470 	/// The spacing at the specified axis (row or column number, depending on the Orientation).
471 	final int spacing(int axis) const { return mSpacing[axis]; }
472 
473 	/// Sets the spacing for a specific axis.
474 	final void spacing(int axis, int spacing)
475 	{
476 		if (spacing < 0)
477 			return;
478 		mSpacing[axis] = spacing;
479 	}
480 
481 	/// Sets the spacing for all axes.
482 	final void spacing(int spacing)
483 	{
484 		if (spacing < 0)
485 			return;
486 		mSpacing[0] = mSpacing[1] = spacing;
487 	}
488 
489 	/// The margin around this GridLayout.
490 	final int margin() const { return mMargin; }
491 
492 	/// Sets the margin of this GridLayout.
493 	final void margin(int margin)
494 	{
495 		if (margin < 0)
496 			return;
497 		mMargin = margin;
498 	}
499 
500 	/**
501 	 * The Alignment of the specified axis (row or column number, depending on
502 	 * the Orientation) at the specified index of that row or column.
503 	 */
504 	final Alignment alignment(int axis, int item) const
505 	{
506 		if (item < cast(int) mAlignment[axis].length)
507 			return mAlignment[axis][item];
508 		else
509 			return mDefaultAlignment[axis];
510 	}
511 
512 	/// Sets the Alignment of the columns.
513 	final void colAlignment(Alignment value) { mDefaultAlignment[0] = value; }
514 
515 	/// Sets the Alignment of the rows.
516 	final void rowAlignment(Alignment value) { mDefaultAlignment[1] = value; }
517 
518 	/// Use this to set variable Alignment for columns.
519 	final void colAlignment(Array!Alignment value) { mAlignment[0] = value; }
520 
521 	/// Use this to set variable Alignment for rows.
522 	final void rowAlignment(Array!Alignment value) { mAlignment[1] = value; }
523 
524 	/// Implementation of the layout interface
525 	/// See `Layout.preferredSize`.
526 	override Vector2i preferredSize(NanoContext ctx, const Widget widget, const Widget skipped = null) const
527 	{
528 		/* Compute minimum row / column sizes */
529 		import std.algorithm : sum;
530 		Array!(int)[2] grid;
531 		computeLayout(ctx, widget, grid, skipped);
532 
533 		auto size = Vector2i(
534 			2*mMargin + sum(grid[0][])
535 			 + max(cast(int) grid[0].length - 1, 0) * mSpacing[0],
536 			2*mMargin + sum(grid[1][])
537 			 + max(cast(int) grid[1].length - 1, 0) * mSpacing[1]
538 		);
539 
540 		const Window window = cast(const Window) widget;
541 		if (window && window.title.length)
542 			size[1] += widget.theme.mWindowHeaderHeight - mMargin/2;
543 
544 		return size;
545 	}
546 
547 	/// See `Layout.performLayout`.
548 	override void performLayout(NanoContext ctx, Widget widget) const
549 	{
550 		Vector2i fs_w = widget.fixedSize;
551 		auto containerSize = Vector2i(
552 			fs_w[0] ? fs_w[0] : widget.width,
553 			fs_w[1] ? fs_w[1] : widget.height
554 		);
555 
556 		/* Compute minimum row / column sizes */
557 		Array!(int)[2] grid;
558 		computeLayout(ctx, widget, grid, null);
559 		if (grid[1].length == 0)
560 			return;
561 		int[2] dim = [ cast(int) grid[0].length, cast(int) grid[1].length ];
562 
563 		Vector2i extra;
564 		const Window window = cast(const Window) widget;
565 		if (window && window.title.length)
566 			extra[1] += widget.theme.mWindowHeaderHeight - mMargin / 2;
567 
568 		/* Strech to size provided by \c widget */
569 		foreach (int i; 0..2) // iterate over axes
570 		{
571 			// set margin + header if any
572 			int gridSize = 2 * mMargin + extra[i];
573 			// add widgets size
574 			foreach(s; grid[i])
575 				gridSize += s;
576 			// add spacing between widgets
577 			gridSize += mSpacing[i] * (grid[i].length - 1);
578 
579 			if (gridSize < containerSize[i]) {
580 				/* Re-distribute remaining space evenly */
581 				int gap = containerSize[i] - gridSize;
582 				int g = gap / dim[i];
583 				int rest = gap - g * dim[i];
584 				for (int j = 0; j < dim[i]; ++j)
585 					grid[i][j] += g;
586 				assert(rest < dim[i]);
587 				for (int j = 0; rest > 0 && j < dim[i]; --rest, ++j)
588 					grid[i][j] += 1;
589 			}
590 		}
591 
592 		int axis1 = cast(int) mOrientation, axis2 = (axis1 + 1) % 2;
593 		Vector2i start = Vector2i(mMargin, mMargin) + extra;
594 
595 		size_t numChildren = widget.children.length;
596 		size_t child = 0;
597 
598 		Vector2i pos = start;
599 		for (int i2 = 0; i2 < dim[axis2]; i2++) {
600 			pos[axis1] = start[axis1];
601 			for (int i1 = 0; i1 < dim[axis1]; i1++) {
602 				Widget w;
603 				do {
604 					if (child >= numChildren)
605 						return;
606 					w = widget.children()[child++];
607 				} while (!w.visible());
608 
609 				Vector2i ps = w.preferredSize(ctx);
610 				Vector2i fs = w.fixedSize();
611 				auto targetSize = Vector2i(
612 					fs[0] ? fs[0] : ps[0],
613 					fs[1] ? fs[1] : ps[1]
614 				);
615 
616 				auto itemPos = Vector2i(pos);
617 				for (int j = 0; j < 2; j++) {
618 					int axis = (axis1 + j) % 2;
619 					int item = j == 0 ? i1 : i2;
620 					Alignment algn = alignment(axis, item);
621 
622 					final switch (algn) {
623 						case Alignment.Minimum:
624 							break;
625 						case Alignment.Middle:
626 							itemPos[axis] += (grid[axis][item] - targetSize[axis]) / 2;
627 							break;
628 						case Alignment.Maximum:
629 							itemPos[axis] += grid[axis][item] - targetSize[axis];
630 							break;
631 						case Alignment.Fill:
632 							targetSize[axis] = fs[axis] ? fs[axis] : grid[axis][item];
633 							break;
634 					}
635 				}
636 				w.position(itemPos);
637 				w.size(targetSize);
638 				w.performLayout(ctx);
639 				pos[axis1] += grid[axis1][i1] + mSpacing[axis1];
640 			}
641 			pos[axis2] += grid[axis2][i2] + mSpacing[axis2];
642 		}
643 	}
644 
645 protected:
646 	/// Compute the maximum row and column sizes
647 	void computeLayout(NanoContext ctx, const Widget widget,
648 					   ref Array!(int)[2] grid, const Widget skipped) const
649 	{
650 		int axis1 = cast(int)  mOrientation;
651 		int axis2 = cast(int) !mOrientation;
652 		size_t numChildren = widget.children.length, visibleChildren = 0;
653 		foreach (w; widget.children)
654 			visibleChildren += w.visible ? 1 : 0;
655 
656 		Vector2i dim;
657 		// count of items in main axis
658 		dim[axis1] = mResolution;
659 		// count of items in secondary axis
660 		dim[axis2] = cast(int) ((visibleChildren + mResolution - 1) / mResolution);
661 
662 		grid[axis1].clear(); grid[axis1].length = dim[axis1]; grid[axis1][] = 0;
663 		grid[axis2].clear(); grid[axis2].length = dim[axis2]; grid[axis2][] = 0;
664 
665 		size_t child;
666 		foreach(int i2; 0..dim[axis2])
667 		{
668 			foreach(int i1; 0..dim[axis1])
669 			{
670 				import std.typecons : Rebindable;
671 				Rebindable!(const Widget) w;
672 				do {
673 					if (child >= numChildren)
674 						return;
675 					w = widget.children[child++];
676 				} while (!w.visible || w is skipped);
677 
678 				Vector2i ps = w.preferredSize(ctx);
679 				Vector2i fs = w.fixedSize();
680 				auto targetSize = Vector2i(
681 					fs[0] ? fs[0] : ps[0],
682 					fs[1] ? fs[1] : ps[1]
683 				);
684 
685 				grid[axis1][i1] = max(grid[axis1][i1], targetSize[axis1]);
686 				grid[axis2][i2] = max(grid[axis2][i2], targetSize[axis2]);
687 			}
688 		}
689 	}
690 
691 	/// The Orientation defining this GridLayout.
692 	Orientation mOrientation;
693 
694 	/// The default Alignment for this GridLayout.
695 	Alignment[2] mDefaultAlignment;
696 
697 	/// The actual Alignment being used.
698 	Array!(Alignment)[2] mAlignment;
699 
700 	/// The number of rows or columns before starting a new one, depending on the Orientation.
701 	int mResolution;
702 
703 	/// The spacing used for each dimension.
704 	Vector2i mSpacing;
705 
706 	/// The margin around this GridLayout.
707 	int mMargin;
708 }
709 
710 /**
711  * The is a fancier grid layout with support for items that span multiple rows
712  * or columns, and per-widget alignment flags. Each row and column additionally
713  * stores a stretch factor that controls how additional space is redistributed.
714  * The downside of this flexibility is that a layout anchor data structure must
715  * be provided for each widget.
716  *
717  * An example:
718  *
719  *    Label label = new Label(window, "A label");
720  *    // Add a centered label at grid position (1, 5), which spans two horizontal cells
721  *    layout.setAnchor(label, AdvancedGridLayout.Anchor(1, 5, 2, 1, Alignment.Middle, Alignment.Middle));
722  *
723  * The grid is initialized with user-specified column and row size vectors
724  * (which can be expanded later on if desired). If a size value of zero is
725  * specified for a column or row, the size is set to the maximum preferred size
726  * of any widgets contained in the same row or column. Any remaining space is
727  * redistributed according to the row and column stretch factors.
728  *
729  * The high level usage somewhat resembles the classic HIG layout:
730  *
731  * - https://web.archive.org/web/20070813221705/http://www.autel.cz/dmi/tutorial.html
732  * - https://github.com/jaapgeurts/higlayout
733  */
734 class AdvancedGridLayout : Layout
735 {
736 	/**
737 	 *Helper struct to coordinate anchor points for the layout.
738 	 */
739 	struct Anchor
740 	{
741 		ubyte[2] pos;	 ///< The ``(x, y)`` position.
742 		ubyte[2] size;	 ///< The ``(x, y)`` size.
743 		Alignment[2] algn;///< The ``(x, y)`` Alignment.
744 
745 		/// Creates a ``0`` Anchor.
746 		//this() { }
747 
748 		/// Create an Anchor at position ``(x, y)`` with specified Alignment.
749 		this(int x, int y, Alignment horiz = Alignment.Fill,
750 			 Alignment vert = Alignment.Fill)
751 		{
752 			pos[0] = cast(ubyte) x; pos[1] = cast(ubyte) y;
753 			size[0] = size[1] = 1;
754 			algn[0] = horiz; algn[1] = vert;
755 		}
756 
757 		/// Create an Anchor at position ``(x, y)`` of size ``(w, h)`` with specified alignments.
758 		this(int x, int y, int w, int h,
759 			 Alignment horiz = Alignment.Fill,
760 			 Alignment vert = Alignment.Fill)
761 		{
762 			pos[0] = cast(ubyte) x; pos[1] = cast(ubyte) y;
763 			size[0] = cast(ubyte) w; size[1] = cast(ubyte) h;
764 			algn[0] = horiz; algn[1] = vert;
765 		}
766 
767 		/// Allows for printing out Anchor position, size, and alignment.
768 		string toString() const
769 		{
770 			import std.string : format;
771 			return format("pos=(%d, %d), size=(%d, %d), align=(%d, %d)",
772 						  pos[0], pos[1], size[0], size[1], cast(int) algn[0], cast(int) algn[1]);
773 		}
774 	}
775 
776 	/// Creates an AdvancedGridLayout with specified columns, rows, and margin.
777 	this(int[] cols, int[] rows, int margin = 0)
778 	{
779 		mCols = Array!int(cols);
780 		mRows = Array!int(rows);
781 		mMargin = margin;
782 		mColStretch.length = mCols.length; mColStretch[] = 0;
783 		mRowStretch.length = mRows.length; mRowStretch[] = 0;
784 	}
785 
786 	/// The margin of this AdvancedGridLayout.
787 	final int margin() const { return mMargin; }
788 
789 	/// Sets the margin of this AdvancedGridLayout.
790 	final void margin(int margin) { mMargin = margin; }
791 
792 	/// Return the number of cols
793 	final int colCount() const { return cast(int) mCols.length; }
794 
795 	/// Return the number of rows
796 	final int rowCount() const { return cast(int) mRows.length; }
797 
798 	/// Append a row of the given size (and stretch factor)
799 	final void appendRow(int size, float stretch = 0f) { mRows.insertBack(size); mRowStretch.insertBack(stretch); }
800 
801 	/// Append a column of the given size (and stretch factor)
802 	final void appendCol(int size, float stretch = 0f) { mCols.insertBack(size); mColStretch.insertBack(stretch); }
803 
804 	/// Set the stretch factor of a given row
805 	final void setRowStretch(int index, float stretch) { mRowStretch[index] = stretch; }
806 
807 	/// Set the stretch factor of a given column
808 	final void setColStretch(int index, float stretch) { mColStretch[index] = stretch; }
809 
810 	/// Specify the anchor data structure for a given widget
811 	final void setAnchor(const Widget widget, const Anchor anchor) { mAnchor[widget] = anchor; }
812 
813 	/// Retrieve the anchor data structure for a given widget
814 	Anchor anchor(const Widget widget) const
815 	{
816 		auto it = widget in mAnchor;
817 		if (it is null)
818 			throw new Exception("Widget was not registered with the grid layout!");
819 
820 		return cast(Anchor) *it;
821 	}
822 
823 	/* Implementation of the layout interface */
824 	Vector2i preferredSize(NanoContext ctx, const Widget widget, const Widget skipped = null) const
825 	{
826 		/* Compute minimum row / column sizes */
827 		Array!int[2] grid;
828 		computeLayout(ctx, widget, grid);
829 
830 		import std.algorithm : sum;
831 
832 		Vector2i size = Vector2i(sum(grid[0][]),
833 								 sum(grid[1][]));
834 		Vector2i extra = Vector2i(2 * mMargin, 2 * mMargin);
835 		auto window = cast(const Window) widget;
836 		if (window && window.title.length)
837 			extra[1] += widget.theme().mWindowHeaderHeight - mMargin/2;
838 
839 		return size+extra;
840 	}
841 
842 	void performLayout(NanoContext ctx, Widget widget) const
843 	{
844 		Array!int[2] grid;
845 		computeLayout(ctx, widget, grid);
846 		grid[0].insertBefore(grid[0][0..$], mMargin);
847 		auto window = cast(const Window) widget;
848 		if (window && window.title.length)
849 			grid[1].insertBefore(grid[1][0..$], widget.theme.mWindowHeaderHeight + mMargin/2);
850 		else
851 			grid[1].insertBefore(grid[1][0..$], mMargin);
852 
853 		for (int axis=0; axis<2; ++axis) {
854 			for (size_t i=1; i<grid[axis].length; ++i)
855 				grid[axis][i] += grid[axis][i-1];
856 
857 			foreach (w; widget.children()) {
858 				if (!w.visible())
859 					continue;
860 				Anchor anchor = this.anchor(w);
861 
862 				int itemPos = grid[axis][anchor.pos[axis]];
863 				int cellSize  = grid[axis][anchor.pos[axis] + anchor.size[axis]] - itemPos;
864 				int ps = w.preferredSize(ctx)[axis], fs = w.fixedSize()[axis];
865 				int targetSize = fs ? fs : ps;
866 
867 				final switch (anchor.algn[axis]) {
868 				case Alignment.Minimum:
869 					break;
870 				case Alignment.Middle:
871 					itemPos += (cellSize - targetSize) / 2;
872 					break;
873 				case Alignment.Maximum:
874 					itemPos += cellSize - targetSize;
875 					break;
876 				case Alignment.Fill:
877 					targetSize = fs ? fs : cellSize;
878 					break;
879 				}
880 
881 				Vector2i pos = w.position(), size = w.size();
882 				pos[axis] = itemPos;
883 				size[axis] = targetSize;
884 				w.position = pos;
885 				w.size = size;
886 				w.performLayout(ctx);
887 			}
888 		}
889 	}
890 
891 protected:
892 	/// Computes the layout
893 	void computeLayout(NanoContext ctx, const Widget widget,
894 					   ref Array!(int)[2] _grid) const
895 	{
896 		Vector2i fs_w = widget.fixedSize();
897 		Vector2i containerSize = Vector2i(fs_w[0] ? fs_w[0] : widget.width(), fs_w[1] ? fs_w[1] : widget.height());
898 		Vector2i extra = Vector2i(2 * mMargin, 2 * mMargin);
899 		auto window = cast(const Window) widget;
900 		if (window && window.title.length)
901 			extra[1] += widget.theme().mWindowHeaderHeight - mMargin/2;
902 
903 		containerSize -= extra;
904 
905 		for (int axis=0; axis<2; ++axis) {
906 			const sizes = axis == 0 ? mCols : mRows;
907 			const stretch = axis == 0 ? mColStretch : mRowStretch;
908 
909 			_grid[axis].clear;
910 			_grid[axis].insertBack(sizes[]);
911 
912 			auto grid = _grid[axis];
913 
914 			for (int phase = 0; phase < 2; ++phase) {
915 				foreach (const Widget w, const Anchor anchor; mAnchor) {
916 					if (!w.visible())
917 						continue;
918 					if ((anchor.size[axis] == 1) != (phase == 0))
919 						continue;
920 					int ps = w.preferredSize(ctx)[axis], fs = w.fixedSize()[axis];
921 					int targetSize = fs ? fs : ps;
922 
923 					if (anchor.pos[axis] + anchor.size[axis] > cast(int) grid.length)
924 						throw new Exception("Advanced grid layout: widget is out of bounds: " ~ anchor.toString);
925 
926 					int currentSize = 0;
927 					float totalStretch = 0;
928 
929 					import std.algorithm : max;
930 
931 					for (int i = anchor.pos[axis];
932 						 i < anchor.pos[axis] + anchor.size[axis]; ++i) {
933 						if (sizes[i] == 0 && anchor.size[axis] == 1)
934 							grid[i] = max(grid[i], targetSize);
935 						currentSize += grid[i];
936 						totalStretch += stretch[i];
937 					}
938 					if (targetSize <= currentSize)
939 						continue;
940 					if (totalStretch == 0)
941 						throw new Exception("Advanced grid layout: no space to place widget: ", anchor.toString);
942 					import std.math : round;
943 
944 					float amt = (targetSize - currentSize) / totalStretch;
945 					for (int i = anchor.pos[axis];
946 						 i < anchor.pos[axis] + anchor.size[axis]; ++i) {
947 						grid[i] += cast(int) round(amt * stretch[i]);
948 					}
949 				}
950 			}
951 
952 			import std.algorithm : sum;
953 			int currentSize = sum(grid[]);
954 			float totalStretch;
955 			foreach(e; stretch[])
956 				totalStretch += e;
957 			if (currentSize >= containerSize[axis] || totalStretch == 0)
958 				continue;
959 			float amt = (containerSize[axis] - currentSize) / totalStretch;
960 			import std.math : round;
961 			for (auto i = 0; i<grid.length; ++i)
962 				grid[i] += cast(int) round(amt * stretch[i]);
963 		}
964 	}
965 
966 protected:
967 	/// The columns of this AdvancedGridLayout.
968 	Array!int mCols;
969 
970 	/// The rows of this AdvancedGridLayout.
971 	Array!int mRows;
972 
973 	/// The stretch for each column of this AdvancedGridLayout.
974 	Array!float mColStretch;
975 
976 	/// The stretch for each row of this AdvancedGridLayout.
977 	Array!float mRowStretch;
978 
979 	/// The mapping of widgets to their specified anchor points.
980 	Anchor[const Widget] mAnchor;
981 
982 	/// The margin around this AdvancedGridLayout.
983 	int mMargin;
984 }