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.guicontext;
8 
9 import std.stdio;
10 import std.typecons : Flag, Yes, No;
11 import datadriven;
12 import voxelman.graphics;
13 import voxelman.gui;
14 import voxelman.platform.input;
15 import voxelman.math;
16 import voxelman.text.linebuffer;
17 public import voxelman.platform.cursoricon : CursorIcon;
18 import voxelman.gui.textedit.texteditorview;
19 import voxelman.gui.textedit.lineedit;
20 
21 struct GuiState
22 {
23 	WidgetId draggedWidget;    /// Will receive onDrag events
24 	WidgetId focusedWidget;     /// Will receive all key events if input is not grabbed by other widget
25 	WidgetId hoveredWidget;     /// Widget over which pointer is located
26 	WidgetId inputOwnerWidget;  /// If set, this widget will receive all pointer movement events
27 	WidgetId lastClickedWidget; /// Used for double-click checking. Is set before click event distribution
28 	WidgetId pressedWidget;
29 
30 	ivec2 canvasSize;
31 	ivec2 prevPointerPos = ivec2(int.max, int.max);
32 	ivec2 pointerPressPos = ivec2(int.max, int.max);
33 	ivec2 curPointerPos;
34 
35 	/// filled with curPointerPos - draggedWidget.absPos at the moment of press
36 	ivec2 draggedWidgetOffset;
37 
38 	/// Icon is reset after widget leave event and before widget enter event.
39 	/// If widget wants to change icon, it must set cursorIcon in PointerEnterEvent handler.
40 	CursorIcon cursorIcon;
41 
42 	string delegate() getClipboard;
43 	void delegate(string) setClipboard;
44 }
45 
46 struct ImplicitGuiStyle
47 {
48 	import voxelman.container.chunkedbuffer;
49 	FontRef defaultFont;
50 	ChunkedBuffer!(FontRef, 16) fontStack;
51 
52 	FontRef font() {
53 		if (fontStack.length) return fontStack.top;
54 		return defaultFont;
55 	}
56 	void pushFont(FontRef font) { fontStack.push(font); }
57 	void popFont() { fontStack.pop(); }
58 
59 	ChunkedBuffer!(Color4ub, 16) colorStack;
60 	Color4ub defaultColor = Colors.black;
61 	Color4ub color() {
62 		if (colorStack.length) return colorStack.top;
63 		return defaultColor;
64 	}
65 	void pushColor(Color4ub color) { colorStack.push(color); }
66 	void popColor() { colorStack.pop(); }
67 
68 	SpriteRef iconPlaceholder;
69 	SpriteRef[string] iconMap;
70 
71 	SpriteRef icon(string iconId) { return iconMap.get(iconId, iconPlaceholder); }
72 }
73 
74 class GuiContext
75 {
76 	EntityIdManager widgetIds;
77 	EntityManager widgets;
78 	WidgetId[string] nameToId;
79 
80 	/// Roots are auto-expanded on hvexpand to state.canvasSize
81 	WidgetId[] roots;
82 
83 	GuiState state;
84 	ImplicitGuiStyle style;
85 	LineBuffer* debugText;
86 
87 	this(LineBuffer* debugText)
88 	{
89 		widgets.eidMan = &widgetIds;
90 		voxelman.gui.widgets.registerComponents(widgets);
91 		voxelman.gui.components.registerComponents(widgets);
92 		voxelman.gui.textedit.texteditorview.registerComponents(widgets);
93 		widgets.registerComponent!LineEditData;
94 
95 		roots ~= createWidget(WidgetType("root")).hvexpand;
96 		roots ~= createWidget(WidgetType("windows")).hvexpand;
97 		this.debugText = debugText;
98 	}
99 
100 	WidgetProxy getRoot(size_t rootIndex) { return WidgetProxy(roots[rootIndex], this); }
101 	// context menus, drop-down lists, tooltips
102 	WidgetProxy createOverlay() {
103 		roots ~= createWidget(WidgetType("overlay")).hvexpand;
104 		return WidgetProxy(roots[$-1], this);
105 	}
106 
107 	ChildrenRange getRoots() { return ChildrenRange(this, roots); }
108 
109 	// layer for dropdown items, context menus and hints
110 
111 	// SET, GET, HAS proxies
112 	void set(Components...)(WidgetId wid, Components components) { widgets.set(wid, components); }
113 	C* get(C)(WidgetId wid) { return widgets.get!C(wid); }
114 	C* getOrCreate(C)(WidgetId wid, C defVal = C.init) { return widgets.getOrCreate!C(wid, defVal); }
115 	bool has(C)(WidgetId wid) { return widgets.has!C(wid); }
116 	void remove(C)(WidgetId wid) { widgets.remove!C(wid); }
117 
118 	// WIDGET METHODS
119 
120 	/// returns 0 if not found
121 	WidgetId getByName(string name)
122 	{
123 		return nameToId.get(name, WidgetId(0));
124 	}
125 
126 	/// Pass string as first parameter to set name
127 	/// Pass WidgetId as first parameter, or after string to set parent
128 	/// createWidget([string name,] [WidgetId parent,] Component... components)
129 	WidgetProxy createWidget(Components...)(Components components)
130 		if (components.length > 0 &&
131 			!is(Components[0] == string) &&
132 			!is(Components[0] == WidgetProxy) &&
133 			!is(Components[0] == WidgetId))
134 	{
135 		auto wid = widgetIds.nextEntityId();
136 		widgets.set(wid, components);
137 		return WidgetProxy(wid, this);
138 	}
139 
140 	/// ditto
141 	WidgetProxy createWidget(Components...)(string name, Components components)
142 	{
143 		auto wid = widgetIds.nextEntityId();
144 		nameToId[name] = wid;
145 		widgets.set(wid, WidgetName(name), components);
146 		return WidgetProxy(wid, this);
147 	}
148 
149 	/// ditto
150 	WidgetProxy createWidget(Components...)(WidgetId parent, Components components)
151 	{
152 		auto wid = widgetIds.nextEntityId();
153 		widgets.set(wid, components);
154 		addChild(parent, wid);
155 		return WidgetProxy(wid, this);
156 	}
157 
158 	/// ditto
159 	WidgetProxy createWidget(Components...)(string name, WidgetId parent, Components components)
160 	{
161 		auto wid = widgetIds.nextEntityId();
162 		nameToId[name] = wid;
163 		widgets.set(wid, WidgetName(name), components);
164 		addChild(parent, wid);
165 		return WidgetProxy(wid, this);
166 	}
167 
168 	void removeWidget(WidgetId wid)
169 	{
170 		auto tr = widgets.getOrCreate!WidgetTransform(wid);
171 
172 		foreach(widget; visitTreeChildrenFirstAll(wid))
173 			widgets.remove(wid);
174 
175 		if (tr.parent)
176 		{
177 			auto container = widgets.get!WidgetContainer(tr.parent);
178 			if (container) container.removeChild(wid);
179 		}
180 		else // check if root
181 		{
182 			import std.algorithm : remove, countUntil;
183 			auto index = countUntil(roots, wid);
184 			if (index != -1) roots = remove(roots, index);
185 		}
186 	}
187 
188 	/// Call to set parent after components are set
189 	/// because it will create WidgetTransform component for child
190 	/// which will be overwritten by set call if Components list
191 	/// contains WidgetTransform components too.
192 	void addChild(WidgetId parent, WidgetId child)
193 	{
194 		widgets.getOrCreate!WidgetContainer(parent).put(child);
195 		widgets.getOrCreate!WidgetTransform(child).parent = parent;
196 	}
197 
198 	WidgetId[] widgetChildren(WidgetId wid)
199 	{
200 		if (auto container = widgets.get!WidgetContainer(wid)) return container.children;
201 		return null;
202 	}
203 
204 	static struct WidgetTreeVisitor(bool rootFirst, bool onlyVisible)
205 	{
206 		WidgetId root;
207 		GuiContext ctx;
208 		int opApply(scope int delegate(WidgetProxy) del)
209 		{
210 			int visitSubtree(WidgetId root)
211 			{
212 				static if (rootFirst) {
213 					if (auto ret = del(WidgetProxy(root, ctx))) return ret;
214 				}
215 
216 				foreach(child; ctx.widgetChildren(root))
217 				{
218 					static if (onlyVisible) {
219 						if (ctx.widgets.has!hidden(child)) continue;
220 					}
221 					if (auto ret = visitSubtree(child))
222 						return ret;
223 				}
224 
225 				static if (!rootFirst) {
226 					if (auto ret = del(WidgetProxy(root, ctx))) return ret;
227 				}
228 
229 				return 0;
230 			}
231 
232 			return visitSubtree(root);
233 		}
234 	}
235 
236 	auto visitTreeRootFirstVisible(WidgetId root) { return WidgetTreeVisitor!(true, true)(root, this); }
237 	auto visitTreeChildrenFirstVisible(WidgetId root) { return WidgetTreeVisitor!(false, true)(root, this); }
238 	auto visitTreeRootFirstAll(WidgetId root) { return WidgetTreeVisitor!(true, false)(root, this); }
239 	auto visitTreeChildrenFirstAll(WidgetId root) { return WidgetTreeVisitor!(false, false)(root, this); }
240 
241 	bool postEvent(Event)(WidgetId wid, auto ref Event event)
242 	{
243 		if (auto events = widgets.get!WidgetEvents(wid))
244 			return events.postEvent(WidgetProxy(wid, this), event);
245 		return false;
246 	}
247 
248 	bool postEvent(Event)(WidgetProxy widget, auto ref Event event)
249 	{
250 		if (auto events = widget.get!WidgetEvents)
251 			return events.postEvent(widget, event);
252 		return false;
253 	}
254 
255 	static bool containsPointer(WidgetId widget, GuiContext context, ivec2 pointerPos)
256 	{
257 		auto transform = context.widgets.getOrCreate!WidgetTransform(widget);
258 		return transform.contains(pointerPos);
259 	}
260 
261 
262 	// EVENT HANDLERS
263 
264 	void onScroll(dvec2 delta)
265 	{
266 		auto event = ScrollEvent(vec2(-delta));
267 
268 		foreach_reverse(root; roots)
269 		{
270 			WidgetId[] path = buildPathToLeaf!(containsPointer)(this, root, this, state.curPointerPos);
271 			WidgetId[] eventConsumerChain = propagateEventSinkBubble(this, path, event, OnHandle.StopTraversing);
272 		}
273 		updateHovered(state.curPointerPos);
274 	}
275 
276 	void onKeyPress(KeyCode key, uint modifiers)
277 	{
278 		if (focusedWidget)
279 		{
280 			auto event = KeyPressEvent(key, modifiers);
281 			postEvent(focusedWidget, event);
282 		}
283 	}
284 
285 	void onKeyRelease(KeyCode key, uint modifiers)
286 	{
287 		if (focusedWidget)
288 		{
289 			auto event = KeyReleaseEvent(key, modifiers);
290 			postEvent(focusedWidget, event);
291 		}
292 	}
293 
294 	void onCharEnter(dchar character)
295 	{
296 		if (focusedWidget)
297 		{
298 			auto event = CharEnterEvent(character);
299 			postEvent(focusedWidget, event);
300 		}
301 	}
302 
303 	void pointerPressed(PointerButton button, uint modifiers)
304 	{
305 		auto event = PointerPressEvent(state.curPointerPos, button, modifiers);
306 		state.pointerPressPos = state.curPointerPos;
307 
308 		foreach_reverse(root; roots)
309 		{
310 			WidgetId[] path = buildPathToLeaf!(containsPointer)(this, root, this, state.curPointerPos);
311 			WidgetId[] eventConsumerChain = propagateEventSinkBubble(this, path, event, OnHandle.StopTraversing);
312 
313 			if (eventConsumerChain.length > 0)
314 			{
315 				WidgetId consumer = eventConsumerChain[$-1];
316 				if (widgets.has!WidgetIsFocusable(consumer))
317 					focusedWidget = consumer;
318 
319 				if (event.beginDrag) beginDrag(consumer);
320 
321 				pressedWidget = consumer;
322 				return;
323 			}
324 		}
325 
326 		focusedWidget = WidgetId(0);
327 	}
328 
329 	void pointerReleased(PointerButton button, uint modifiers)
330 	{
331 		auto event = PointerReleaseEvent(state.curPointerPos, button, modifiers);
332 		scope(exit) pressedWidget = WidgetId(0);
333 
334 		if (draggedWidget)
335 		{
336 			endDrag();
337 			return;
338 		}
339 
340 		foreach_reverse(root; roots)
341 		{
342 			WidgetId[] path = buildPathToLeaf!(containsPointer)(this, root, this, state.curPointerPos);
343 
344 			foreach_reverse(item; path) // test if pointer over pressed widget.
345 			{
346 				if (item == pressedWidget)
347 				{
348 					WidgetId[] eventConsumerChain = propagateEventSinkBubble(this, path, event, OnHandle.StopTraversing);
349 
350 					if (eventConsumerChain.length > 0)
351 					{
352 						if (pressedWidget == eventConsumerChain[$-1])
353 						{
354 							auto clickEvent = PointerClickEvent(state.curPointerPos, button);
355 							lastClickedWidget = pressedWidget;
356 							postEvent(pressedWidget, clickEvent);
357 						}
358 					}
359 
360 					return;
361 				}
362 			}
363 		}
364 
365 		if (pressedWidget) // no one handled event. Let's pressed widget know that pointer was released.
366 		{
367 			postEvent(pressedWidget, event); // pressed widget will know if pointer was unpressed somewhere else.
368 			updateHovered(state.curPointerPos); // So widget knows if pointer released not over it.
369 		}
370 	}
371 
372 	void pointerMoved(ivec2 newPointerPos)
373 	{
374 		if (newPointerPos == state.curPointerPos) return;
375 
376 		state.prevPointerPos = state.curPointerPos;
377 		state.curPointerPos = newPointerPos;
378 		ivec2 delta = state.curPointerPos - state.prevPointerPos;
379 
380 		auto event = PointerMoveEvent(newPointerPos, delta);
381 
382 		if (draggedWidget)
383 		{
384 			doDrag(delta);
385 		}
386 		else if (pressedWidget)
387 		{
388 			if (containsPointer(pressedWidget, this, state.curPointerPos))
389 			{
390 				postEvent(pressedWidget, event);
391 				if (event.handled)
392 				{
393 					hoveredWidget = pressedWidget;
394 					return;
395 				}
396 			}
397 		}
398 		else
399 		{
400 			if (updateHovered(newPointerPos, delta)) return;
401 		}
402 
403 		hoveredWidget = WidgetId(0);
404 	}
405 
406 	private bool updateHovered(ivec2 pointerPos, ivec2 delta = ivec2(0,0))
407 	{
408 		foreach_reverse(root; roots)
409 		{
410 			WidgetId[] path = buildPathToLeaf!(containsPointer)(this, root, this, pointerPos);
411 			auto event = PointerMoveEvent(pointerPos, delta);
412 
413 			foreach_reverse(widget; path)
414 			{
415 				postEvent(widget, event);
416 				if (event.handled)
417 				{
418 					hoveredWidget = widget;
419 					return true;
420 				}
421 			}
422 		}
423 
424 		hoveredWidget = WidgetId(0);
425 		return false;
426 	}
427 
428 	void update(double deltaTime, RenderQueue renderQueue)
429 	{
430 		foreach(root; roots)
431 			propagateEventSinkBubbleTree!(No.CheckHidden)(this, root, GuiUpdateEvent(deltaTime));
432 
433 		updateLayout();
434 
435 		auto drawEvent = DrawEvent(renderQueue);
436 		foreach(root; roots)
437 			propagateEventSinkBubbleTree(this, root, drawEvent);
438 	}
439 
440 	private void updateLayout()
441 	{
442 		foreach(root; roots)
443 		{
444 			MeasureEvent measureEvent;
445 			foreach(widget; visitTreeChildrenFirstVisible(root))
446 			{
447 				postEvent(widget, measureEvent);
448 				auto childTransform = widgets.getOrCreate!WidgetTransform(widget);
449 				childTransform.size = childTransform.constrainedSize;
450 			}
451 
452 			auto trans = widgets.getOrCreate!WidgetTransform(root);
453 			trans.size = trans.constrainedSize;
454 			if (trans.hasHexpand) trans.size.x = state.canvasSize.x;
455 			if (trans.hasVexpand) trans.size.y = state.canvasSize.y;
456 			trans.absPos = trans.relPos;
457 
458 			LayoutEvent layoutEvent;
459 			foreach(widget; visitTreeRootFirstVisible(root))
460 			{
461 				postEvent(widget, layoutEvent);
462 				auto parentTransform = widget.getOrCreate!WidgetTransform;
463 				foreach(child; widget.children)
464 				{
465 					auto childTransform = child.getOrCreate!WidgetTransform;
466 					childTransform.absPos = parentTransform.absPos + childTransform.relPos;
467 					childTransform.size.x = max(childTransform.size.x, 0);
468 					childTransform.size.y = max(childTransform.size.y, 0);
469 				}
470 			}
471 		}
472 	}
473 
474 	// STATE
475 
476 	string clipboard()
477 	{
478 		return state.getClipboard();
479 	}
480 
481 	void clipboard(S)(S str)
482 	{
483 		import std.array : array;
484 		import std.utf : byChar;
485 		state.setClipboard(cast(string)str.byChar.array);
486 	}
487 
488 	void cursorIcon(CursorIcon icon) { state.cursorIcon = icon; }
489 
490 	void beginDrag(WidgetId wid)
491 	{
492 		draggedWidget = wid;
493 		state.draggedWidgetOffset = state.curPointerPos - get!WidgetTransform(wid).absPos;
494 		postEvent(wid, DragBeginEvent());
495 	}
496 
497 	void doDrag(ivec2 delta)
498 	{
499 		assert(draggedWidget);
500 		postEvent(draggedWidget, DragEvent(delta));
501 	}
502 
503 	void endDrag()
504 	{
505 		assert(draggedWidget);
506 		postEvent(draggedWidget, DragEndEvent());
507 		draggedWidget = WidgetId(0);
508 		updateHovered(state.curPointerPos);
509 	}
510 
511 	WidgetId draggedWidget() { return state.draggedWidget; }
512 	void draggedWidget(WidgetId wid) { state.draggedWidget = wid; updateHovered(state.curPointerPos); }
513 
514 	WidgetId focusedWidget() { return state.focusedWidget; }
515 	void focusedWidget(WidgetId wid)
516 	{
517 		if (state.focusedWidget != wid)
518 		{
519 			if (state.focusedWidget) postEvent(state.focusedWidget, FocusLoseEvent());
520 			if (wid) postEvent(wid, FocusGainEvent());
521 			state.focusedWidget = wid;
522 		}
523 	}
524 
525 	WidgetId hoveredWidget() { return state.hoveredWidget; }
526 	void hoveredWidget(WidgetId wid) @trusted
527 	{
528 		if (state.hoveredWidget != wid)
529 		{
530 			if (state.hoveredWidget) postEvent(state.hoveredWidget, PointerLeaveEvent());
531 			cursorIcon = CursorIcon.arrow;
532 			if (wid) postEvent(wid, PointerEnterEvent());
533 			state.hoveredWidget = wid;
534 		}
535 	}
536 
537 	WidgetId inputOwnerWidget() { return state.inputOwnerWidget; }
538 	void inputOwnerWidget(WidgetId wid) { state.inputOwnerWidget = wid; }
539 
540 	WidgetId lastClickedWidget() { return state.lastClickedWidget; }
541 	void lastClickedWidget(WidgetId wid) { state.lastClickedWidget = wid; }
542 
543 	WidgetId pressedWidget() { return state.pressedWidget; }
544 	void pressedWidget(WidgetId wid) { state.pressedWidget = wid; updateHovered(state.curPointerPos); }
545 }