1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 */
19 
20 //
21 // gui_cursor.c
22 //
23 
24 #include "gui_local.h"
25 
26 /*
27 =============================================================================
28 
29 	BOUNDS CALCULATION
30 
31 =============================================================================
32 */
33 
34 /*
35 ==================
36 GUI_GenerateBounds
37 ==================
38 */
GUI_GenerateBounds(gui_t * gui)39 void GUI_GenerateBounds (gui_t *gui)
40 {
41 	gui_t	*child;
42 	int		i;
43 
44 	// Generate bounds
45 	gui->mins[0] = gui->rect[0];
46 	gui->mins[1] = gui->rect[1];
47 	gui->maxs[0] = gui->mins[0] + gui->rect[2];
48 	gui->maxs[1] = gui->mins[1] + gui->rect[3];
49 
50 	// Generate bounds for children
51 	for (i=0, child=gui->childList ; i<gui->numChildren ; child++, i++) {
52 		GUI_GenerateBounds (child);
53 
54 		// Add to the parent
55 		AddBoundsTo2DBounds (child->mins, child->maxs, gui->mins, gui->maxs);
56 	}
57 }
58 
59 /*
60 =============================================================================
61 
62 	CURSOR COLLISION
63 
64 =============================================================================
65 */
66 
67 /*
68 ==================
69 GUI_CursorUpdate
70 ==================
71 */
72 static gui_t	*gui_bestWindow;
GUI_r_CursorUpdate(gui_t * gui,evType_t forceEvent)73 void GUI_r_CursorUpdate (gui_t *gui, evType_t forceEvent)
74 {
75 	gui_t	*child;
76 	int		i;
77 
78 	// Check for collision
79 	if (forceEvent == WEV_SHUTDOWN) {
80 		GUI_QueueTrigger (gui, WEV_SHUTDOWN);
81 	}
82 	else if (forceEvent == WEV_MOUSE_EXIT) {
83 		GUI_QueueTrigger (gui, WEV_MOUSE_EXIT);
84 	}
85 	else if (!FRVALUE (gui, FR_VISIBLE)) {
86 		GUI_QueueTrigger (gui, WEV_SHUTDOWN);
87 		forceEvent = WEV_SHUTDOWN;
88 	}
89 	else if (FRVALUE (gui, FR_NO_EVENTS)
90 	|| gui->shared->cursor.d.pos[0] < gui->mins[0]
91 	|| gui->shared->cursor.d.pos[1] < gui->mins[1]
92 	|| gui->shared->cursor.d.pos[0] > gui->maxs[0]
93 	|| gui->shared->cursor.d.pos[1] > gui->maxs[1]) {
94 		GUI_QueueTrigger (gui, WEV_MOUSE_EXIT);
95 		forceEvent = WEV_MOUSE_EXIT;
96 	}
97 	else {
98 		// Collided with this child
99 		if (!FRVALUE (gui_bestWindow, FR_MODAL) || FRVALUE (gui, FR_MODAL)) {
100 			GUI_QueueTrigger (gui, WEV_MOUSE_ENTER);
101 			forceEvent = WEV_NONE;
102 			gui_bestWindow = gui;
103 		}
104 	}
105 
106 	// Recurse down the children
107 	for (i=0, child=gui->childList ; i<gui->numChildren ; child++, i++)
108 		GUI_r_CursorUpdate (child, forceEvent);
109 }
GUI_CursorUpdate(gui_t * gui)110 void GUI_CursorUpdate (gui_t *gui)
111 {
112 	evType_t	forceEvent;
113 	gui_t		*child;
114 	int			i;
115 
116 	if (!gui->shared->cursor.mouseMoved)
117 		return;
118 	gui->shared->cursor.mouseMoved = qFalse;
119 
120 	cl_guiState.inputWindow = NULL;
121 	gui_bestWindow = NULL;
122 
123 	// Check for collision
124 	if (!FRVALUE (gui, FR_VISIBLE)) {
125 		GUI_QueueTrigger (gui, WEV_SHUTDOWN);
126 		forceEvent = WEV_SHUTDOWN;
127 	}
128 	else if (FRVALUE (gui, FR_NO_EVENTS)
129 	|| gui->shared->cursor.d.pos[0] < gui->mins[0]
130 	|| gui->shared->cursor.d.pos[1] < gui->mins[1]
131 	|| gui->shared->cursor.d.pos[0] > gui->maxs[0]
132 	|| gui->shared->cursor.d.pos[1] > gui->maxs[1]) {
133 		GUI_QueueTrigger (gui, WEV_MOUSE_EXIT);
134 		forceEvent = WEV_MOUSE_EXIT;
135 	}
136 	else {
137 		GUI_QueueTrigger (gui, WEV_MOUSE_ENTER);
138 		forceEvent = WEV_NONE;
139 
140 		// Collided with this window
141 		cl_guiState.inputWindow = gui;
142 		gui_bestWindow = gui;
143 	}
144 
145 	// Recurse down the children
146 	for (i=0, child=gui->childList ; i<gui->numChildren ; child++, i++)
147 		GUI_r_CursorUpdate (child, forceEvent);
148 
149 	// Trigger events
150 	if (gui->shared->cursor.curWindow != gui_bestWindow) {
151 		if (gui->shared->cursor.curWindow)
152 			GUI_QueueTrigger (gui->shared->cursor.curWindow, WEV_SHUTDOWN);
153 	}
154 
155 	// Store
156 	gui->shared->cursor.curWindow = gui_bestWindow;
157 }
158 
159 /*
160 =============================================================================
161 
162 	CURSOR MOVEMENT
163 
164 =============================================================================
165 */
166 
167 /*
168 ==================
169 GUI_MoveMouse
170 ==================
171 */
GUI_MoveMouse(int xMove,int yMove)172 void GUI_MoveMouse (int xMove, int yMove)
173 {
174 	guiCursor_t	*cursor;
175 	gui_t		*gui;
176 	int			i;
177 
178 	// Let open GUIs know the cursor moved
179 	if (xMove || yMove) {
180 		for (i=0 ; i<cl_guiState.numLayers ; i++)
181 			cl_guiState.openLayers[i]->shared->cursor.mouseMoved = qTrue;
182 	}
183 
184 	// Move for the input window
185 	gui = cl_guiState.inputWindow;
186 	if (!gui)
187 		return;
188 
189 	cursor = &gui->shared->cursor;
190 	if (cursor->d.locked)
191 		return;
192 
193 	// Scale
194 	xMove *= gui->shared->xScale;
195 	yMove *= gui->shared->yScale;
196 
197 	// Move
198 	if (gui_mouseFilter->intVal) {
199 		cursor->d.pos[0] = (cursor->d.pos[0] * 2 + (xMove * gui_mouseSensitivity->floatVal)) * 0.5f;
200 		cursor->d.pos[1] = (cursor->d.pos[1] * 2 + (yMove * gui_mouseSensitivity->floatVal)) * 0.5f;
201 	}
202 	else {
203 		cursor->d.pos[0] += xMove * gui_mouseSensitivity->floatVal;
204 		cursor->d.pos[1] += yMove * gui_mouseSensitivity->floatVal;
205 	}
206 
207 	// Clamp
208 	cursor->d.pos[0] = clamp (cursor->d.pos[0], gui->owner->mins[0], gui->owner->maxs[0]);
209 	cursor->d.pos[1] = clamp (cursor->d.pos[1], gui->owner->mins[1], gui->owner->maxs[1]);
210 }
211 
212 
213 /*
214 ==================
215 GUI_AdjustCursor
216 ==================
217 */
218 static qBool	gui_bestFurthest;
219 static float	gui_bestKeyDist;
220 static gui_t	*gui_bestKeyWindow;
GUI_r_AdjustCursor(gui_t * gui,guiCursor_t * cursor,vec4_t bounds)221 void GUI_r_AdjustCursor (gui_t *gui, guiCursor_t *cursor, vec4_t bounds)
222 {
223 	gui_t	*child;
224 	float	dist;
225 	vec2_t	center;
226 	int		i;
227 
228 	// Must be visible and accept events
229 	if (!FRVALUE (gui, FR_VISIBLE)
230 	|| FRVALUE (gui, FR_NO_EVENTS)) {
231 		return;
232 	}
233 
234 	// Check for collision with movement bounds
235 	if (bounds[0] > gui->maxs[0]
236 	|| bounds[1] > gui->maxs[1]
237 	|| bounds[2] < gui->mins[0]
238 	|| bounds[3] < gui->mins[1])
239 		return;
240 
241 	// Can't be the same window and must have a selectable item flag
242 	if (gui != cursor->curWindow && gui->flags & WFL_ITEM) {
243 		// Find the center point of this window
244 		center[0] = gui->mins[0] + ((gui->maxs[0] - gui->mins[0]) * 0.5f);
245 		center[1] = gui->mins[1] + ((gui->maxs[1] - gui->mins[1]) * 0.5f);
246 
247 		// Calculate distance
248 		dist = Vec2Dist (cursor->d.pos, center);
249 
250 		// Check if it's the closest
251 		if (gui_bestFurthest) {
252 			if (dist > gui_bestKeyDist) {
253 				gui_bestKeyDist = dist;
254 				gui_bestKeyWindow = gui;
255 			}
256 		}
257         else if (dist < gui_bestKeyDist) {
258 			gui_bestKeyDist = dist;
259 			gui_bestKeyWindow = gui;
260 		}
261 	}
262 
263 	// Recurse down the children
264 	for (i=0, child=gui->childList ; i<gui->numChildren ; child++, i++)
265 		GUI_r_AdjustCursor (child, cursor, bounds);
266 }
GUI_AdjustCursor(keyNum_t keyNum)267 void GUI_AdjustCursor (keyNum_t keyNum)
268 {
269 	gui_t		*gui, *curItem;
270 	guiCursor_t	*cursor;
271 	vec2_t		end;
272 	vec4_t		bounds;
273 	float		xMove, yMove;
274 
275 	// Move for the input window
276 	gui = cl_guiState.inputWindow;
277 	if (!gui)
278 		return;
279 
280 	cursor = &gui->shared->cursor;
281 	if (!cursor || cursor->d.locked)
282 		return;
283 
284 	curItem = cursor->curWindow;
285 	if (!(curItem->flags & WFL_ITEM))
286 		curItem = NULL;
287 
288 	// Generate bounds
289 	switch (keyNum) {
290 	case K_UPARROW:
291 	case K_KP_UPARROW:
292 		bounds[0] = gui->owner->mins[0];
293 		bounds[1] = gui->owner->mins[1];
294 		bounds[2] = gui->owner->maxs[0];
295 		bounds[3] = (curItem) ? curItem->mins[1] + 1 : cursor->d.pos[1] + 1;
296 		break;
297 
298 	case K_DOWNARROW:
299 	case K_KP_DOWNARROW:
300 		bounds[0] = gui->owner->mins[0];
301 		bounds[1] = (curItem) ? curItem->maxs[1] - 1 : cursor->d.pos[1] - 1;
302 		bounds[2] = gui->owner->maxs[0];
303 		bounds[3] = gui->owner->maxs[1];
304 		break;
305 
306 	case K_LEFTARROW:
307 	case K_KP_LEFTARROW:
308 		bounds[0] = gui->owner->mins[0];
309 		bounds[1] = gui->owner->mins[1];
310 		bounds[2] = (curItem) ? curItem->mins[0] - 1 : cursor->d.pos[0] - 1;
311 		bounds[3] = gui->owner->maxs[1];
312 		break;
313 
314 	case K_RIGHTARROW:
315 	case K_KP_RIGHTARROW:
316 		bounds[0] = (curItem) ? curItem->maxs[0] + 1 : cursor->d.pos[0] + 1;
317 		bounds[1] = gui->owner->mins[1];
318 		bounds[2] = gui->owner->maxs[0];
319 		bounds[3] = gui->owner->maxs[1];
320 		break;
321 
322 	default:
323 		assert (0);
324 		break;
325 	}
326 
327 	// Search for the nearest item in that direction
328 	if (!gui_bestFurthest)
329 		gui_bestKeyDist = 999999;
330 	gui_bestKeyWindow = NULL;
331 	GUI_r_AdjustCursor (gui->owner, cursor, bounds);
332 
333 	// If nothing is found, try the complete opposite direction
334 	if (!gui_bestKeyWindow) {
335 		if (!gui_bestFurthest) {
336 			gui_bestFurthest = qTrue;
337 			gui_bestKeyDist = -999999;
338 			switch (keyNum) {
339 			case K_UPARROW:
340 			case K_KP_UPARROW:
341 				GUI_AdjustCursor (K_DOWNARROW);
342 				break;
343 
344 			case K_DOWNARROW:
345 			case K_KP_DOWNARROW:
346 				GUI_AdjustCursor (K_UPARROW);
347 				break;
348 
349 			case K_LEFTARROW:
350 			case K_KP_LEFTARROW:
351 				GUI_AdjustCursor (K_RIGHTARROW);
352 				break;
353 
354 			case K_RIGHTARROW:
355 			case K_KP_RIGHTARROW:
356 				GUI_AdjustCursor (K_LEFTARROW);
357 				break;
358 			}
359 			gui_bestFurthest = qFalse;
360 		}
361 		return;
362 	}
363 
364 	// Calculate the final end point (center of the best item)
365 	end[0] = gui_bestKeyWindow->mins[0] + ((gui_bestKeyWindow->maxs[0] - gui_bestKeyWindow->mins[0]) * 0.5f);
366 	end[1] = gui_bestKeyWindow->mins[1] + ((gui_bestKeyWindow->maxs[1] - gui_bestKeyWindow->mins[1]) * 0.5f);
367 
368 	// Find the distance moved
369 	if (cursor->d.pos[0] >= end[0])
370 		xMove = -(cursor->d.pos[0] - end[0]);
371 	else
372 		xMove = end[0] - cursor->d.pos[0];
373 	if (cursor->d.pos[1] >= end[1])
374 		yMove = -(cursor->d.pos[1] - end[1]);
375 	else
376 		yMove = end[1] - cursor->d.pos[1];
377 
378 	// Remove scale, GUI_MouseMove scales
379 	if (xMove)
380 		xMove /= gui->shared->xScale;
381 	if (yMove)
382 		yMove /= gui->shared->yScale;
383 
384 	// Move the cursor to that item
385 	GUI_MoveMouse (xMove, yMove);
386 }
387