1 /**
2 Copyright: Copyright (c) 2013-2014 Andrey Penechko.
3 License: a$(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 
7 module anchovy.gui.widget;
8 
9 import std.traits;
10 import anchovy.gui;
11 public import anchovy.utils.flexibleobject;
12 
13 enum defaultAnchor = Sides.left | Sides.top;
14 
15 /// Used to specify Widget.anchor.
16 enum Sides
17 {
18 	left = 1,
19 	right = 2,
20 	top = 4,
21 	bottom = 8,
22 }
23 
24 //version = Debug_widget;
25 
26 /// Container for common properties
27 class Widget : FlexibleObject
28 {
29 public:
30 
31 	this()
32 	{
33 		properties["type"] = new ValueProperty(this, "widget");
34 		properties["anchor"] = anchor = new ValueProperty(this, defaultAnchor);
35 		properties["children"] = children = new ValueProperty(this, cast(Widget[])[]);
36 		properties["logicalChildren"] = logicalChildren = new ValueProperty(this, cast(Widget[])[]);
37 		properties["parent"] = parent = new ValueProperty(this, null);
38 
39 		properties["position"] = position = new ValueProperty(this, ivec2(0,0));
40 		properties["staticPosition"] = staticPosition = new ValueProperty(this, ivec2(0,0));
41 
42 		properties["minSize"] = minSize = new ValueProperty(this, ivec2(0,0));
43 		properties["size"] = size = new ValueProperty(this, ivec2(0,0));
44 		properties["prefSize"] = prefferedSize = new ValueProperty(this, ivec2(0,0));
45 		properties["staticRect"] = staticRect = new ValueProperty(this, Rect(0,0,0,0));
46 
47 		properties["state"] = state = new ValueProperty(this, "normal");
48 		properties["style"] = style = new ValueProperty(this, "");
49 		properties["geometry"] = geometry = new ValueProperty(this, (TexRectArray[string]).init);
50 
51 		properties["hasBack"] = hasBack = new ValueProperty(this, true);
52 		properties["isVisible"] = isVisible = new ValueProperty(this, true);
53 		properties["isFocusable"] = isFocusable = new ValueProperty(this, false);
54 		properties["isEnabled"] = isEnabled = new ValueProperty(this, true);
55 		properties["isHovered"] = isHovered = new ValueProperty(this, false);
56 		properties["respondsToPointer"] = respondsToPointer = new ValueProperty(this, true);
57 		properties["context"] = context = new ValueProperty(this, null);
58 
59 		auto onParentChanged = (FlexibleObject obj, Variant newParent){
60 			(cast(Widget)obj).invalidateLayout;
61 		};
62 
63 		auto onPositionChanged = (FlexibleObject obj, Variant newPosition){
64 			(cast(Widget)obj).invalidateLayout;
65 		};
66 
67 		property("parent").valueChanged.connect(onParentChanged);
68 		property("position").valueChanged.connect(onPositionChanged);
69 
70 		auto onStaticPositionChanged = (FlexibleObject obj, Variant newStaticPosition){
71 			obj["staticRect"] = Rect(newStaticPosition.get!ivec2, obj.getPropertyAs!("size", ivec2));
72 		};
73 
74 		auto onSizeChanged = (FlexibleObject obj, Variant newSize){
75 			obj["staticRect"] = Rect(obj.getPropertyAs!("staticPosition", ivec2), newSize.get!ivec2);
76 
77 			obj.setProperty!("geometry", TexRectArray[string])(null);
78 
79 			(cast(Widget)obj).invalidateLayout;
80 		};
81 
82 		auto onVisibilityChanged = (FlexibleObject obj, Variant isVisible)
83 		{
84 			Widget parent = obj.getPropertyAs!("parent", Widget);
85 			if (parent is null) return;
86 
87 			Widget child = cast(Widget)obj;
88 
89 			import std.algorithm : remove;
90 
91 			if (obj["isVisible"] == true)
92 				parent.setProperty!"children"(parent["children"] ~ child); // FIX, sholud insert in the same position
93 			else
94 				parent["children"] = parent["children"].get!(Widget[]).remove!((a) => a == child);
95 		};
96 
97 		property("staticPosition").valueChanged.connect(onStaticPositionChanged);
98 		property("size").valueChanged.connect(onSizeChanged);
99 		property("isVisible").valueChanged.connect(onVisibilityChanged);
100 
101 		addEventHandler(&handleDraw);
102 		addEventHandler(&handleUpdatePosition);
103 		addEventHandler(&handleExpand);
104 		addEventHandler(&handleMinimize);
105 	}
106 
107 	bool handleExpand(Widget widget, ExpandLayoutEvent event)
108 	{
109 		if (auto layout = widget.peekPropertyAs!("layout", ILayout))
110 		{
111 			layout.expand(widget);
112 		}
113 
114 		return true;
115 	}
116 
117 	bool handleMinimize(Widget widget, MinimizeLayoutEvent event)
118 	{
119 		if (auto layout = widget.peekPropertyAs!("layout", ILayout))
120 		{
121 			layout.minimize(widget);
122 		}
123 
124 		return true;
125 	}
126 
127 	bool handleUpdatePosition(Widget widget, UpdatePositionEvent event)
128 	{
129 		if (auto parent = widget.peekPropertyAs!("parent", Widget))
130 		{
131 			if (*parent !is null)
132 			{
133 				ivec2 parentPos = (*parent).getPropertyAs!("staticPosition", ivec2);
134 				ivec2 childPos = widget.getPropertyAs!("position", ivec2);
135 				ivec2 newStaticPosition = parentPos + childPos; // Workaround bug with direct summ.
136 				widget["staticPosition"] = newStaticPosition;
137 				widget["staticRect"] = Rect(newStaticPosition, widget.getPropertyAs!("size", ivec2));
138 			}
139 		}
140 
141 		return true;
142 	}
143 
144 	bool handleDraw(Widget widget, DrawEvent event)
145 	{
146 		bool clipContent = widget.hasProperty!("clipContent");
147 
148 		if (event.sinking)
149 		{
150 			Rect staticRect = widget.getPropertyAs!("staticRect", Rect);
151 
152 			if (clipContent)
153 			{
154 				event.guiRenderer.pushClientArea(staticRect);
155 			}
156 
157 			if (widget.getPropertyAs!("hasBack", bool))
158 				event.guiRenderer.drawControlBack(widget, staticRect);
159 		}
160 		else if (clipContent)
161 			event.guiRenderer.popClientArea;
162 
163 		return true;
164 	}
165 
166 
167 	ValueProperty anchor;
168 
169 	ValueProperty children; // visible children
170 	ValueProperty logicalChildren; // all children
171 	ValueProperty parent;
172 	ValueProperty context;
173 
174 	ValueProperty position;
175 	ValueProperty staticPosition;
176 
177 	ValueProperty minSize;
178 	ValueProperty size;
179 	ValueProperty prefferedSize;
180 	ValueProperty staticRect;
181 
182 	ValueProperty state;
183 	ValueProperty style;
184 	ValueProperty geometry;
185 
186 	ValueProperty isFocusable;
187 	ValueProperty isEnabled;
188 	ValueProperty isHovered;
189 	ValueProperty isVisible;
190 	ValueProperty hasBack;
191 	ValueProperty respondsToPointer;
192 
193 	void addEventHandler(T)(T handler)
194 	{
195 		static assert(isDelegate!T, "handler must be a delegate, not " ~ T.stringof);
196 		alias widgetType = ParameterTypeTuple!T[0];
197 		alias eventType = ParameterTypeTuple!T[1];
198 		static assert(!is(eventType == Event), "handler's parameter must not be Event class but inherited one");
199 		static assert(is(eventType : Event), "handler's parameter must be inherited from Event class");
200 		static assert(is(widgetType : Widget), "handler must accept Widget as first parameter");
201 		static assert(ParameterTypeTuple!T.length == 2, "handler must have only two parameters, Widget's and Event's descendant");
202 		_eventHandlers[typeid(eventType)] ~= cast(bool delegate(Widget, Event))handler;
203 	}
204 
205 	void removeEventHandlers(T)()
206 	{
207 		_eventHandlers[typeid(T)] = null;
208 	}
209 
210 	/// Returns true if event was handled
211 	/// This handler will be called by Gui class twice, before and after visiting its children.
212 	/// In first case sinking flag will be true;
213 	bool handleEvent(Event e)
214 	{
215 		bool result = false;
216 		if (auto handlers = typeid(e) in _eventHandlers)
217 		{
218 			foreach(h; *handlers)
219 			{
220 				result |= h(this, e);
221 			}
222 		}
223 		return result;
224 	}
225 
226 	/// Event handlers.
227 	bool delegate(Widget, Event)[][TypeInfo] _eventHandlers;
228 }
229 
230 Widget getParentFromWidget(Widget root)
231 {
232 	Widget* container;
233 	container = root.peekPropertyAs!("container", Widget);
234 
235 	if (container is null) container = &root;
236 	assert(*container);
237 
238 	return *container;
239 }
240 
241 Behavior getWidgetBehavior(Behavior)(Widget widget)
242 {
243 	IWidgetBehavior[] behaviors = widget.getPropertyAs!("behaviors", IWidgetBehavior[]);
244 
245 	foreach(b; behaviors)
246 	{
247 		if (auto beh = cast(Behavior)b)
248 			return beh;
249 	}
250 
251 	return null;
252 }
253 
254 import std.range : only;
255 void addChildBefore(Widget root, Widget child, Widget before)
256 {
257 	import std.algorithm : findSplitBefore;
258 
259 	Widget parent = getParentFromWidget(root);
260 
261 	child.setProperty!"parent"(parent);
262 
263 	Widget[] logicalChildren = parent.getPropertyAs!("logicalChildren", Widget[]);
264 	auto result1 = findSplitBefore(logicalChildren, only(before));
265 	parent.setProperty!"logicalChildren"(result1[0] ~ child ~ result1[1]);
266 
267 	if (child.isVisible.value == true)
268 	{
269 		Widget[] children = parent.getPropertyAs!("children", Widget[]);
270 		auto result2 = findSplitBefore(children, only(before));
271 		parent.setProperty!"children"(result2[0] ~ child ~ result2[1]);
272 	}
273 }
274 
275 void addChildAfter(Widget root, Widget child, Widget after)
276 {
277 	import std.algorithm : findSplitAfter;
278 
279 	Widget parent = getParentFromWidget(root);
280 
281 	child.setProperty!"parent"(parent);
282 
283 	Widget[] logicalChildren = parent.getPropertyAs!("logicalChildren", Widget[]);
284 	auto result1 = findSplitAfter(logicalChildren, only(after));
285 	parent.setProperty!"logicalChildren"(result1[0] ~ child ~ result1[1]);
286 
287 	if (child.isVisible.value == true)
288 	{
289 		Widget[] children = parent.getPropertyAs!("children", Widget[]);
290 		auto result2 = findSplitAfter(children, only(after));
291 		parent.setProperty!"children"(result2[0] ~ child ~ result2[1]);
292 	}
293 }
294 
295 void replaceChildBy(Widget root, Widget child, Widget replacement)
296 {
297 	assert(root);
298 	assert(child);
299 	assert(replacement);
300 
301 	import std.algorithm : findSplit;
302 
303 	Widget parent = getParentFromWidget(root);
304 
305 	replacement.setProperty!"parent"(parent);
306 	child.setProperty!"parent"(null);
307 
308 	Widget[] logicalChildren = parent.getPropertyAs!("logicalChildren", Widget[]);
309 	auto result1 = findSplit(logicalChildren, only(child));
310 	parent.setProperty!"logicalChildren"(result1[0] ~ replacement ~ result1[2]);
311 
312 	if (replacement.isVisible.value == true)
313 	{
314 		Widget[] children = parent.getPropertyAs!("children", Widget[]);
315 		auto result2 = findSplit(children, only(child));
316 		parent.setProperty!"children"(result2[0] ~ replacement ~ result2[2]);
317 	}
318 }
319 
320 void addChild(Widget root, Widget child)
321 in
322 {
323 	assert(root);
324 	assert(child);
325 }
326 body
327 {
328 	Widget parent = getParentFromWidget(root);
329 
330 	parent.setProperty!"logicalChildren"(parent["logicalChildren"] ~ child);
331 	child.setProperty!"parent"(parent);
332 	if (child.isVisible.value == true)
333 		parent.setProperty!"children"(parent["children"] ~ child);
334 }
335 
336 void detachFromParent(Widget child)
337 {
338 	assert(child);
339 
340 	import std.algorithm : remove;
341 
342 	Widget parent = child.getPropertyAs!("parent", Widget);
343 
344 	child["parent"] = null;
345 	parent["children"] = parent["children"].get!(Widget[]).remove!((a) => a == child);
346 	parent["logicalChildren"] = parent["logicalChildren"].get!(Widget[]).remove!((a) => a == child);
347 }
348 
349 void removeChild(Widget root, Widget child)
350 {
351 	if (child is null) return;
352 
353 	import std.algorithm : remove;
354 
355 	Widget parent = getParentFromWidget(root);
356 	child["parent"] = null;
357 	parent["children"] = parent["children"].get!(Widget[]).remove!((a) => a == child);
358 	parent["logicalChildren"] = parent["logicalChildren"].get!(Widget[]).remove!((a) => a == child);
359 }
360 
361 /// Says to global layout manager that this widget needs layout update.
362 void invalidateLayout(Widget widget)
363 {
364 	widget.getPropertyAs!("context", GuiContext).eventDispatcher.invalidateWidgetLayout(widget);
365 }