1 /**
2 Copyright: Copyright (c) 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.eventdispatcher;
8 
9 import anchovy.gui;
10 
11 struct EventDispatcher
12 {
13 private:
14 	GuiContext _context;
15 
16 	ivec2 _lastPointerPosition = ivec2(int.max, int.max);
17 
18 	/// Current input owner If set, this widget will receive all pointer moved events.
19 	/// See_Also: inputOwnerWidget
20 	Widget		_inputOwnerWidget;
21 
22 	/// Currently dragging widget. Will receive onDrag events.
23 	Widget		_draggingWidget;
24 
25 	/// Last clicked widget. Used for double-click checking.
26 	/// See_Also: lastClickedWidget
27 	Widget		_lastClickedWidget;
28 
29 	/// Currently pressed widget
30 	Widget		_pressedWidget;
31 
32 	/// Hovered widget. Widget over which pointer is located.
33 	/// See_Also: hoveredWidget
34 	Widget		_hoveredWidget;
35 
36 	/// Focused widget.
37 	///
38 	/// Will receive all key events if input is not grabbed by other widget.
39 	Widget		_focusedWidget;
40 
41 	bool isLayoutValid; // Will be updated in update method
42 
43 public:
44 
45 	@disable this();
46 
47 	this(GuiContext context)
48 	{
49 		_context = context;
50 	}
51 
52 @property
53 {
54 	ivec2 lastPointerPosition()
55 	{
56 		return _lastPointerPosition;
57 	}
58 	/// Used to get last clicked widget.
59 	Widget lastClickedWidget() @safe
60 	{
61 		return _lastClickedWidget;
62 	}
63 
64 	/// Used to set last clicked widget.
65 	void lastClickedWidget(Widget widget) @safe
66 	{
67 		_lastClickedWidget = widget;
68 	}
69 
70 	/// Used to get current hovered widget.
71 	Widget hoveredWidget() @safe
72 	{
73 		return _hoveredWidget;
74 	}
75 
76 	/// Used to set current hovered widget.
77 	void hoveredWidget(Widget widget) @trusted
78 	{
79 		if (_hoveredWidget !is widget)
80 		{
81 			if (_hoveredWidget !is null)
82 			{
83 				auto event = new PointerLeaveEvent;
84 				event.context = _context;
85 				_hoveredWidget.handleEvent(event);
86 			}
87 
88 			if (widget !is null)
89 			{
90 				auto event = new PointerEnterEvent;
91 				event.context = _context;
92 				widget.handleEvent(event);
93 			}
94 
95 			_context.tooltipManager.onWidgetHovered(widget);
96 			_hoveredWidget = widget;
97 		}
98 	}
99 
100 	/// Used to get current focused input owner widget
101 	Widget inputOwnerWidget() @safe pure
102 	{
103 		return _inputOwnerWidget;
104 	}
105 
106 	/// Used to set current focused input owner widget
107 	void inputOwnerWidget(Widget widget) @trusted
108 	{
109 		_inputOwnerWidget = widget;
110 	}
111 
112 	/// Used to get current focused input owner widget
113 	Widget pressedWidget() @safe pure
114 	{
115 		return _pressedWidget;
116 	}
117 
118 	/// Used to set current focused input owner widget
119 	void pressedWidget(Widget widget) @trusted
120 	{
121 		_pressedWidget = widget;
122 	}
123 
124 	/// Used to get current focused widget
125 	Widget focusedWidget() @safe pure
126 	{
127 		return _focusedWidget;
128 	}
129 
130 	/// Used to set current focused widget
131 	void focusedWidget(Widget widget)
132 	{
133 		if (_focusedWidget !is widget)
134 		{
135 			if (_focusedWidget !is null)
136 			{
137 				auto event = new FocusLoseEvent;
138 				event.context = _context;
139 				_focusedWidget.handleEvent(event);
140 			}
141 
142 			if (widget !is null)
143 			{
144 				auto event = new FocusGainEvent;
145 				event.context = _context;
146 				widget.handleEvent(event);
147 			}
148 
149 			_focusedWidget = widget;
150 		}
151 	}
152 }
153 
154 	//+-------------------------------------------------------------------------------+
155 	//|                                Event handling                                 |
156 	//+-------------------------------------------------------------------------------+
157 
158 	static bool containsPointer(Widget widget, ivec2 pointerPosition)
159 	{
160 		return widget.getPropertyAs!("staticRect", Rect).contains(pointerPosition);
161 	}
162 
163 	void invalidateWidgetLayout(Widget container)
164 	{
165 		isLayoutValid = false;
166 	}
167 
168 	void update(double deltaTime)
169 	{
170 		if (!isLayoutValid)
171 		{
172 			doLayout();
173 			isLayoutValid = true;
174 
175 			if (pressedWidget is null)
176 			{
177 				scope moveEvent = new PointerMoveEvent(lastPointerPosition, ivec2(0, 0));
178 				moveEvent.context = _context;
179 				updateHovered(moveEvent);
180 			}
181 		}
182 	}
183 
184 	void doLayout()
185 	{
186 		foreach(root; _context.roots)
187 		{
188 			root.propagateEventChildrenFirst(new MinimizeLayoutEvent);
189 			root.propagateEventParentFirst(new ExpandLayoutEvent);
190 			root.propagateEventParentFirst(new UpdatePositionEvent);
191 		}
192 	}
193 
194 	void draw()
195 	{
196 		auto event = new DrawEvent(_context.guiRenderer);
197 		event.context = _context;
198 
199 		foreach(root; _context.roots)
200 		{
201 			root.propagateEventSinkBubbleTree(event);
202 		}
203 	}
204 
205 	/// Handler for key press event.
206 	///
207 	/// Must be called by user application.
208 	bool keyPressed(in KeyCode key, uint modifiers)
209 	{
210 		if (_focusedWidget !is null)
211 		{
212 			auto event = new KeyPressEvent(key, modifiers);
213 			event.context = _context;
214 			_focusedWidget.handleEvent(event);
215 		}
216 
217 		return false;
218 	}
219 
220 	/// Handler for key release event.
221 	///
222 	/// Must be called by user application.
223 	bool keyReleased(in KeyCode key, uint modifiers)
224 	{
225 		if (_focusedWidget !is null)
226 		{
227 			auto event = new KeyReleaseEvent(key, modifiers);
228 			event.context = _context;
229 			_focusedWidget.handleEvent(event);
230 		}
231 
232 		return false;
233 	}
234 
235 	/// Handler for char enter event.
236 	///
237 	/// Must be called by user application.
238 	bool charEntered(in dchar chr)
239 	{
240 		if (_focusedWidget !is null)
241 		{
242 			auto event = new CharEnterEvent(chr);
243 			event.context = _context;
244 			_focusedWidget.handleEvent(event);
245 		}
246 
247 		return false;
248 	}
249 
250 	/// Handler for pointer press event.
251 	///
252 	/// Must be called by user application.
253 	bool pointerPressed(ivec2 pointerPosition, PointerButton button)
254 	{
255 		_lastPointerPosition = pointerPosition;
256 
257 		auto event = new PointerPressEvent(pointerPosition, button);
258 		event.context = _context;
259 
260 		foreach_reverse(rootWidget; _context.roots)
261 		{
262 			Widget[] widgetChain = buildPathToLeaf!(containsPointer)(rootWidget, pointerPosition);
263 
264 			Widget[] eventConsumerChain = propagateEventSinkBubble(widgetChain, event);
265 
266 			if (eventConsumerChain.length > 0)
267 			{
268 				if (eventConsumerChain[$-1].getPropertyAs!("isFocusable", bool))
269 					focusedWidget = eventConsumerChain[$-1];
270 
271 				pressedWidget = eventConsumerChain[$-1];
272 
273 				return false;
274 			}
275 		}
276 
277 		focusedWidget = null;
278 
279 		return false;
280 	}
281 
282 	/// Handler for pointer release event.
283 	///
284 	/// Must be called by user application.
285 	bool pointerReleased(ivec2 pointerPosition, PointerButton button)
286 	{
287 		_lastPointerPosition = pointerPosition;
288 
289 		scope event = new PointerReleaseEvent(pointerPosition, button);
290 		event.context = _context;
291 
292 		root_loop:
293 		foreach_reverse(rootWidget; _context.roots)
294 		{
295 			Widget[] widgetChain = buildPathToLeaf!(containsPointer)(rootWidget, pointerPosition);
296 
297 			foreach_reverse(item; widgetChain) // test if pointer over pressed widget.
298 			{
299 				if (item is pressedWidget)
300 				{
301 					Widget[] eventConsumerChain = propagateEventSinkBubble(widgetChain, event);
302 
303 					if (eventConsumerChain.length > 0)
304 					{
305 						if (pressedWidget is eventConsumerChain[$-1])
306 						{
307 							scope clickEvent = new PointerClickEvent(pointerPosition, button);
308 							clickEvent.context = _context;
309 
310 							pressedWidget.handleEvent(clickEvent);
311 
312 							lastClickedWidget = pressedWidget;
313 						}
314 					}
315 
316 					pressedWidget = null;
317 
318 					return true;
319 				}
320 			}
321 		}
322 
323 		if (pressedWidget !is null) // no one handled event. Let's pressed widget know that pointer released.
324 		{
325 			pressedWidget.handleEvent(event); // pressed widget will know if pointer unpressed somwhere else.
326 
327 			scope moveEvent = new PointerMoveEvent(pointerPosition, ivec2(0, 0));
328 			moveEvent.context = _context;
329 			updateHovered(moveEvent); // So widget knows if pointer released not over it.
330 		}
331 
332 		pressedWidget = null;
333 
334 		return false;
335 	}
336 
337 
338 	/// Handler for pointer move event.
339 	///
340 	/// Must be called by user application.
341 	bool pointerMoved(ivec2 newPointerPosition, ivec2 delta)
342 	{
343 		_lastPointerPosition = newPointerPosition;
344 
345 		scope event = new PointerMoveEvent(newPointerPosition, delta);
346 		event.context = _context;
347 
348 		if (pressedWidget !is null)
349 		{
350 			bool handled = pressedWidget.handleEvent(event);
351 
352 			if (handled)
353 			{
354 				hoveredWidget = pressedWidget;
355 
356 				return true;
357 			}
358 		}
359 		else
360 		{
361 			if (updateHovered(event))
362 				return true;
363 		}
364 
365 		hoveredWidget = null;
366 
367 		return false;
368 	}
369 
370 	bool updateHovered(PointerMoveEvent event)
371 	{
372 		foreach_reverse(rootWidget; _context.roots)
373 		{
374 			Widget[] widgetChain = buildPathToLeaf!(containsPointer)(rootWidget, event.pointerPosition);
375 
376 			foreach_reverse(widget; widgetChain)
377 			{
378 				if (widget.getPropertyAs!("respondsToPointer", bool))
379 				{
380 					hoveredWidget = widget;
381 					return true;
382 				}
383 			}
384 		}
385 
386 		hoveredWidget = null;
387 
388 		return false;
389 	}
390 }