1 /**
2  * @file
3  */
4 
5 /*
6 Copyright (C) 2002-2013 UFO: Alien Invasion.
7 
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 See the GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22 
23 */
24 
25 #include "ui_main.h"
26 #include "ui_internal.h"
27 #include "ui_actions.h"
28 #include "ui_input.h"
29 #include "ui_internal.h"
30 #include "ui_nodes.h"
31 #include "ui_node.h"
32 #include "ui_parse.h"
33 #include "ui_draw.h"
34 #include "ui_dragndrop.h"
35 #include "ui_timer.h"
36 
37 #include "../input/cl_keys.h"
38 #include "../input/cl_input.h"
39 #include "../cl_shared.h"
40 #include "../battlescape/cl_localentity.h"
41 #include "../battlescape/cl_camera.h"
42 #include "../battlescape/cl_actor.h"
43 #include "../battlescape/cl_battlescape.h"
44 
45 /**
46  * @brief save the node with the focus
47  */
48 static uiNode_t* focusNode;
49 
50 /**
51  * @brief save the current hovered node (first node under the mouse)
52  * @sa UI_GetHoveredNode
53  * @sa UI_MouseMove
54  * @sa UI_CheckMouseMove
55  */
56 static uiNode_t* hoveredNode;
57 
58 /**
59  * @brief save the previous hovered node
60  */
61 static uiNode_t* oldHoveredNode;
62 
63 /**
64  * @brief save old position of the mouse
65  */
66 static int oldMousePosX, oldMousePosY;
67 
68 /**
69  * @brief save the captured node
70  * @sa UI_SetMouseCapture
71  * @sa UI_GetMouseCapture
72  * @sa UI_MouseRelease
73  * @todo think about replacing it by a boolean. When capturedNode != nullptr => hoveredNode == capturedNode
74  * it create unneed case
75  */
76 static uiNode_t* capturedNode;
77 
78 /**
79  * @brief Store node which receive a mouse down event
80  */
81 static uiNode_t* pressedNode;
82 
83 /**
84  * @brief X position of the mouse when pressedNode != nullptr
85  */
86 static int pressedNodeLocationX;
87 
88 /**
89  * @brief Y position of the mouse when pressedNode != nullptr
90  */
91 static int pressedNodeLocationY;
92 
93 /**
94  * @brief Button pressed when pressedNode != nullptr
95  */
96 static int pressedNodeButton;
97 
98 /**
99  * @brief Value of pressedNodeLocationX when event already sent
100  */
101 #define UI_STARTDRAG_ALREADY_SENT -1
102 
103 /**
104  * @brief Value of the delay to wait before sending a long press event
105  * This value is used for Qt 4.7. Maybe we can use a cvar
106  */
107 #define LONGPRESS_DELAY 800
108 
109 /**
110  * @brief Timer used to manage long press event
111  */
112 static uiTimer_t* longPressTimer;
113 
114 
115 /**
116  * @brief Execute the current focused action node
117  * @note Action nodes are nodes with click defined
118  * @sa Key_Event
119  * @sa UI_FocusNextActionNode
120  */
UI_FocusExecuteActionNode(void)121 static bool UI_FocusExecuteActionNode (void)
122 {
123 #if 0	/**< @todo need a cleanup */
124 	if (IN_GetMouseSpace() != MS_UI)
125 		return false;
126 
127 	if (UI_GetMouseCapture())
128 		return false;
129 
130 	if (focusNode) {
131 		if (focusNode->onClick) {
132 			UI_ExecuteEventActions(focusNode, focusNode->onClick);
133 		}
134 		UI_ExecuteEventActions(focusNode, focusNode->onMouseLeave);
135 		focusNode = nullptr;
136 		return true;
137 	}
138 #endif
139 	return false;
140 }
141 
142 #if 0	/**< @todo need a cleanup */
143 /**
144  * @sa UI_FocusExecuteActionNode
145  * @note node must be in a window
146  */
147 static uiNode_t* UI_GetNextActionNode (uiNode_t* node)
148 {
149 	if (node)
150 		node = node->next;
151 	while (node) {
152 		if (UI_CheckVisibility(node) && !node->invis
153 		 && ((node->onClick && node->onMouseEnter) || node->onMouseEnter))
154 			return node;
155 		node = node->next;
156 	}
157 	return nullptr;
158 }
159 #endif
160 
161 /**
162  * @brief Set the focus to the next action node
163  * @note Action nodes are nodes with click defined
164  * @sa Key_Event
165  * @sa UI_FocusExecuteActionNode
166  */
UI_FocusNextActionNode(void)167 static bool UI_FocusNextActionNode (void)
168 {
169 #if 0	/**< @todo need a cleanup */
170 	static int i = UI_MAX_WINDOWSTACK + 1;	/* to cycle between all windows */
171 
172 	if (IN_GetMouseSpace() != MS_UI)
173 		return false;
174 
175 	if (UI_GetMouseCapture())
176 		return false;
177 
178 	if (i >= ui_global.windowStackPos)
179 		i = UI_GetLastFullScreenWindow();
180 
181 	assert(i >= 0);
182 
183 	if (focusNode) {
184 		uiNode_t* node = UI_GetNextActionNode(focusNode);
185 		if (node)
186 			return UI_FocusSetNode(node);
187 	}
188 
189 	while (i < ui_global.windowStackPos) {
190 		uiNode_t* window;
191 		window = ui_global.windowStack[i++];
192 		if (UI_FocusSetNode(UI_GetNextActionNode(window->firstChild)))
193 			return true;
194 	}
195 	i = UI_GetLastFullScreenWindow();
196 
197 	/* no node to focus */
198 	UI_RemoveFocus();
199 #endif
200 	return false;
201 }
202 
203 /**
204  * @brief request the focus for a node
205  */
UI_RequestFocus(uiNode_t * node)206 void UI_RequestFocus (uiNode_t* node)
207 {
208 	uiNode_t* tmp;
209 	assert(node);
210 	if (node == focusNode)
211 		return;
212 
213 	/* invalidate the data before calling the event */
214 	tmp = focusNode;
215 	focusNode = nullptr;
216 
217 	/* lost the focus */
218 	if (tmp) {
219 		UI_Node_FocusLost(tmp);
220 	}
221 
222 	/* get the focus */
223 	focusNode = node;
224 	UI_Node_FocusGained(focusNode);
225 }
226 
227 /**
228  * @brief check if a node got the focus
229  */
UI_HasFocus(const uiNode_t * node)230 bool UI_HasFocus (const uiNode_t* node)
231 {
232 	return node == focusNode;
233 }
234 
235 /**
236  * @sa UI_FocusExecuteActionNode
237  * @sa UI_FocusNextActionNode
238  * @sa IN_Frame
239  * @sa Key_Event
240  */
UI_RemoveFocus(void)241 void UI_RemoveFocus (void)
242 {
243 	uiNode_t* tmp;
244 
245 	if (UI_GetMouseCapture())
246 		return;
247 
248 	if (!focusNode)
249 		return;
250 
251 	/* invalidate the data before calling the event */
252 	tmp = focusNode;
253 	focusNode = nullptr;
254 
255 	/* callback the lost of the focus */
256 	UI_Node_FocusLost(tmp);
257 }
258 
UI_AllocStaticKeyBinding(void)259 static uiKeyBinding_t* UI_AllocStaticKeyBinding (void)
260 {
261 	uiKeyBinding_t* result;
262 	if (ui_global.numKeyBindings >= UI_MAX_KEYBINDING)
263 		Com_Error(ERR_FATAL, "UI_AllocStaticKeyBinding: UI_MAX_KEYBINDING hit");
264 
265 	result = &ui_global.keyBindings[ui_global.numKeyBindings];
266 	ui_global.numKeyBindings++;
267 
268 	OBJZERO(*result);
269 	return result;
270 }
271 
UI_GetKeyBindingCount(void)272 int UI_GetKeyBindingCount (void)
273 {
274 	return ui_global.numKeyBindings;
275 }
276 
UI_GetKeyBindingByIndex(int index)277 uiKeyBinding_t* UI_GetKeyBindingByIndex (int index)
278 {
279 	return &ui_global.keyBindings[index];
280 }
281 
282 /**
283  * @brief Set a binding from a key to a node to active.
284  *
285  * This command create a relation between a key and a node.
286  *
287  * The relation is stored to the node (to display the shortcut into tooltip)
288  * and to parent window of the node (to faster search of all available shortcuts).
289  *
290  * The storage on the node is not a list, then if there is more than one shortcut
291  * to a node we can't display all shortcut to tooltip, but the binding will still
292  * work.
293  *
294  * If the parent window is inherited, the binding is dup to others extend
295  * windows and the relation is flagged as "inherited".
296  *
297  * @param path Path to a node, or a node mathod
298  * @param key The key number to use (see for example the K_* names are matched up.)
299  * @param inherited True if this binding is inherited from another binding.
300  *
301  * @todo check: only one binding per nodes
302  * @todo check: key per window must be unique
303  * @todo check: key used into UI_KeyPressed can't be used
304  */
UI_SetKeyBindingEx(const char * path,int key,const char * description,bool inherited)305 static void UI_SetKeyBindingEx (const char* path, int key, const char* description, bool inherited)
306 {
307 	uiNode_t* node;
308 	uiKeyBinding_t* binding;
309 	const value_t* property = nullptr;
310 	int windowId;
311 	char newPath[256];
312 
313 	UI_ReadNodePath(path, nullptr, &node, &property);
314 	if (node == nullptr) {
315 		Com_Printf("UI_SetKeyBinding: node \"%s\" not found.\n", path);
316 		return;
317 	}
318 
319 	if (property != nullptr && property->type != V_UI_NODEMETHOD)
320 		Com_Error(ERR_FATAL, "UI_SetKeyBinding: Only node and method are supported. Property @%s not found in path \"%s\".", property->string, path);
321 
322 	/* init and link the keybinding */
323 	binding = UI_AllocStaticKeyBinding();
324 	binding->node = node;
325 	binding->property = property;
326 	binding->key = key;
327 	binding->inherited = inherited;
328 	node->key = binding;
329 
330 	if (Q_strnull(description))
331 		Com_Printf("Warning: Empty description for UI keybinding: %s (%s)\n", path, Key_KeynumToString(key));
332 	else
333 		binding->description = Mem_PoolStrDup(description, ui_dynPool, 0);
334 
335 	UI_WindowNodeRegisterKeyBinding(node->root, binding);
336 
337 	/* search and update windows extend node->root */
338 	for (windowId = 0; windowId < ui_global.numWindows; windowId++) {
339 		uiNode_t* window = ui_global.windows[windowId];
340 
341 		/* skip window which are not direct extends of the main window */
342 		if (window->super != node->root)
343 			continue;
344 
345 		/* create a new patch from the new windows */
346 		newPath[0] = '\0';
347 		Q_strcat(newPath, sizeof(newPath), "%s%s", window->name, path + strlen(node->root->name));
348 		UI_SetKeyBindingEx(newPath, key, description, true);
349 	}
350 }
351 
352 /**
353  * @brief Set a binding from a key to a node to active.
354  *
355  * @param path Path to a node, or a node mathod
356  * @param key The key number to use (see for example the K_* names are matched up.)
357  *
358  * @todo check: only one binding per nodes?
359  * @todo check: key per window must be unique
360  * @todo check: key used into UI_KeyPressed can't be used
361  */
UI_SetKeyBinding(const char * path,int key,const char * description)362 void UI_SetKeyBinding (const char* path, int key, const char* description)
363 {
364 	UI_SetKeyBindingEx(path, key, description, false);
365 }
366 
367 /**
368  * @brief Check if a key binding exists for a window and execute it
369  */
UI_KeyPressedInWindow(unsigned int key,const uiNode_t * window)370 static bool UI_KeyPressedInWindow (unsigned int key, const uiNode_t* window)
371 {
372 	uiNode_t* node;
373 	const uiKeyBinding_t* binding;
374 
375 	/* search requested key binding */
376 	binding = UI_WindowNodeGetKeyBinding(window, key);
377 	if (!binding)
378 		return false;
379 
380 	/* check node visibility */
381 	node = binding->node;
382 	while (node) {
383 		if (node->disabled || node->invis)
384 			return false;
385 		node = node->parent;
386 	}
387 
388 	/* execute event */
389 	node = binding->node;
390 	if (binding->property == nullptr)
391 		UI_Node_Activate(node);
392 	else if (binding->property->type == V_UI_NODEMETHOD) {
393 		uiCallContext_t newContext;
394 		uiNodeMethod_t func = (uiNodeMethod_t) binding->property->ofs;
395 		newContext.source = node;
396 		newContext.useCmdParam = false;
397 		func(node, &newContext);
398 	} else
399 		Com_Printf("UI_KeyPressedInWindow: @%s not supported.", binding->property->string);
400 
401 	return true;
402 }
403 
404 /**
405  * @brief Called by the client when the user released a key
406  * @param[in] key key code, either K_ value or lowercase ascii
407  * @param[in] unicode translated meaning of keypress in unicode
408  * @return true, if we used the event
409  */
UI_KeyRelease(unsigned int key,unsigned short unicode)410 bool UI_KeyRelease (unsigned int key, unsigned short unicode)
411 {
412 	/* translate event into the node with focus */
413 	if (focusNode) {
414 		return UI_Node_KeyReleased(focusNode, key, unicode);
415 	}
416 
417 	return false;
418 }
419 
420 /**
421  * @brief Called by the client when the user type a key
422  * @param[in] key key code, either K_ value or lowercase ascii
423  * @param[in] unicode translated meaning of keypress in unicode
424  * @return true, if we used the event
425  * @todo think about what we should do when the mouse is captured
426  */
UI_KeyPressed(unsigned int key,unsigned short unicode)427 bool UI_KeyPressed (unsigned int key, unsigned short unicode)
428 {
429 	int windowId;
430 	int lastWindowId;
431 
432 	if (UI_DNDIsDragging()) {
433 		if (key == K_ESCAPE) {
434 			UI_DNDAbort();
435 			return true;
436 		}
437 		return false;
438 	}
439 
440 	if (key == K_ESCAPE && CL_BattlescapeRunning()
441 	 && selActor && CL_ActorFireModeActivated(selActor->actorMode)) {
442 		/* Cancel firing with Escape, needed for Android, where right mouse click is bound to multitouch, which is non-obvious */
443 		CL_ActorSetMode(selActor, M_MOVE);
444 		return true;
445 	}
446 
447 	/* translate event into the node with focus */
448 	if (focusNode && UI_Node_KeyPressed(focusNode, key, unicode)) {
449 		return true;
450 	}
451 
452 	/* else use common behaviour */
453 	switch (key) {
454 	case K_TAB:
455 		if (UI_FocusNextActionNode())
456 			return true;
457 		break;
458 	case K_ENTER:
459 	case K_KP_ENTER:
460 		if (UI_FocusExecuteActionNode())
461 			return true;
462 		break;
463 	case K_ESCAPE:
464 		if (UI_GetMouseCapture() != nullptr) {
465 			UI_MouseRelease();
466 			return true;
467 		}
468 		UI_PopWindowWithEscKey();
469 		return true;
470 	}
471 
472 	lastWindowId = UI_GetLastFullScreenWindow();
473 	if (lastWindowId < 0)
474 		return false;
475 
476 	/* check "active" window from top to down */
477 	for (windowId = ui_global.windowStackPos - 1; windowId >= lastWindowId; windowId--) {
478 		const uiNode_t* window = ui_global.windowStack[windowId];
479 		if (!window)
480 			return false;
481 		if (UI_KeyPressedInWindow(key, window))
482 			return true;
483 		if (UI_WindowIsModal(window))
484 			break;
485 	}
486 
487 	return false;
488 }
489 
490 /**
491  * @brief Release all captured input (keyboard or mouse)
492  */
UI_ReleaseInput(void)493 void UI_ReleaseInput (void)
494 {
495 	UI_RemoveFocus();
496 	UI_MouseRelease();
497 	if (UI_DNDIsDragging())
498 		UI_DNDAbort();
499 }
500 
501 /**
502  * @brief Return the captured node
503  * @return Return a node, else nullptr
504  */
UI_GetMouseCapture(void)505 uiNode_t* UI_GetMouseCapture (void)
506 {
507 	return capturedNode;
508 }
509 
510 /**
511  * @brief Captured the mouse into a node
512  */
UI_SetMouseCapture(uiNode_t * node)513 void UI_SetMouseCapture (uiNode_t* node)
514 {
515 	assert(capturedNode == nullptr);
516 	assert(node != nullptr);
517 	capturedNode = node;
518 }
519 
520 /**
521  * @brief Release the captured node
522  */
UI_MouseRelease(void)523 void UI_MouseRelease (void)
524 {
525 	uiNode_t* tmp = capturedNode;
526 
527 	if (capturedNode == nullptr)
528 		return;
529 
530 	capturedNode = nullptr;
531 	UI_Node_CapturedMouseLost(tmp);
532 	UI_InvalidateMouse();
533 }
534 
UI_ResetInput(void)535 void UI_ResetInput (void)
536 {
537 	hoveredNode = nullptr;
538 	oldHoveredNode = nullptr;
539 	focusNode = nullptr;
540 	capturedNode = nullptr;
541 	pressedNode = nullptr;
542 	longPressTimer = nullptr;
543 }
544 
545 /**
546  * @brief Get the current hovered node
547  * @return A node, else nullptr if the mouse hover nothing
548  */
UI_GetHoveredNode(void)549 uiNode_t* UI_GetHoveredNode (void)
550 {
551 	return hoveredNode;
552 }
553 
554 /**
555  * @brief Force to invalidate the mouse position and then to update hover nodes...
556  */
UI_InvalidateMouse(void)557 void UI_InvalidateMouse (void)
558 {
559 	oldMousePosX = -1;
560 	oldMousePosY = -1;
561 }
562 
563 /**
564  * @brief Call mouse move only if the mouse position change
565  */
UI_CheckMouseMove(void)566 bool UI_CheckMouseMove (void)
567 {
568 	/* is hovered node no more draw */
569 	if (hoveredNode && (hoveredNode->invis || !UI_CheckVisibility(hoveredNode)))
570 		UI_InvalidateMouse();
571 
572 	if (mousePosX != oldMousePosX || mousePosY != oldMousePosY) {
573 		oldMousePosX = mousePosX;
574 		oldMousePosY = mousePosY;
575 		UI_MouseMove(mousePosX, mousePosY);
576 		return true;
577 	}
578 
579 	return false;
580 }
581 
582 /**
583  * @brief Is called every time the mouse position change
584  */
UI_MouseMove(int x,int y)585 void UI_MouseMove (int x, int y)
586 {
587 	if (UI_DNDIsDragging())
588 		return;
589 
590 	if (pressedNode && !capturedNode) {
591 		if (pressedNodeLocationX != UI_STARTDRAG_ALREADY_SENT) {
592 			UI_TimerStop(longPressTimer);
593 			int dist = abs(pressedNodeLocationX - x) + abs(pressedNodeLocationY - y);
594 			if (dist > 4) {
595 				uiNode_t* node = pressedNode;
596 				while (node) {
597 					if (UI_Node_StartDragging(node, pressedNodeLocationX, pressedNodeLocationY, x, y, pressedNodeButton))
598 						break;
599 					node = node->parent;
600 				}
601 				pressedNodeLocationX = UI_STARTDRAG_ALREADY_SENT;
602 			}
603 		}
604 	}
605 
606 	/* send the captured move mouse event */
607 	if (capturedNode) {
608 		UI_Node_CapturedMouseMove(capturedNode, x, y);
609 		return;
610 	}
611 
612 	hoveredNode = UI_GetNodeAtPosition(x, y);
613 
614 	/* update houvered node by sending messages */
615 	if (oldHoveredNode != hoveredNode) {
616 		uiNode_t* commonNode = hoveredNode;
617 		uiNode_t* node;
618 
619 		/* search the common node */
620 		while (commonNode) {
621 			node = oldHoveredNode;
622 			while (node) {
623 				if (node == commonNode)
624 					break;
625 				node = node->parent;
626 			}
627 			if (node != nullptr)
628 				break;
629 			commonNode = commonNode->parent;
630 		}
631 
632 		/* send 'leave' event from old node to common node */
633 		node = oldHoveredNode;
634 		while (node != commonNode) {
635 			UI_Node_MouseLeave(node);
636 			node = node->parent;
637 		}
638 		if (oldHoveredNode)
639 			oldHoveredNode->state = false;
640 
641 		/* send 'enter' event from common node to new node */
642 		while (commonNode != hoveredNode) {
643 			/** @todo we can remove that loop with an array allocation */
644 			node = hoveredNode;
645 			while (node->parent != commonNode)
646 				node = node->parent;
647 			commonNode = node;
648 			UI_Node_MouseEnter(node);
649 		}
650 		if (hoveredNode) {
651 			hoveredNode->state = true;
652 			UI_Node_MouseEnter(node);
653 		}
654 	}
655 
656 	oldHoveredNode = hoveredNode;
657 
658 	/* send the move event */
659 	if (hoveredNode)
660 		UI_Node_MouseMove(hoveredNode, x, y);
661 }
662 
663 #define UI_IsMouseInvalidate() (oldMousePosX == -1)
664 
665 /**
666  * @brief Is called every time one clicks on a window/screen. Then checks if anything needs to be executed in the area of the click
667  * (e.g. button-commands, inventory-handling, geoscape-stuff, etc...)
668  * @sa UI_ExecuteEventActions
669  * @sa UI_RightClick
670  * @sa Key_Message
671  */
UI_LeftClick(int x,int y)672 static void UI_LeftClick (int x, int y)
673 {
674 	if (UI_IsMouseInvalidate())
675 		return;
676 
677 	/* send it to the captured mouse node */
678 	if (capturedNode) {
679 		UI_Node_LeftClick(capturedNode, x, y);
680 		return;
681 	}
682 
683 	/* if we click outside a dropdown window, we close it */
684 	/** @todo need to refactoring it with the focus code (cleaner) */
685 	/** @todo at least should be moved on the mouse down event (when the focus should change) */
686 	/** @todo this code must be on mouse down */
687 	if (!pressedNode && ui_global.windowStackPos != 0) {
688 		uiNode_t* window = ui_global.windowStack[ui_global.windowStackPos - 1];
689 		if (UI_WindowIsDropDown(window)) {
690 			UI_PopWindow();
691 		}
692 	}
693 
694 	const bool disabled = (pressedNode == nullptr) || (pressedNode->disabled) || (pressedNode->parent && pressedNode->parent->disabled);
695 	if (!disabled) {
696 		UI_Node_LeftClick(pressedNode, x, y);
697 	}
698 }
699 
700 /**
701  * @sa GEO_ResetAction
702  * @sa UI_ExecuteEventActions
703  * @sa UI_LeftClick
704  * @sa UI_MiddleClick
705  * @sa UI_MouseWheel
706  */
UI_RightClick(int x,int y)707 static void UI_RightClick (int x, int y)
708 {
709 	if (UI_IsMouseInvalidate())
710 		return;
711 
712 	/* send it to the captured mouse node */
713 	if (capturedNode) {
714 		UI_Node_RightClick(capturedNode, x, y);
715 		return;
716 	}
717 
718 	const bool disabled = (pressedNode == nullptr) || (pressedNode->disabled) || (pressedNode->parent && pressedNode->parent->disabled);
719 	if (!disabled) {
720 		UI_Node_RightClick(pressedNode, x, y);
721 	}
722 }
723 
724 /**
725  * @sa UI_LeftClick
726  * @sa UI_RightClick
727  * @sa UI_MouseWheel
728  */
UI_MiddleClick(int x,int y)729 static void UI_MiddleClick (int x, int y)
730 {
731 	if (UI_IsMouseInvalidate())
732 		return;
733 
734 	/* send it to the captured mouse node */
735 	if (capturedNode) {
736 		UI_Node_MiddleClick(capturedNode, x, y);
737 		return;
738 	}
739 
740 	const bool disabled = (pressedNode == nullptr) || (pressedNode->disabled) || (pressedNode->parent && pressedNode->parent->disabled);
741 	if (!disabled) {
742 		UI_Node_MiddleClick(pressedNode, x, y);
743 	}
744 }
745 
746 /**
747  * @brief Called when we are in UI mode and scroll via mousewheel
748  * @note The geoscape zooming code is here, too (also in @see CL_ParseInput)
749  * @sa UI_LeftClick
750  * @sa UI_RightClick
751  * @sa UI_MiddleClick
752  */
UI_MouseScroll(int deltaX,int deltaY)753 void UI_MouseScroll (int deltaX, int deltaY)
754 {
755 	uiNode_t* node;
756 
757 	/* send it to the captured mouse node */
758 	if (capturedNode) {
759 		UI_Node_Scroll(capturedNode, deltaX, deltaY);
760 		return;
761 	}
762 
763 	node = hoveredNode;
764 
765 	while (node) {
766 		if (UI_Node_Scroll(node, deltaX, deltaY)) {
767 			break;
768 		}
769 		node = node->parent;
770 	}
771 }
772 
773 /**
774  * Callback function waked up to send long click event
775  */
UI_LongPressCallback(uiNode_t *,uiTimer_t * timer)776 static void UI_LongPressCallback (uiNode_t* , uiTimer_t* timer)
777 {
778 	UI_TimerStop(timer);
779 
780 	/* make sure the event still make sense */
781 	if (pressedNode == nullptr)
782 		return;
783 
784 	uiNode_t* node = pressedNode;
785 	while (node) {
786 		if (UI_Node_MouseLongPress(node, pressedNodeLocationX, pressedNodeLocationY, pressedNodeButton))
787 			break;
788 		node = node->parent;
789 	}
790 }
791 
792 /**
793  * @brief Called when we are in UI mode and down a mouse button
794  * @sa UI_LeftClick
795  * @sa UI_RightClick
796  * @sa UI_MiddleClick
797  */
UI_MouseDown(int x,int y,int button)798 void UI_MouseDown (int x, int y, int button)
799 {
800 	/* disable old long click event */
801 	if (longPressTimer)
802 		UI_TimerStop(longPressTimer);
803 
804 	uiNode_t* node;
805 
806 	/* captured or hover node */
807 	node = capturedNode ? capturedNode : hoveredNode;
808 
809 	if (node != nullptr) {
810 		UI_MoveWindowOnTop(node->root);
811 		UI_Node_MouseDown(node, x, y, button);
812 	}
813 
814 	/* select clickableNode on button up, and detect multipress button */
815 	if (pressedNode == nullptr) {
816 		pressedNode = node;
817 		pressedNodeLocationX = x;
818 		pressedNodeLocationY = y;
819 		pressedNodeButton = button;
820 		/** @todo find a way to create the timer when UI loading and remove "if (longPressTimer)" */
821 		if (longPressTimer == nullptr) {
822 			longPressTimer = UI_AllocTimer(nullptr, LONGPRESS_DELAY, UI_LongPressCallback);
823 		}
824 		UI_TimerStart(longPressTimer);
825 	} else {
826 		pressedNode = nullptr;
827 	}
828 }
829 
830 /**
831  * @brief Called when we are in UI mode and up a mouse button
832  * @sa UI_LeftClick
833  * @sa UI_RightClick
834  * @sa UI_MiddleClick
835  */
UI_MouseUp(int x,int y,int button)836 void UI_MouseUp (int x, int y, int button)
837 {
838 	/* disable long click event */
839 	if (longPressTimer)
840 		UI_TimerStop(longPressTimer);
841 
842 	/* send click event */
843 	/** @todo it is really need to clean up this subfunctions */
844 	if (pressedNode || capturedNode) {
845 		switch (button) {
846 		case K_MOUSE1:
847 			UI_LeftClick(x, y);
848 			break;
849 		case K_MOUSE2:
850 			UI_RightClick(x, y);
851 			break;
852 		case K_MOUSE3:
853 			UI_MiddleClick(x, y);
854 			break;
855 		}
856 	}
857 
858 	/* captured or hovered node */
859 	uiNode_t* node = nullptr;
860 	if (capturedNode) {
861 		node = capturedNode;
862 	} else if (pressedNode == hoveredNode) {
863 		node = hoveredNode;
864 	}
865 
866 	pressedNode = nullptr;
867 	if (node == nullptr)
868 		return;
869 
870 	UI_Node_MouseUp(node, x, y, button);
871 }
872