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.guicontext;
30 
31 import anchovy.gui;
32 import anchovy.gui.interfaces.iwidgetbehavior : IWidgetBehavior;
33 
34 //version = Debug_guicontext;
35 
36 class GuiContext
37 {
38 	alias WidgetCreator = Widget delegate();
39 	alias LayoutCreator = ILayout delegate();
40 	alias BehaviorCreator = IWidgetBehavior delegate();
41 
42 	WidgetCreator[string] widgetFactories;
43 	LayoutCreator[string] layoutFactories;
44 	BehaviorCreator[][string] behaviorFactories;
45 
46 	Widget[] roots;
47 
48 	bool isLayoutValid; // Will be updated in update method
49 
50 	void invalidateWidgetLayout(Widget container)
51 	{
52 		isLayoutValid = false;
53 	}
54 
55 	void doLayout()
56 	{
57 		foreach(root; roots)
58 		{
59 			root.propagateEventChildrenFirst(new MinimizeLayoutEvent);
60 			root.propagateEventParentFirst(new ExpandLayoutEvent);
61 			root.propagateEventParentFirst(new UpdatePositionEvent);
62 		}
63 	}
64 
65 	void update(double deltaTime)
66 	{
67 		if (!isLayoutValid)
68 		{
69 			doLayout();
70 			isLayoutValid = true;
71 
72 			if (pressedWidget !is null)
73 			{
74 				scope moveEvent = new PointerMoveEvent(lastPointerPosition, ivec2(0, 0));
75 				moveEvent.context = this;
76 				updateHovered(moveEvent);
77 			}
78 		}
79 	}
80 
81 protected:
82 	KeyModifiers modifiers;
83 
84 	/// Gui renderer used for drawing all children widgets.
85 	IGuiRenderer	_guiRenderer;
86 
87 	/// Used for timers.
88 	TimerManager	_timerManager;
89 
90 	TemplateManager _templateManager;
91 
92 	/// Current input owner If set, this widget will receive all pointer moved events.
93 	/// See_Also: inputOwnerWidget
94 	Widget		_inputOwnerWidget;
95 
96 	/// Currently dragging widget. Will receive onDrag events.
97 	Widget		_draggingWidget;
98 
99 	/// Last clicked widget. Used for double-click checking.
100 	/// See_Also: lastClickedWidget
101 	Widget		_lastClickedWidget;
102 	
103 	Widget		_pressedWidget;
104 
105 	/// Hovered widget. Widget over which pointer is located.
106 	/// See_Also: hoveredWidget
107 	Widget		_hoveredWidget;
108 
109 	/// Focused widget.
110 	/// 
111 	/// Will receive all key events if input is not grabbed by other widget.
112 	Widget		_focusedWidget;
113 
114 	/// Stores widgets with id property.
115 	Widget[string] ids;
116 
117 	ivec2 lastPointerPosition = ivec2(int.max, int.max);
118 
119 	/// This will be called when widget sets clipboard string.
120 	void delegate(dstring newClipboardString) _setClipboardStringCallback;
121 
122 	/// This will be called when widget requests clipboard string.
123 	dstring delegate() _getClipboardStringCallback;
124 
125 public:
126 
127 	this(IGuiRenderer guiRenderer, TimerManager timerManager, TemplateManager templateManager, GuiSkin skin)
128 	in
129 	{
130 		assert(guiRenderer);
131 		assert(skin);
132 		assert(timerManager);
133 	}
134 	body
135 	{
136 		_guiRenderer = guiRenderer;
137 		_timerManager = timerManager;
138 		_templateManager = templateManager;
139 	}
140 
141 	void addRoot(Widget root)
142 	{
143 		root.setProperty!"size"(cast(ivec2)_guiRenderer.renderer.windowSize);
144 		roots ~= root;
145 	}
146 
147 	Widget createBaseWidget(string type)
148 	{
149 		if (auto factory = type in widgetFactories)
150 		{
151 			return widgetFactories[type]();
152 		}
153 		else
154 		{
155 			return new Widget;
156 		}
157 	}
158 
159 	//---------------------------- Helpers ---------------------------------
160 	import std.conv : parse;
161 	import std.string : munch;
162 
163 	private Variant parseProperty(string name, ref Variant value, Widget widget)
164 	{
165 		switch(name)
166 		{
167 			case "layout":
168 			//writeln("found layout property: ", value.get!string);
169 				if (auto factory = value.get!string in layoutFactories)
170 				{
171 					return Variant((*factory)());
172 				}
173 				writeln("Error: unknown layout '", value.get!string, "' found");
174 				break;
175 			case "minSize", "prefSize":
176 				try
177 				{
178 					string nums = value.get!string;
179 					int w = parse!int(nums);
180 					munch(nums, " \t\n\r");
181 					int h = parse!int(nums);
182 					return Variant(ivec2(w, h));
183 				}
184 				catch (Exception e)
185 				{
186 					writeln("Error parsing "~name~" ", e);
187 				}
188 				return Variant(ivec2(16, 16));
189 				break;
190 			case "id":
191 				string id = value.get!string;
192 				if (id in ids)
193 				{
194 					writeln("Duplicate id found: ", id, ", overriding...");
195 				}
196 				ids[id] = widget;
197 
198 				return value;
199 				break;
200 			default:
201 				return value;
202 		}
203 
204 		return value;
205 	}
206 
207 	Widget createSubwidget(SubwidgetTemplate sub, Widget subwidget, Widget root)
208 	{
209 		//----------------------- Forwarding properties ------------------------
210 		foreach(forwardedProperty; sub.forwardedProperties)
211 		{
212 			version(Debug_guicontext) writeln("forwarding ", forwardedProperty.propertyName ," to ",
213 				forwardedProperty.targetPropertyName);
214 			root[forwardedProperty.propertyName] = subwidget.property(forwardedProperty.targetPropertyName);
215 		}
216 
217 		//------------------------ Assigning properties ------------------------
218 		foreach(propertyKey; sub.properties.byKey)
219 		{
220 			auto value = parseProperty(propertyKey, sub.properties[propertyKey], subwidget);
221 			version(Debug_guicontext) writeln("Assigning properties ", propertyKey," ",value,
222 			 " ", subwidget["name"], " ", subwidget["type"], " ", root["name"], " ", root["type"]);
223 			subwidget[propertyKey] = value;
224 			version(Debug_guicontext) writeln(subwidget[propertyKey]);
225 		}
226 
227 		if (sub.isContainer)
228 		{
229 			root["container"] = subwidget;
230 		}
231 
232 		Variant* name = "name" in sub.properties;
233 		if(name)
234 		{
235 			if (auto subtemplateName = name.peek!string)
236 			{
237 				Widget[string] subwidgets = root["subwidgets"].get!(Widget[string]);
238 				subwidgets[*subtemplateName] = subwidget;
239 				root["subwidgets"] = subwidgets;
240 			}
241 		}
242 		
243 		//------------------------ Creating subwidgets -------------------------
244 		foreach(subtemplate; sub.subwidgets)
245 		{
246 			createSubwidget(subtemplate, createWidget(subtemplate.properties["type"].get!string, subwidget), root);
247 		}
248 
249 		return subwidget;
250 	}
251 
252 	Widget createWidget(string type, Widget parent = null)
253 	{
254 		Widget widget;
255 
256 		//----------------------- Instatiating templates ---------------------------
257 
258 		//writeln("template for ", type, " is ", _templateManager.getTemplate(type));
259 		if (WidgetTemplate templ = _templateManager.getTemplate(type))
260 		{
261 			//----------------------- Base type construction -----------------------
262 
263 			Widget baseWidget;
264 
265 			if (templ.baseType != "widget")
266 			{
267 				baseWidget = createWidget(templ.baseType);
268 			}
269 			else
270 			{
271 				baseWidget = createBaseWidget(type); // Create using factory.
272 			}
273 
274 			baseWidget["type"] = type;
275 			baseWidget["context"] = this; // widget may access context before construction ends.
276 			baseWidget.setProperty!("subwidgets", Widget[string])(null);
277 
278 			//----------------------- Template construction ------------------------
279 			// Recursively creates widgets as stated in template. widget is root of that tree.
280 			widget = createSubwidget(templ.tree, baseWidget, baseWidget);
281 
282 			widget["template"] = templ;
283 		}
284 		else
285 		{
286 			// if there is no template, lets create regular one.
287 			widget = createBaseWidget(type);
288 		}
289 
290 		// default style
291 		if (widget["style"] == Variant(null))
292 		{
293 			widget["style"] = type;
294 		}
295 
296 		widget["context"] = this; // if widget attempts to override context.
297 
298 		// adding parent
299 		if (parent !is null)
300 		{
301 			addChild(parent, widget);
302 		}
303 		else
304 		{
305 			widget["parent"] = null;
306 		}
307 
308 		//----------------------- Attaching behaviors ---------------------------
309 		if (auto factories = type in behaviorFactories)
310 		{
311 			foreach(factory; *factories)
312 			{
313 				factory().attachTo(widget);
314 			}
315 		}
316 
317 		return widget;
318 	}
319 
320 	/// Returns widget found by given id.
321 	Widget getWidgetById(string id)
322 	{
323 		if (auto widget = id in ids)
324 		{
325 			return *widget;
326 		}
327 		else
328 			return null;
329 	}
330 
331 //+-------------------------------------------------------------------------------+
332 //|                                  Properties                                   |
333 //+-------------------------------------------------------------------------------+
334 
335 @property
336 {
337 	IGuiRenderer guiRenderer()
338 	{
339 		return _guiRenderer;
340 	}
341 
342 /// Sets new size for all root widgets.
343 	void size(ivec2 newSize)
344 	{
345 		foreach(widget; roots)
346 		{
347 			widget.setProperty!"size"(newSize);
348 		}
349 	}
350 	
351 	/// Used to get last clicked widget.
352 	Widget lastClickedWidget() @safe
353 	{
354 		return _lastClickedWidget;
355 	}
356 
357 	/// Used to set last clicked widget.
358 	void lastClickedWidget(Widget widget) @safe
359 	{
360 		_lastClickedWidget = widget;
361 	}
362 
363 	/// Used to get current hovered widget.
364 	Widget hoveredWidget() @safe
365 	{
366 		return _hoveredWidget;
367 	}
368 
369 	/// Used to set current hovered widget.
370 	void hoveredWidget(Widget widget) @trusted
371 	{
372 		if (_hoveredWidget !is widget)
373 		{
374 			if (_hoveredWidget !is null)
375 			{
376 				auto event = new PointerLeaveEvent;
377 				event.context = this;
378 				_hoveredWidget.handleEvent(event);
379 			}
380 
381 			if (widget !is null)
382 			{
383 				auto event = new PointerEnterEvent;
384 				event.context = this;
385 				widget.handleEvent(event);
386 			}
387 
388 			_hoveredWidget = widget;
389 		}
390 	}
391 
392 	/// Used to get current focused input owner widget
393 	Widget inputOwnerWidget() @safe pure
394 	{
395 		return _inputOwnerWidget;
396 	}
397 
398 	/// Used to set current focused input owner widget
399 	void inputOwnerWidget(Widget widget) @trusted
400 	{
401 		_inputOwnerWidget = widget;
402 	}
403 	
404 	/// Used to get current focused input owner widget
405 	Widget pressedWidget() @safe pure
406 	{
407 		return _pressedWidget;
408 	}
409 
410 	/// Used to set current focused input owner widget
411 	void pressedWidget(Widget widget) @trusted
412 	{
413 		_pressedWidget = widget;
414 	}
415 
416 	/// Used to get current focused widget
417 	Widget focusedWidget() @safe pure
418 	{
419 		return _focusedWidget;
420 	}
421 
422 	/// Used to set current focused widget
423 	void focusedWidget(Widget widget)
424 	{
425 		if (_focusedWidget !is widget)
426 		{
427 			
428 
429 			if (_focusedWidget !is null)
430 			{
431 				auto event = new FocusLoseEvent;
432 				event.context = this;
433 				_focusedWidget.handleEvent(event);
434 			}
435 
436 			if (widget !is null)
437 			{
438 				auto event = new FocusGainEvent;
439 				event.context = this;
440 				widget.handleEvent(event);
441 				//write("focused ", widget, " ");
442 				//write(widget["name"], " ", widget["type"]);
443 			}
444 			//writeln;
445 
446 			_focusedWidget = widget;
447 		}
448 	}
449 
450 	/// Used to get current clipboard string
451 	string clipboardString()
452 	{
453 		if (_getClipboardStringCallback !is null) 
454 			return to!string(_getClipboardStringCallback());
455 		else
456 			return "";
457 	}
458 
459 	/// Used to set current clipboard string
460 	void clipboardString(string newString)
461 	{
462 		if (_setClipboardStringCallback !is null) 
463 			_setClipboardStringCallback(to!dstring(newString));
464 	}
465 
466 	/// Will be used by window to provide clipboard functionality.
467 	void getClipboardStringCallback(dstring delegate() callback)
468 	{
469 		_getClipboardStringCallback = callback;
470 	}
471 
472 	/// ditto
473 	void setClipboardStringCallback(void delegate(dstring) callback)
474 	{
475 		_setClipboardStringCallback = callback;
476 	}
477 
478 	TimerManager timerManager()
479 	{
480 		return _timerManager;
481 	}
482 }
483 
484 //+-------------------------------------------------------------------------------+
485 //|                                Event handling                                 |
486 //+-------------------------------------------------------------------------------+
487 
488 	static bool containsPointer(Widget widget, ivec2 pointerPosition)
489 	{
490 		return widget.getPropertyAs!("staticRect", Rect).contains(pointerPosition);
491 	}
492 
493 	void draw()
494 	{
495 		auto event = new DrawEvent(_guiRenderer);
496 		event.context = this;
497 
498 		foreach(root; roots)
499 		{
500 			root.propagateEventParentFirst(event);
501 		}
502 	}
503 
504 	/// Handler for key press event.
505 	/// 
506 	/// Must be called by user application.
507 	bool keyPressed(in KeyCode key, KeyModifiers modifiers)
508 	{
509 		if (_focusedWidget !is null)
510 		{
511 			auto event = new KeyPressEvent(key, modifiers);
512 			event.context = this;
513 			_focusedWidget.handleEvent(event);
514 		}
515 
516 		return false;
517 	}
518 
519 	/// Handler for key release event.
520 	/// 
521 	/// Must be called by user application.
522 	bool keyReleased(in KeyCode key, KeyModifiers modifiers)
523 	{
524 		if (_focusedWidget !is null)
525 		{
526 			auto event = new KeyReleaseEvent(key, modifiers);
527 			event.context = this;
528 			_focusedWidget.handleEvent(event);
529 		}
530 
531 		return false;
532 	}
533 
534 	/// Handler for char enter event.
535 	/// 
536 	/// Must be called by user application.
537 	bool charEntered(in dchar chr)
538 	{
539 		if (_focusedWidget !is null)
540 		{
541 			auto event = new CharEnterEvent(chr);
542 			event.context = this;
543 			_focusedWidget.handleEvent(event);
544 		}
545 
546 		return false;
547 	}
548 
549 	/// Handler for pointer press event.
550 	/// 
551 	/// Must be called by user application.
552 	bool pointerPressed(ivec2 pointerPosition, PointerButton button)
553 	{	
554 		lastPointerPosition = pointerPosition;
555 
556 		auto event = new PointerPressEvent(pointerPosition, button);
557 		event.context = this;
558 		
559 		foreach_reverse(rootWidget; roots)
560 		{
561 			Widget[] widgetChain = buildPathToLeaf!(containsPointer)(rootWidget, pointerPosition);
562 
563 			Widget[] eventConsumerChain = propagateEventSinkBubble(widgetChain, event);
564 
565 			if (eventConsumerChain.length > 0)
566 			{
567 				if (eventConsumerChain[$-1].getPropertyAs!("isFocusable", bool))
568 					focusedWidget = eventConsumerChain[$-1];
569 				
570 				pressedWidget = eventConsumerChain[$-1];
571 				
572 				return false;
573 			}
574 		}
575 
576 		focusedWidget = null;
577 
578 		return false;
579 	}
580 
581 	/// Handler for pointer release event.
582 	/// 
583 	/// Must be called by user application.
584 	bool pointerReleased(ivec2 pointerPosition, PointerButton button)
585 	{
586 		lastPointerPosition = pointerPosition;
587 
588 		scope event = new PointerReleaseEvent(pointerPosition, button);
589 		event.context = this;
590 
591 		root_loop:
592 		foreach_reverse(rootWidget; roots)
593 		{
594 			Widget[] widgetChain = buildPathToLeaf!(containsPointer)(rootWidget, pointerPosition);
595 
596 			foreach_reverse(item; widgetChain) // test if pointer over pressed widget.
597 			{
598 				if (item is pressedWidget)
599 				{
600 					Widget[] eventConsumerChain = propagateEventSinkBubble(widgetChain, event);
601 
602 					if (eventConsumerChain.length > 0)
603 					{
604 						if (pressedWidget is eventConsumerChain[$-1])
605 						{
606 							scope clickEvent = new PointerClickEvent(pointerPosition, button);
607 							clickEvent.context = this;
608 
609 							pressedWidget.handleEvent(clickEvent);
610 
611 							lastClickedWidget = pressedWidget;
612 						}
613 					}
614 
615 					pressedWidget = null;
616 
617 					return true;
618 				}
619 			}
620 		}
621 
622 		if (pressedWidget !is null) // no one handled event. Let's pressed widget know that pointer released.
623 		{
624 			pressedWidget.handleEvent(event); // pressed widget will know if pointer unpressed somwhere else.
625 
626 			scope moveEvent = new PointerMoveEvent(pointerPosition, ivec2(0, 0));
627 			moveEvent.context = this;
628 			updateHovered(moveEvent); // So widget knows if pointer released not over it.
629 		}
630 
631 		pressedWidget = null;
632 	
633 		return false;
634 	}
635 
636 
637 	/// Handler for pointer move event.
638 	/// 
639 	/// Must be called by user application.
640 	bool pointerMoved(ivec2 newPointerPosition, ivec2 delta)
641 	{	
642 		lastPointerPosition = newPointerPosition;
643 
644 		scope event = new PointerMoveEvent(newPointerPosition, delta);
645 		event.context = this;
646 		
647 		if (pressedWidget !is null)
648 		{
649 			bool handled = pressedWidget.handleEvent(event);
650 
651 			if (handled)
652 			{
653 				hoveredWidget = pressedWidget;
654 
655 				return true;
656 			}
657 		}
658 		else
659 		{	
660 			if (updateHovered(event))
661 				return true;
662 		}
663 
664 		hoveredWidget = null;
665 		
666 		return false;
667 	}
668 
669 	bool updateHovered(PointerMoveEvent event)
670 	{
671 		foreach_reverse(rootWidget; roots)
672 		{
673 			Widget[] widgetChain = buildPathToLeaf!(containsPointer)(rootWidget, event.pointerPosition);
674 
675 			Widget[] eventConsumerChain = propagateEventSinkBubble(widgetChain, event);
676 
677 			if (eventConsumerChain.length > 0)
678 			{
679 				hoveredWidget = eventConsumerChain[$-1];
680 
681 				return true;
682 			}
683 		}
684 
685 		hoveredWidget = null;
686 
687 		return false;
688 	}
689 }