1 /**
2 Grimoire
3 Copyright (c) 2017 Enalye
4 
5 This software is provided 'as-is', without any express or implied warranty.
6 In no event will the authors be held liable for any damages arising
7 from the use of this software.
8 
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute
11 it freely, subject to the following restrictions:
12 
13 	1. The origin of this software must not be misrepresented;
14 	   you must not claim that you wrote the original software.
15 	   If you use this software in a product, an acknowledgment
16 	   in the product documentation would be appreciated but
17 	   is not required.
18 
19 	2. Altered source versions must be plainly marked as such,
20 	   and must not be misrepresented as being the original software.
21 
22 	3. This notice may not be removed or altered from any source distribution.
23 */
24 
25 module ui.layout;
26 
27 import std.conv: to;
28 import std.algorithm.comparison: max;
29 
30 import render.window;
31 import core.all;
32 import ui.widget;
33 
34 import common.all;
35 
36 class VLayout: WidgetGroup {
37 	private {
38 		Vec2f _spacing = Vec2f.zero;
39 		uint _capacity;
40 	}
41 
42 	@property {
43 		Vec2f spacing() const { return _spacing; }
44 		Vec2f spacing(Vec2f newPadding) { _spacing = newPadding; resize(); return _spacing; }
45 
46 		uint capacity() const { return _capacity; }
47 		uint capacity(uint newCapacity) { _capacity = newCapacity; resize(); return _capacity; }
48 	}
49 
50 	this() {}
51 
52 	this(Vec2f newSize) {
53 		size = newSize;
54 	}
55 
56 	override void addChild(Widget widget) {
57 		super.addChild(widget);
58 		resize();
59 	}
60 
61     override void onSize() {
62         resize();
63     }
64 
65 	protected void resize() {
66 		if(!_children.length)
67 			return;
68 		Vec2f childSize = Vec2f(_size.x, _size.y / (_capacity != 0u ? _capacity : _children.length));
69 		Vec2f origin = (_isFrame ? Vec2f.zero : _position) - _size / 2 + childSize / 2f;
70 		foreach(uint id, Widget widget; _children) {
71 			widget.position = origin + Vec2f(0f, childSize.y * to!float(id));
72 			widget.size = childSize - _spacing;
73 		}
74 	}
75 }
76 
77 class HLayout: WidgetGroup {
78 	private {
79 		Vec2f _spacing = Vec2f.zero;
80 		uint _capacity;
81 	}
82 
83 	@property {
84 		Vec2f spacing() const { return _spacing; }
85 		Vec2f spacing(Vec2f newPadding) { _spacing = newPadding; resize(); return _spacing; }
86 
87 		uint capacity() const { return _capacity; }
88 		uint capacity(uint newCapacity) { _capacity = newCapacity; resize(); return _capacity; }
89 	}
90 
91 	this() {}
92 
93 	this(Vec2f newSize) {
94 		size = newSize;
95 	}
96 
97 	override void addChild(Widget widget) {
98 		super.addChild(widget);
99 		resize();
100 	}
101 
102     override void onSize() {
103         resize();
104     }
105 
106 	protected void resize() {
107 		if(!_children.length)
108 			return;
109 		Vec2f childSize = Vec2f(_size.x / (_capacity != 0u ? _capacity : _children.length), _size.y);
110 		Vec2f origin = (_isFrame ? Vec2f.zero : _position) - _size / 2 + childSize / 2f;
111 		foreach(uint id, Widget widget; _children) {
112 			widget.position = origin + Vec2f(childSize.x * to!float(id), 0f);
113 			widget.size = childSize - _spacing;
114 		}
115 	}
116 }
117 
118 class GridLayout: WidgetGroup {
119 	private {
120 		Vec2f _spacing = Vec2f.zero;
121 		Vec2u _capacity;
122 	}
123 
124 	@property {
125 		Vec2f spacing() const { return _spacing; }
126 		Vec2f spacing(Vec2f newPadding) { _spacing = newPadding; resize(); return _spacing; }
127 
128 		Vec2u capacity() const { return _capacity; }
129 		Vec2u capacity(Vec2u newCapacity) { _capacity = newCapacity; resize(); return _capacity; }
130 	}
131 
132 	this() {}
133 
134 	this(Vec2f newSize) {
135 		size = newSize;
136 	}
137 
138 	override void addChild(Widget widget) {
139 		super.addChild(widget);
140 		resize();
141 	}
142 
143     override void onSize() {
144         resize();
145     }
146 
147 	protected void resize() {
148 		if(!_children.length || _capacity.x == 0u)
149 			return;
150 
151 		int yCapacity = _capacity.y;
152 		if(yCapacity == 0u)
153 			yCapacity = (to!int(_children.length) / _capacity.x) + 1;
154 
155 		Vec2f childSize = Vec2f(_size.x / _capacity.x, _size.y / yCapacity);
156 		Vec2f origin = (_isFrame ? Vec2f.zero : _position) - _size / 2 + childSize / 2f;
157 		foreach(uint id, Widget widget; _children) {
158 			Vec2u coords = Vec2u(id % _capacity.x, id / _capacity.x);
159 			widget.position = origin + Vec2f(childSize.x * coords.x, childSize.y * coords.y);
160 			widget.size = childSize - _spacing;
161 		}
162 	}
163 }
164 
165 class VContainer: WidgetGroup {
166 	protected {
167 		Vec2f _spacing = Vec2f.zero;
168 	}
169 
170 	@property {
171 		Vec2f spacing() const { return _spacing; }
172 		Vec2f spacing(Vec2f newPadding) { _spacing = newPadding; resize(); return _spacing; }
173 	}
174 
175 	this() {}
176 
177 	override void addChild(Widget widget) {
178 		super.addChild(widget);
179 		resize();
180 	}
181 
182     override void onPosition() {
183         resize();
184     }
185 
186     override void onSize() {
187         resize();
188     }
189 
190     override void onAnchor() {
191         resize();
192     }
193 
194 	protected void resize() {
195 		if(!_children.length) {
196 			_size = Vec2f.zero;
197 			return;
198 		}
199 
200 		Vec2f totalSize = Vec2f.zero;
201 		foreach(Widget widget; _children) {
202 			totalSize.y += widget.size.y + _spacing.y;
203 			totalSize.x = max(totalSize.x, widget.size.x);
204 		}
205 		_size = totalSize + Vec2f(_spacing.x * 2f, _spacing.y);
206 		Vec2f currentPosition = _position - (_size * _anchor) + _spacing;
207 		foreach(Widget widget; _children) {
208 			widget.position = currentPosition + widget.size / 2f;
209 			currentPosition = currentPosition + Vec2f(0f, widget.size.y + _spacing.y);
210 		}
211 	}
212 }
213 
214 class HContainer: WidgetGroup {
215 	protected {
216 		Vec2f _spacing = Vec2f.zero;
217 	}
218 
219 	@property {
220 		Vec2f spacing() const { return _spacing; }
221 		Vec2f spacing(Vec2f newPadding) { _spacing = newPadding; resize(); return _spacing; }
222 	}
223 
224 	this() {}
225 
226 	override void addChild(Widget widget) {
227 		super.addChild(widget);
228 		resize();
229 	}
230 
231     override void onPosition() {
232         resize();
233     }
234 
235     override void onSize() {
236         resize();
237     }
238 
239     override void onAnchor() {
240         resize();
241     }
242 
243 	protected void resize() {
244 		if(!_children.length) {
245 			_size = Vec2f.zero;
246 			return;
247 		}
248 
249 		Vec2f totalSize = Vec2f.zero;
250 		foreach(Widget widget; _children) {
251 			totalSize.y = max(totalSize.y, widget.size.y);
252 			totalSize.x += widget.size.x + _spacing.x;
253 		}
254 		_size = totalSize + Vec2f(_spacing.x, _spacing.y * 2f);
255 		Vec2f currentPosition = _position - (_size * _anchor) + _spacing;
256 		foreach(Widget widget; _children) {
257 			widget.position = currentPosition + widget.size / 2f;
258 			currentPosition = currentPosition + Vec2f(widget.size.x + _spacing.x, 0f);
259 		}
260 	}
261 }
262 
263 class AnchoredLayout: WidgetGroup {
264 	private {
265 		Vec2f[] _childrenPositions, _childrenSizes;
266 	}
267 
268 	this() {}
269 
270 	this(Vec2f newSize) {
271 		size = newSize;
272 	}
273 
274 	override void addChild(Widget widget) {
275 		super.addChild(widget);
276 		_childrenPositions ~= Vec2f.half;
277 		_childrenSizes ~= Vec2f.one;
278 		resize();
279 	}
280 
281     override void onPosition() {
282         resize();
283     }
284 
285     override void onSize() {
286         resize();
287     }
288 
289 	void addChild(Widget widget, Vec2f position, Vec2f size) {
290 		super.addChild(widget);
291 		_childrenPositions ~= position;
292 		_childrenSizes ~= size;
293 		resize();
294 	}
295 
296 	override void removeChildren() {
297 		super.removeChildren();
298 		_childrenPositions.length = 0L;
299 		_childrenSizes.length = 0L;
300 	}
301 
302 	protected void resize() {
303 		if(!_children.length)
304 			return;
305 		Vec2f origin = (_isFrame ? Vec2f.zero : _position) - _size / 2;
306 		foreach(uint id, Widget widget; _children) {
307 			widget.position = origin + _size * _childrenPositions[id];
308 			widget.size = _size * _childrenSizes[id];
309 		}
310 	}
311 }
312 
313 class LogLayout: WidgetGroup {
314 	this() {}
315 
316 	override void addChild(Widget widget) {
317 		super.addChild(widget);
318 		resize();
319 	}
320 
321     override void onSize() {
322         resize();
323     }
324 
325 	protected void resize() {
326 		if(!_children.length)
327 			return;
328 
329 		Vec2f totalSize = Vec2f.zero;
330 		foreach(Widget widget; _children) {
331 			totalSize.y += widget.size.y;
332 			totalSize.x = max(totalSize.x, widget.size.x);
333 		}
334 		_size = totalSize;
335 		Vec2f currentPosition = _position - _size / 2f;
336 		foreach(Widget widget; _children) {
337 			widget.position = currentPosition + widget.size / 2f;
338 			currentPosition = currentPosition + Vec2f(0f, widget.size.y);
339 		}
340 	}
341 }