1 /**
2 Copyright: Copyright (c) 2017-2018 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 
7 module voxelman.gui.components;
8 
9 import voxelman.math;
10 import voxelman.gui;
11 import cbor : ignore;
12 import voxelman.graphics : Color4ub;
13 import datadriven.entityman : EntityManager;
14 
15 void registerComponents(ref EntityManager widgets)
16 {
17 	widgets.registerComponent!WidgetTransform;
18 	widgets.registerComponent!WidgetStyle;
19 	widgets.registerComponent!WidgetName;
20 	widgets.registerComponent!WidgetType;
21 	widgets.registerComponent!WidgetContainer;
22 	widgets.registerComponent!WidgetIsFocusable;
23 	widgets.registerComponent!WidgetEvents;
24 	widgets.registerComponent!hidden;
25 }
26 
27 enum WidgetFlags
28 {
29 	hexpand = 0b0000_0001,
30 	vexpand = 0b0000_0010,
31 }
32 
33 /// Mandatory component
34 @Component("gui.WidgetTransform", Replication.none)
35 struct WidgetTransform
36 {
37 	ivec2 relPos; // set by user/layout
38 	ivec2 size;   // set by user/layout
39 	ivec2 minSize;// set by user
40 	WidgetId parent; // 0 in root widget
41 
42 	uint flags; // flags from WidgetFlags
43 	ivec2 absPos; // updated in layout phase from root to leaves by parent layout
44 	ivec2 measuredSize; // updated in measure phase from leaves to root by widget's layout
45 	ivec2 constrainedSize() { return vector_max(measuredSize, minSize); }
46 	bool hasHexpand() { return !!(flags & WidgetFlags.hexpand); }
47 	bool hasVexpand() { return !!(flags & WidgetFlags.vexpand); }
48 	void hexpand(bool val) { flags = set_flag(flags, val, WidgetFlags.hexpand); }
49 	void vexpand(bool val) { flags = set_flag(flags, val, WidgetFlags.vexpand); }
50 	void hvexpand(bool val) { hexpand(val); vexpand(val); }
51 
52 	bool contains(ivec2 pos) { return irect(absPos, size).contains(pos); }
53 }
54 
55 /// Convenience setters
56 WidgetProxy minSize(WidgetProxy widget, ivec2 minSize) { widget.getOrCreate!WidgetTransform.minSize = minSize; return widget; } /// ditto
57 WidgetProxy minSize(WidgetProxy widget, int w, int h) { widget.getOrCreate!WidgetTransform.minSize = ivec2(w, h); return widget; } /// ditto
58 WidgetProxy size(WidgetProxy widget, int w, int h) { widget.getOrCreate!WidgetTransform.size = ivec2(w, h); return widget; } /// ditto
59 WidgetProxy pos(WidgetProxy widget, ivec2 p) { widget.getOrCreate!WidgetTransform.relPos = p; return widget; } /// ditto
60 WidgetProxy pos(WidgetProxy widget, int x, int y) { widget.getOrCreate!WidgetTransform.relPos = ivec2(x, y); return widget; } /// ditto
61 WidgetProxy hexpand(WidgetProxy widget) { widget.getOrCreate!WidgetTransform.hexpand = true; return widget; } /// ditto
62 WidgetProxy vexpand(WidgetProxy widget) { widget.getOrCreate!WidgetTransform.vexpand = true; return widget; } /// ditto
63 WidgetProxy hvexpand(WidgetProxy widget) { widget.getOrCreate!WidgetTransform.hvexpand = true; return widget; } /// ditto
64 WidgetProxy measuredSize(WidgetProxy widget, ivec2 ms) { widget.getOrCreate!WidgetTransform.measuredSize = ms; return widget; } /// ditto
65 WidgetProxy measuredSize(WidgetProxy widget, int w, int h) { widget.getOrCreate!WidgetTransform.measuredSize = ivec2(w, h); return widget; } /// ditto
66 
67 bool hasHexpand(WidgetProxy widget) { return widget.getOrCreate!WidgetTransform.hasHexpand; } /// ditto
68 bool hasVexpand(WidgetProxy widget) { return widget.getOrCreate!WidgetTransform.hasVexpand; } /// ditto
69 
70 @Component("gui.hidden", Replication.none)
71 struct hidden {}
72 
73 @Component("gui.WidgetStyle", Replication.none)
74 struct WidgetStyle
75 {
76 	Color4ub color;
77 	Color4ub borderColor;
78 }
79 
80 @Component("gui.WidgetName", Replication.none)
81 struct WidgetName
82 {
83 	string name;
84 }
85 
86 @Component("gui.WidgetType", Replication.none)
87 struct WidgetType
88 {
89 	string name;
90 }
91 
92 string widgetType(WidgetProxy widget) {
93 	if (auto type = widget.get!WidgetType) return type.name;
94 	return "Widget";
95 }
96 
97 string widgetType(GuiContext ctx, WidgetId wid) {
98 	if (auto type = ctx.widgets.get!WidgetType(wid)) return type.name;
99 	return "Widget";
100 }
101 
102 @Component("gui.WidgetContainer", Replication.none)
103 struct WidgetContainer
104 {
105 	WidgetId[] children;
106 	void put(WidgetId wid) {
107 		children ~= wid;
108 	}
109 	void removeChild(WidgetId wid)
110 	{
111 		import std.algorithm : remove, countUntil;
112 		auto index = countUntil(children, wid);
113 		if (index != -1) children = remove(children, index);
114 	}
115 	void bringToFront(WidgetId wid) {
116 		foreach(index, child; children)
117 		{
118 			if (child == wid)
119 			{
120 				moveItemToEnd(index, children);
121 				return;
122 			}
123 		}
124 	}
125 }
126 
127 ///
128 ChildrenRange children(WidgetProxy w) {
129 	if (auto container = w.ctx.widgets.get!WidgetContainer(w.wid)) return ChildrenRange(w.ctx, container.children);
130 	return ChildrenRange(w.ctx, null);
131 }
132 
133 void moveItemToEnd(T)(size_t index, T[] array)
134 {
135 	if (index+1 < array.length) // dont use length-1 due to size_t underflow
136 	{
137 		T item = array[index];
138 		foreach(i; index..array.length-1)
139 		{
140 			array[i] = array[i+1];
141 		}
142 		array[$-1] = item;
143 	}
144 }
145 
146 unittest
147 {
148 	void test(T)(size_t index, T[] array, T[] expected) {
149 		auto initial = array.dup;
150 		moveItemToEnd(index, array);
151 		import std..string : format;
152 		assert(array == expected, format(
153 			"for (%s, %s) expected %s, got %s",
154 			index, initial, expected, array));
155 	}
156 	test(0, cast(int[])[], cast(int[])[]);
157 	test(0, [3], [3]);
158 	test(0, [3,2], [2,3]);
159 	test(0, [3,2,1], [2,1,3]);
160 	test(1, [3], [3]);
161 	test(1, [3,2], [3,2]);
162 	test(2, [3,2,1], [3,2,1]);
163 }
164 
165 size_t numberOfChildren(WidgetProxy widget)
166 {
167 	if (auto container = widget.get!WidgetContainer) return container.children.length;
168 	return 0;
169 }
170 
171 size_t numberOfChildren(GuiContext ctx, WidgetId wid)
172 {
173 	if (auto container = ctx.widgets.get!WidgetContainer(wid)) return container.children.length;
174 	return 0;
175 }
176 
177 void bringToFront(WidgetProxy widget)
178 {
179 	auto parentId = widget.get!WidgetTransform.parent;
180 	if (auto container = widget.ctx.get!WidgetContainer(parentId))
181 		container.bringToFront(widget.wid);
182 }
183 
184 @Component("gui.WidgetIsFocusable", Replication.none)
185 struct WidgetIsFocusable {}
186 
187 @Component("gui.WidgetEvents", Replication.none)
188 struct WidgetEvents
189 {
190 	import std.stdio;
191 	this(Handlers...)(Handlers handlers)
192 	{
193 		addEventHandlers(handlers);
194 	}
195 	private alias EventHandler = void delegate(WidgetProxy widget, ref void* event);
196 
197 	@ignore EventHandler[][TypeInfo] eventHandlers;
198 
199 	void addEventHandlers(Handlers...)(Handlers handlers)
200 	{
201 		foreach(h; handlers) addEventHandler(h);
202 	}
203 
204 	private enum void* FUNCTION_CONTEXT_VALUE = cast(void*)42;
205 	private static struct DelegatePayload
206 	{
207 		void* contextPtr;
208 		void* funcPtr;
209 	}
210 
211 	void addEventHandler(EventType)(void delegate(WidgetProxy, ref EventType) handler)
212 	{
213 		eventHandlers[typeid(EventType)] ~= cast(EventHandler)handler;
214 	}
215 
216 	void addEventHandler(EventType)(void function(WidgetProxy, ref EventType) handler)
217 	{
218 		// We use non-null value because null if a valid context pointer in delegates
219 		DelegatePayload fakeDelegate = {FUNCTION_CONTEXT_VALUE, handler};
220 		eventHandlers[typeid(EventType)] ~= *cast(EventHandler*)&fakeDelegate;
221 	}
222 
223 	void replaceEventHandler(EventType)(void delegate(WidgetProxy, ref EventType) handler)
224 	{
225 		eventHandlers[typeid(EventType)] = [cast(EventHandler)handler];
226 	}
227 
228 	void replaceEventHandler(EventType)(void function(WidgetProxy, ref EventType) handler)
229 	{
230 		// We use non-null value because null if a valid context pointer in delegates
231 		DelegatePayload fakeDelegate = {FUNCTION_CONTEXT_VALUE, handler};
232 		eventHandlers[typeid(EventType)] = [*cast(EventHandler*)&fakeDelegate];
233 	}
234 
235 	void removeEventHandlers(T)()
236 	{
237 		eventHandlers.remove(typeid(T));
238 	}
239 
240 	/// Returns true if any handlers were called
241 	/// This handler will be called by Gui twice, before and after visiting its children.
242 	/// In first case sinking flag will be true;
243 	bool postEvent(Event)(WidgetProxy widget, auto ref Event event)
244 	{
245 		if (auto handlers = typeid(Event) in eventHandlers)
246 		{
247 			foreach(handler; *handlers)
248 			{
249 				auto payload = *cast(DelegatePayload*)&handler;
250 				if (payload.contextPtr == FUNCTION_CONTEXT_VALUE)
251 					(cast(void function(WidgetProxy, ref Event))payload.funcPtr)(widget, event);
252 				else
253 					(cast(void delegate(WidgetProxy, ref Event))handler)(widget, event);
254 			}
255 			return true;
256 		}
257 		else
258 			return false;
259 	}
260 }