1 /*
2 Copyright (c) 2013 Andrey Penechko
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license the "Software" to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12 
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19 
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 */
28 
29 module anchovy.gui.widget;
30 
31 import std.traits;
32 import anchovy.gui;
33 public import anchovy.utils.flexibleobject;
34 
35 enum defaultAnchor = Sides.LEFT | Sides.TOP;
36 
37 /// Used to specify Widget.anchor.
38 enum Sides
39 {
40 	LEFT = 1,
41 	RIGHT = 2,
42 	TOP = 4,
43 	BOTTOM = 8,
44 }
45 
46 //version = Debug_widget;
47 
48 /// Container for common properties
49 class Widget : FlexibleObject
50 {
51 public:
52 
53 	this()
54 	{
55 		properties["type"] = new ValueProperty(this, "widget");
56 		properties["anchor"] = anchor = new ValueProperty(this, defaultAnchor);
57 		properties["children"] = children = new ValueProperty(this, cast(Widget[])[]);
58 		properties["parent"] = parent = new ValueProperty(this, null);
59 
60 		properties["position"] = position = new ValueProperty(this, ivec2(0,0));
61 		properties["staticPosition"] = staticPosition = new ValueProperty(this, ivec2(0,0));
62 
63 		properties["minSize"] = minSize = new ValueProperty(this, ivec2(0,0));
64 		properties["size"] = size = new ValueProperty(this, ivec2(0,0));
65 		properties["prefSize"] = prefferedSize = new ValueProperty(this, ivec2(0,0));
66 		properties["staticRect"] = staticRect = new ValueProperty(this, Rect(0,0,0,0));
67 
68 		properties["state"] = state = new ValueProperty(this, "normal");
69 		properties["style"] = style = new ValueProperty(this, "");
70 		properties["geometry"] = geometry = new ValueProperty(this, (TexRectArray[string]).init);
71 
72 		properties["isVisible"] = isVisible = new ValueProperty(this, true);
73 		properties["isFocusable"] = isFocusable = new ValueProperty(this, false);
74 		properties["isEnabled"] = isEnabled = new ValueProperty(this, true);
75 		properties["isHovered"] = isHovered = new ValueProperty(this, false);
76 		properties["context"] = context = new ValueProperty(this, null);
77 
78 		auto onParentChanged = (FlexibleObject obj, Variant newParent){
79 			(cast(Widget)obj).invalidateLayout;
80 		};
81 
82 		auto onPositionChanged = (FlexibleObject obj, Variant newPosition){
83 			(cast(Widget)obj).invalidateLayout;
84 		};
85 
86 		property("parent").valueChanged.connect(onParentChanged);
87 		property("position").valueChanged.connect(onPositionChanged);
88 
89 		auto onStaticPositionChanged = (FlexibleObject obj, Variant newStaticPosition){
90 			obj["staticRect"] = Rect(newStaticPosition.get!ivec2, obj.getPropertyAs!("size", ivec2));
91 		};
92 
93 		auto onSizeChanged = (FlexibleObject obj, Variant newSize){
94 			obj["staticRect"] = Rect(obj.getPropertyAs!("staticPosition", ivec2), newSize.get!ivec2);
95 
96 			obj.setProperty!("geometry", TexRectArray[string])(null);
97 
98 			(cast(Widget)obj).invalidateLayout;
99 		};
100 		
101 		property("staticPosition").valueChanged.connect(onStaticPositionChanged);
102 		property("size").valueChanged.connect(onSizeChanged);
103 
104 		addEventHandler(&handleDraw);
105 		addEventHandler(&handleUpdatePosition);
106 		addEventHandler(&handleExpand);
107 		addEventHandler(&handleMinimize);
108 	}
109 
110 	bool handleExpand(Widget widget, ExpandLayoutEvent event)
111 	{
112 		if (auto layout = widget.peekPropertyAs!("layout", ILayout))
113 		{
114 			layout.expand(widget);
115 		}
116 
117 		return true;
118 	}
119 
120 	bool handleMinimize(Widget widget, MinimizeLayoutEvent event)
121 	{
122 		if (auto layout = widget.peekPropertyAs!("layout", ILayout))
123 		{
124 			layout.minimize(widget);
125 		}
126 
127 		return true;
128 	}
129 
130 	bool handleUpdatePosition(Widget widget, UpdatePositionEvent event)
131 	{
132 		if (auto parent = widget.peekPropertyAs!("parent", Widget))
133 		{
134 			if (*parent !is null)
135 			{
136 				ivec2 parentPos = (*parent).getPropertyAs!("staticPosition", ivec2);
137 				ivec2 childPos = widget.getPropertyAs!("position", ivec2);
138 				ivec2 newStaticPosition = parentPos + childPos; // Workaround bug with direct summ.
139 				widget["staticPosition"] = newStaticPosition;
140 				widget["staticRect"] = Rect(newStaticPosition, widget.getPropertyAs!("size", ivec2));
141 			}
142 		}
143 
144 		return true;
145 	}
146 
147 	bool handleDraw(Widget widget, DrawEvent event)
148 	{
149 		if(widget.getPropertyAs!("isVisible", bool))
150 			event.guiRenderer.drawControlBack(widget, widget["staticRect"].get!Rect);
151 
152 		return true;
153 	}
154 
155 
156 	ValueProperty anchor;
157 
158 	ValueProperty children;
159 	ValueProperty parent;
160 	ValueProperty context;
161 
162 	ValueProperty position;
163 	ValueProperty staticPosition;
164 
165 	ValueProperty minSize;
166 	ValueProperty size;
167 	ValueProperty prefferedSize;
168 	ValueProperty staticRect;
169 
170 	ValueProperty state;
171 	ValueProperty style;
172 	ValueProperty geometry;
173 
174 	ValueProperty isFocusable;
175 	ValueProperty isEnabled;
176 	ValueProperty isHovered;
177 	ValueProperty isVisible;
178 	
179 	void addEventHandler(T)(T handler)
180 	{
181 		static assert(isDelegate!T, "handler must be a delegate, not " ~ T.stringof);
182 		alias widgetType = ParameterTypeTuple!T[0];
183 		alias eventType = ParameterTypeTuple!T[1];
184 		static assert(!is(eventType == Event), "handler's parameter must not be Event class but inherited one");
185 		static assert(is(eventType : Event), "handler's parameter must be inherited from Event class");
186 		static assert(is(widgetType : Widget), "handler must accept Widget as first parameter");
187 		static assert(ParameterTypeTuple!T.length == 2, "handler must have only two parameters, Widget's and Event's descendant");
188 		_eventHandlers[typeid(eventType)] ~= cast(bool delegate(Widget, Event))handler;
189 	}
190 
191 	void removeEventHandlers(T)()
192 	{
193 		_eventHandlers[typeid(T)] = null;
194 	}
195 
196 	/// Returns true if event was handled
197 	/// This handler will be called by Gui class twice, before and after visiting its children.
198 	/// In first case sinking flag will be true;
199 	bool handleEvent(Event e)
200 	{
201 		bool result = false;
202 		if (auto handlers = typeid(e) in _eventHandlers)
203 		{
204 			foreach(h; *handlers)
205 			{
206 				result |= h(this, e);
207 			}
208 		}
209 		return result;
210 	}
211 	
212 	/// Event handlers.
213 	bool delegate(Widget, Event)[][TypeInfo] _eventHandlers;
214 }
215 
216 void addChild(Widget root, Widget child)
217 in
218 {
219 	assert(root);
220 	assert(child);
221 }
222 body
223 {
224 	Widget* container;
225 	container = root.peekPropertyAs!("container", Widget);
226 
227 	if (container is null) container = &root;
228 	assert(*container);
229 
230 	Widget parent = *container;
231 
232 	parent.setProperty!"children"(parent["children"] ~ child);
233 	child.setProperty!"parent"(parent);
234 }
235 
236 /// Says to global layout manager that this widget needs layout update.
237 void invalidateLayout(Widget widget)
238 {
239 	widget.getPropertyAs!("context", GuiContext).invalidateWidgetLayout(widget);
240 }