1 
2 //=================================================================================================
3 //	MouseSystem.c
4 //
5 //	Routines for handling prioritized mouse regions. The system as setup below allows the use of
6 //	callback functions for each region, as well as allowing a different cursor to be defined for
7 //	each region.
8 //
9 //	Written by Bret Rowdon, Jan 30 '97
10 //  Re-Written by Kris Morness, since...
11 //
12 //=================================================================================================
13 
14 #include <stdexcept>
15 
16 #include "Font.h"
17 #include "HImage.h"
18 #include "Types.h"
19 #include "Debug.h"
20 #include "Input.h"
21 #include "MemMan.h"
22 #include "Line.h"
23 #include "VObject.h"
24 #include "Video.h"
25 #include "MouseSystem.h"
26 #include "Cursor_Control.h"
27 #include "Button_System.h"
28 #include "Timer.h"
29 #include "Font_Control.h"
30 #include "JAScreens.h"
31 #include "Local.h"
32 #include "Render_Dirty.h"
33 #include "VSurface.h"
34 #include "ScreenIDs.h"
35 #include "UILayout.h"
36 
37 
38 //Kris:	Nov 31, 1999 -- Added support for double clicking
39 //
40 //Max double click delay (in milliseconds) to be considered a double click
41 #define MSYS_DOUBLECLICK_DELAY		400
42 //
43 //Records and stores the last place the user clicked.  These values are compared to the current
44 //click to determine if a double click event has been detected.
45 static MOUSE_REGION* gpRegionLastLButtonDown      = NULL;
46 static MOUSE_REGION* gpRegionLastLButtonUp        = NULL;
47 static UINT32        guiRegionLastLButtonDownTime = 0;
48 
49 
50 INT16 MSYS_CurrentMX=0;
51 INT16 MSYS_CurrentMY=0;
52 static INT16 MSYS_CurrentButtons = 0;
53 static INT16 MSYS_Action         = 0;
54 
55 static BOOLEAN MSYS_SystemInitialized   = FALSE;
56 
57 static MOUSE_REGION* g_clicked_region;
58 
59 static MOUSE_REGION* MSYS_RegList = NULL;
60 
61 static MOUSE_REGION* MSYS_PrevRegion = 0;
62 static MOUSE_REGION* MSYS_CurrRegion = NULL;
63 
64 static const INT16 gsFastHelpDelay = 600; // In timer ticks
65 
66 
67 static BOOLEAN gfRefreshUpdate = FALSE;
68 
69 //Kris:  December 3, 1997
70 //Special internal debugging utilities that will ensure that you don't attempt to delete
71 //an already deleted region.  It will also ensure that you don't create an identical region
72 //that already exists.
73 //TO REMOVE ALL DEBUG FUNCTIONALITY:  simply comment out MOUSESYSTEM_DEBUGGING definition
74 #if defined _DEBUG && !defined BOUNDS_CHECKER
75 #	define MOUSESYSTEM_DEBUGGING
76 #endif
77 
78 
79 static void MSYS_TrashRegList(void);
80 
81 
82 //======================================================================================================
83 //	MSYS_Init
84 //
85 //	Initialize the mouse system.
86 //
MSYS_Init(void)87 void MSYS_Init(void)
88 {
89 	MSYS_TrashRegList();
90 
91 	MSYS_CurrentMX = 0;
92 	MSYS_CurrentMY = 0;
93 	MSYS_CurrentButtons = 0;
94 	MSYS_Action=MSYS_NO_ACTION;
95 
96 	MSYS_PrevRegion = NULL;
97 	MSYS_SystemInitialized = TRUE;
98 }
99 
100 
101 //======================================================================================================
102 //	MSYS_Shutdown
103 //
104 //	De-inits the "mousesystem" mouse region handling code.
105 //
MSYS_Shutdown(void)106 void MSYS_Shutdown(void)
107 {
108 	MSYS_SystemInitialized = FALSE;
109 	MSYS_TrashRegList();
110 }
111 
112 
113 static void MSYS_UpdateMouseRegion(void);
114 
115 
MouseSystemHook(UINT16 Type,UINT16 Xcoord,UINT16 Ycoord)116 void MouseSystemHook(UINT16 Type, UINT16 Xcoord, UINT16 Ycoord)
117 {
118 	// If the mouse system isn't initialized, get out o' here
119 	if (!MSYS_SystemInitialized) return;
120 
121 	INT16 action = MSYS_NO_ACTION;
122 	switch (Type)
123 	{
124 		case LEFT_BUTTON_DOWN:  action |= MSYS_DO_LBUTTON_DWN; goto update_buttons;
125 
126 		case LEFT_BUTTON_UP:
127 			/* Kris:
128 			 * Used only if applicable.  This is used for that special button that is
129 			 * locked with the mouse press -- just like windows.  When you release the
130 			 * button, the previous state of the button is restored if you released
131 			 * the mouse outside of it's boundaries.  If you release inside of the
132 			 * button, the action is selected -- but later in the code.
133 			 * NOTE:  It has to be here, because the mouse can be released anywhere
134 			 *        regardless of regions, buttons, etc. */
135 			ReleaseAnchorMode();
136 			action |= MSYS_DO_LBUTTON_UP;
137 			goto update_buttons;
138 
139 		case RIGHT_BUTTON_DOWN: action |= MSYS_DO_RBUTTON_DWN; goto update_buttons;
140 		case RIGHT_BUTTON_UP:   action |= MSYS_DO_RBUTTON_UP;  goto update_buttons;
141 		case MIDDLE_BUTTON_DOWN:action |= MSYS_DO_MBUTTON_DWN; goto update_buttons;
142 		case MIDDLE_BUTTON_UP:  action |= MSYS_DO_MBUTTON_UP;  goto update_buttons;
143 
144 update_buttons:
145 			MSYS_CurrentButtons &= ~(MSYS_LEFT_BUTTON | MSYS_RIGHT_BUTTON | MSYS_MIDDLE_BUTTON);
146 			MSYS_CurrentButtons |= (_LeftButtonDown  ? MSYS_LEFT_BUTTON  : 0);
147 			MSYS_CurrentButtons |= (_RightButtonDown ? MSYS_RIGHT_BUTTON : 0);
148 			MSYS_CurrentButtons |= (_MiddleButtonDown ? MSYS_MIDDLE_BUTTON : 0);
149 			break;
150 
151 		// ATE: Checks here for mouse button repeats.....
152 		// Call mouse region with new reason
153 		case LEFT_BUTTON_REPEAT:  action |= MSYS_DO_LBUTTON_REPEAT; break;
154 		case RIGHT_BUTTON_REPEAT: action |= MSYS_DO_RBUTTON_REPEAT; break;
155 		case MIDDLE_BUTTON_REPEAT:action |= MSYS_DO_MBUTTON_REPEAT; break;
156 
157 		case MOUSE_WHEEL_UP:   action |= MSYS_DO_WHEEL_UP;   break;
158 		case MOUSE_WHEEL_DOWN: action |= MSYS_DO_WHEEL_DOWN; break;
159 
160 		case MOUSE_POS:
161 			if (gfRefreshUpdate)
162 			{
163 				gfRefreshUpdate = FALSE;
164 				goto force_move;
165 			}
166 			break;
167 
168 		default: return; /* Not a mouse message, ignore it */
169 	}
170 
171 	if (Xcoord != MSYS_CurrentMX || Ycoord != MSYS_CurrentMY)
172 	{
173 force_move:
174 		action         |= MSYS_DO_MOVE;
175 		MSYS_CurrentMX  = Xcoord;
176 		MSYS_CurrentMY  = Ycoord;
177 	}
178 
179 	MSYS_Action = action;
180 	if (action != MSYS_NO_ACTION) MSYS_UpdateMouseRegion();
181 }
182 
183 
184 //======================================================================================================
185 //	MSYS_TrashRegList
186 //
187 //	Deletes the entire region list.
188 //
MSYS_TrashRegList(void)189 static void MSYS_TrashRegList(void)
190 {
191 	while( MSYS_RegList )
192 	{
193 		if( MSYS_RegList->uiFlags & MSYS_REGION_EXISTS )
194 		{
195 			MSYS_RemoveRegion(MSYS_RegList);
196 		}
197 		else
198 		{
199 			MSYS_RegList = MSYS_RegList->next;
200 		}
201 	}
202 }
203 
204 
205 static void MSYS_DeleteRegionFromList(MOUSE_REGION*);
206 
207 
208 /* Add a region struct to the current list. The list is sorted by priority
209  * levels. If two entries have the same priority level, then the latest to enter
210  * the list gets the higher priority. */
MSYS_AddRegionToList(MOUSE_REGION * const r)211 static void MSYS_AddRegionToList(MOUSE_REGION* const r)
212 {
213 	/* If region seems to already be in list, delete it so we can re-insert the
214 	 * region. */
215 	MSYS_DeleteRegionFromList(r);
216 
217 	MOUSE_REGION* i = MSYS_RegList;
218 	if (!i)
219 	{ // Empty list, so add it straight up.
220 		MSYS_RegList = r;
221 	}
222 	else
223 	{
224 		// Walk down list until we find place to insert (or at end of list)
225 		for (; i->next; i = i->next)
226 		{
227 			if (i->PriorityLevel <= r->PriorityLevel) break;
228 		}
229 
230 		if (i->PriorityLevel > r->PriorityLevel)
231 		{ // Add after node
232 			r->prev = i;
233 			r->next = i->next;
234 			if (r->next) r->next->prev = r;
235 			i->next = r;
236 		}
237 		else
238 		{ // Add before node
239 			r->prev = i->prev;
240 			r->next = i;
241 			*(r->prev ? &r->prev->next : &MSYS_RegList) = r;
242 			i->prev = r;
243 		}
244 	}
245 }
246 
247 
248 // Removes a region from the current list.
MSYS_DeleteRegionFromList(MOUSE_REGION * const r)249 static void MSYS_DeleteRegionFromList(MOUSE_REGION* const r)
250 {
251 	MOUSE_REGION* const prev = r->prev;
252 	MOUSE_REGION* const next = r->next;
253 	if (prev) prev->next = next;
254 	if (next) next->prev = prev;
255 
256 	if (MSYS_RegList == r) MSYS_RegList = next;
257 
258 	r->prev = 0;
259 	r->next = 0;
260 }
261 
262 
263 /* Searches the list for the highest priority region and updates its info.  It
264  * also dispatches the callback functions */
MSYS_UpdateMouseRegion(void)265 static void MSYS_UpdateMouseRegion(void)
266 {
267 	MOUSE_REGION* cur;
268 	for (cur = MSYS_RegList; cur != NULL; cur = cur->next)
269 	{
270 		if (cur->uiFlags & (MSYS_REGION_ENABLED | MSYS_ALLOW_DISABLED_FASTHELP) &&
271 			cur->RegionTopLeftX <= MSYS_CurrentMX && MSYS_CurrentMX <= cur->RegionBottomRightX &&
272 			cur->RegionTopLeftY <= MSYS_CurrentMY && MSYS_CurrentMY <= cur->RegionBottomRightY)
273 		{
274 			/* We got the right region. We don't need to check for priorities because
275 			 * the whole list is sorted the right way! */
276 			break;
277 		}
278 	}
279 	MSYS_CurrRegion = cur;
280 
281 	MOUSE_REGION* prev = MSYS_PrevRegion;
282 	if (prev)
283 	{
284 		prev->uiFlags &= ~MSYS_MOUSE_IN_AREA;
285 
286 		if (prev != cur)
287 		{
288 			/* Remove the help text for the previous region if one is currently being
289 			 * displayed. */
290 			if (!prev->FastHelpText.empty())
291 			{
292 #ifdef _JA2_RENDER_DIRTY
293 				if (prev->uiFlags & MSYS_GOT_BACKGROUND)
294 				{
295 					FreeBackgroundRectPending(prev->FastHelpRect);
296 				}
297 #endif
298 				prev->uiFlags &= ~MSYS_GOT_BACKGROUND;
299 				prev->uiFlags &= ~MSYS_FASTHELP_RESET;
300 			}
301 
302 			/* Force a callbacks to happen on previous region to indicate that the
303 			 * mouse has left the old region */
304 			if (prev->MovementCallback != NULL && prev->uiFlags & MSYS_REGION_ENABLED)
305 			{
306 				prev->MovementCallback(prev, MSYS_CALLBACK_REASON_LOST_MOUSE);
307 			}
308 		}
309 	}
310 
311 	// If a region was found in the list, update its data
312 	if (cur != NULL)
313 	{
314 		if (cur != prev)
315 		{
316 			cur->FastHelpTimer = gsFastHelpDelay;
317 
318 			//Kris -- October 27, 1997
319 			//Implemented gain mouse region
320 			if (cur->MovementCallback != NULL)
321 			{
322 				if (!cur->FastHelpText.empty() && !(cur->uiFlags & MSYS_FASTHELP_RESET))
323 				{
324 #ifdef _JA2_RENDER_DIRTY
325 					if (cur->uiFlags & MSYS_GOT_BACKGROUND)
326 					{
327 						FreeBackgroundRectPending(cur->FastHelpRect);
328 					}
329 #endif
330 					cur->uiFlags &= ~MSYS_GOT_BACKGROUND;
331 					cur->uiFlags |= MSYS_FASTHELP_RESET;
332 				}
333 				if (cur->uiFlags & MSYS_REGION_ENABLED)
334 				{
335 					cur->MovementCallback(cur, MSYS_CALLBACK_REASON_GAIN_MOUSE);
336 				}
337 			}
338 
339 			// if the cursor is set and is not set to no cursor
340 			if (cur->uiFlags & MSYS_REGION_ENABLED && cur->Cursor != MSYS_NO_CURSOR)
341 			{
342 				MSYS_SetCurrentCursor(cur->Cursor);
343 			}
344 			else
345 			{
346 				/* Addition Oct 10/1997 Carter, patch for mouse cursor
347 				 * start at region and find another region encompassing */
348 				for (const MOUSE_REGION* i = cur->next; i != NULL; i = i->next)
349 				{
350 					if (i->uiFlags & MSYS_REGION_ENABLED &&
351 							i->RegionTopLeftX <= MSYS_CurrentMX && MSYS_CurrentMX <= i->RegionBottomRightX &&
352 							i->RegionTopLeftY <= MSYS_CurrentMY && MSYS_CurrentMY <= i->RegionBottomRightY &&
353 							i->Cursor != MSYS_NO_CURSOR)
354 					{
355 						MSYS_SetCurrentCursor(i->Cursor);
356 						break;
357 					}
358 				}
359 			}
360 		}
361 
362 		// OK, if we do not have a button down, any button is game!
363 		if (!g_clicked_region || g_clicked_region == cur)
364 		{
365 			cur->uiFlags |= MSYS_MOUSE_IN_AREA;
366 
367 			cur->MouseXPos    = MSYS_CurrentMX;
368 			cur->MouseYPos    = MSYS_CurrentMY;
369 			cur->RelativeXPos = MSYS_CurrentMX - cur->RegionTopLeftX;
370 			cur->RelativeYPos = MSYS_CurrentMY - cur->RegionTopLeftY;
371 
372 			cur->ButtonState = MSYS_CurrentButtons;
373 
374 			if (cur->uiFlags & MSYS_REGION_ENABLED &&
375 					cur->MovementCallback != NULL &&
376 					MSYS_Action & MSYS_DO_MOVE)
377 			{
378 				cur->MovementCallback(cur, MSYS_CALLBACK_REASON_MOVE);
379 			}
380 
381 			MSYS_Action &= ~MSYS_DO_MOVE;
382 
383 			if (cur->ButtonCallback != NULL && MSYS_Action & MSYS_DO_BUTTONS)
384 			{
385 				if (cur->uiFlags & MSYS_REGION_ENABLED)
386 				{
387 					UINT32 ButtonReason = MSYS_CALLBACK_REASON_NONE;
388 					if (MSYS_Action & MSYS_DO_LBUTTON_DWN)
389 					{
390 						ButtonReason |= MSYS_CALLBACK_REASON_LBUTTON_DWN;
391 						g_clicked_region = cur;
392 					}
393 
394 					if (MSYS_Action & MSYS_DO_LBUTTON_UP)
395 					{
396 						ButtonReason |= MSYS_CALLBACK_REASON_LBUTTON_UP;
397 						g_clicked_region = 0;
398 					}
399 
400 					if (MSYS_Action & MSYS_DO_RBUTTON_DWN)
401 					{
402 						ButtonReason |= MSYS_CALLBACK_REASON_RBUTTON_DWN;
403 						g_clicked_region = cur;
404 					}
405 
406 					if (MSYS_Action & MSYS_DO_RBUTTON_UP)
407 					{
408 						ButtonReason |= MSYS_CALLBACK_REASON_RBUTTON_UP;
409 						g_clicked_region = 0;
410 					}
411 
412 					if (MSYS_Action & MSYS_DO_MBUTTON_DWN)
413 					{
414 						ButtonReason |= MSYS_CALLBACK_REASON_MBUTTON_DWN;
415 						g_clicked_region = cur;
416 					}
417 
418 					if (MSYS_Action & MSYS_DO_MBUTTON_UP)
419 					{
420 						ButtonReason |= MSYS_CALLBACK_REASON_MBUTTON_UP;
421 						g_clicked_region = 0;
422 					}
423 
424 					// ATE: Added repeat resons....
425 					if (MSYS_Action & MSYS_DO_LBUTTON_REPEAT)
426 					{
427 						ButtonReason |= MSYS_CALLBACK_REASON_LBUTTON_REPEAT;
428 					}
429 
430 					if (MSYS_Action & MSYS_DO_RBUTTON_REPEAT)
431 					{
432 						ButtonReason |= MSYS_CALLBACK_REASON_RBUTTON_REPEAT;
433 					}
434 
435 					if (MSYS_Action & MSYS_DO_MBUTTON_REPEAT)
436 					{
437 						ButtonReason |= MSYS_CALLBACK_REASON_MBUTTON_REPEAT;
438 					}
439 
440 					if (MSYS_Action & MSYS_DO_WHEEL_UP)   ButtonReason |= MSYS_CALLBACK_REASON_WHEEL_UP;
441 					if (MSYS_Action & MSYS_DO_WHEEL_DOWN) ButtonReason |= MSYS_CALLBACK_REASON_WHEEL_DOWN;
442 
443 					if (ButtonReason != MSYS_CALLBACK_REASON_NONE)
444 					{
445 						if (cur->uiFlags & MSYS_FASTHELP)
446 						{
447 							// Button was clicked so remove any FastHelp text
448 							cur->uiFlags &= ~MSYS_FASTHELP;
449 #ifdef _JA2_RENDER_DIRTY
450 							if (cur->uiFlags & MSYS_GOT_BACKGROUND)
451 							{
452 								FreeBackgroundRectPending(cur->FastHelpRect);
453 							}
454 #endif
455 							cur->uiFlags &= ~MSYS_GOT_BACKGROUND;
456 							cur->uiFlags &= ~MSYS_FASTHELP_RESET;
457 
458 							cur->FastHelpTimer = gsFastHelpDelay;
459 						}
460 
461 						//Kris: Nov 31, 1999 -- Added support for double click events.
462 						//This is where double clicks are checked and passed down.
463 						if (ButtonReason == MSYS_CALLBACK_REASON_LBUTTON_DWN)
464 						{
465 							UINT32 uiCurrTime = GetClock();
466 							if (gpRegionLastLButtonDown == cur &&
467 									gpRegionLastLButtonUp   == cur &&
468 									uiCurrTime <= guiRegionLastLButtonDownTime + MSYS_DOUBLECLICK_DELAY)
469 							{
470 								/* Sequential left click on same button within the maximum time
471 								 * allowed for a double click.  Double click check succeeded,
472 								 * set flag and reset double click globals. */
473 								ButtonReason |= MSYS_CALLBACK_REASON_LBUTTON_DOUBLECLICK;
474 								gpRegionLastLButtonDown = NULL;
475 								gpRegionLastLButtonUp   = NULL;
476 								guiRegionLastLButtonDownTime = 0;
477 							}
478 							else
479 							{
480 								/* First click, record time and region pointer (to check if 2nd
481 								 * click detected later) */
482 								gpRegionLastLButtonDown = cur;
483 								guiRegionLastLButtonDownTime = GetClock();
484 							}
485 						}
486 						else if (ButtonReason == MSYS_CALLBACK_REASON_LBUTTON_UP)
487 						{
488 							UINT32 uiCurrTime = GetClock();
489 							if (gpRegionLastLButtonDown == cur &&
490 									uiCurrTime <= guiRegionLastLButtonDownTime + MSYS_DOUBLECLICK_DELAY)
491 							{
492 								/* Double click is Left down, then left up, then left down.  We
493 								 * have just detected the left up here (step 2). */
494 								gpRegionLastLButtonUp = cur;
495 							}
496 							else
497 							{
498 								/* User released mouse outside of current button, so kill any
499 								 * chance of a double click happening. */
500 								gpRegionLastLButtonDown = NULL;
501 								gpRegionLastLButtonUp   = NULL;
502 								guiRegionLastLButtonDownTime = 0;
503 							}
504 						}
505 
506 						cur->ButtonCallback(cur, ButtonReason);
507 					}
508 				}
509 			}
510 
511 			MSYS_Action &= ~MSYS_DO_BUTTONS;
512 		}
513 		else if (cur->uiFlags & MSYS_REGION_ENABLED)
514 		{
515 			// OK here, if we have release a button, UNSET LOCK wherever you are....
516 			// Just don't give this button the message....
517 			if (MSYS_Action & MSYS_DO_RBUTTON_UP) g_clicked_region = 0;
518 			if (MSYS_Action & MSYS_DO_LBUTTON_UP) g_clicked_region = 0;
519 			if (MSYS_Action & MSYS_DO_MBUTTON_UP) g_clicked_region = 0;
520 
521 			// OK, you still want move messages however....
522 			cur->uiFlags |= MSYS_MOUSE_IN_AREA;
523 			cur->MouseXPos = MSYS_CurrentMX;
524 			cur->MouseYPos = MSYS_CurrentMY;
525 			cur->RelativeXPos = MSYS_CurrentMX - cur->RegionTopLeftX;
526 			cur->RelativeYPos = MSYS_CurrentMY - cur->RegionTopLeftY;
527 
528 			if (cur->MovementCallback != NULL && MSYS_Action & MSYS_DO_MOVE)
529 			{
530 				cur->MovementCallback(cur, MSYS_CALLBACK_REASON_MOVE);
531 			}
532 
533 			MSYS_Action &= ~MSYS_DO_MOVE;
534 		}
535 	}
536 	/* the current region can get deleted during this function, so fetch the
537 	 * latest value here */
538 	MSYS_PrevRegion = MSYS_CurrRegion;
539 }
540 
541 
542 
543 /* Inits a MOUSE_REGION structure for use with the mouse system */
MSYS_DefineRegion(MOUSE_REGION * const r,UINT16 const tlx,UINT16 const tly,UINT16 const brx,UINT16 const bry,INT8 priority,UINT16 const crsr,MOUSE_CALLBACK const movecallback,MOUSE_CALLBACK const buttoncallback)544 void MSYS_DefineRegion(MOUSE_REGION* const r, UINT16 const tlx, UINT16 const tly, UINT16 const brx, UINT16 const bry, INT8 priority, UINT16 const crsr, MOUSE_CALLBACK const movecallback, MOUSE_CALLBACK const buttoncallback)
545 {
546 #ifdef MOUSESYSTEM_DEBUGGING
547 	AssertMsg(!(r->uiFlags & MSYS_REGION_EXISTS), "Attempting to define a region that already exists.");
548 #endif
549 
550 	if (priority <= MSYS_PRIORITY_LOWEST) priority = MSYS_PRIORITY_LOWEST;
551 
552 	r->PriorityLevel      = priority;
553 	r->uiFlags            = MSYS_REGION_ENABLED | MSYS_REGION_EXISTS;
554 	r->RegionTopLeftX     = tlx;
555 	r->RegionTopLeftY     = tly;
556 	r->RegionBottomRightX = brx;
557 	r->RegionBottomRightY = bry;
558 	r->MouseXPos          = 0;
559 	r->MouseYPos          = 0;
560 	r->RelativeXPos       = 0;
561 	r->RelativeYPos       = 0;
562 	r->ButtonState        = 0;
563 	r->Cursor             = crsr;
564 	r->MovementCallback   = movecallback;
565 	r->ButtonCallback     = buttoncallback;
566 	r->FastHelpTimer      = 0;
567 	r->FastHelpText       = ST::null;
568 	r->next               = 0;
569 	r->prev               = 0;
570 
571 	MSYS_AddRegionToList(r);
572 	gfRefreshUpdate = TRUE;
573 }
574 
575 
ChangeCursor(UINT16 const crsr)576 void MOUSE_REGION::ChangeCursor(UINT16 const crsr)
577 {
578 	Cursor = crsr;
579 	if (crsr != MSYS_NO_CURSOR && uiFlags & MSYS_MOUSE_IN_AREA)
580 	{
581 		MSYS_SetCurrentCursor(crsr);
582 	}
583 }
584 
585 
MSYS_RemoveRegion(MOUSE_REGION * const r)586 void MSYS_RemoveRegion(MOUSE_REGION* const r)
587 {
588 #ifdef MOUSESYSTEM_DEBUGGING
589 	AssertMsg(r, "Attempting to remove a NULL region.");
590 #endif
591 	if (!r) return;
592 #ifdef MOUSESYSTEM_DEBUGGING
593 	AssertMsg(r->uiFlags & MSYS_REGION_EXISTS, "Attempting to remove an already removed region.");
594 #endif
595 
596 #ifdef _JA2_RENDER_DIRTY
597 	if (r->uiFlags & MSYS_HAS_BACKRECT)
598 	{
599 		FreeBackgroundRectPending(r->FastHelpRect);
600 		r->uiFlags &= ~MSYS_HAS_BACKRECT;
601 	}
602 #endif
603 
604 	r->FastHelpText = ST::null;
605 
606 	MSYS_DeleteRegionFromList(r);
607 
608 	if (MSYS_PrevRegion  == r) MSYS_PrevRegion  = 0;
609 	if (MSYS_CurrRegion  == r) MSYS_CurrRegion  = 0;
610 	if (g_clicked_region == r) g_clicked_region = 0;
611 
612 	gfRefreshUpdate = TRUE;
613 	*r = MOUSE_REGION{};
614 }
615 
616 
617 //=================================================================================================
618 //	MSYS_SetCurrentCursor
619 //
620 //	Sets the mouse cursor to the regions defined value.
621 //
MSYS_SetCurrentCursor(UINT16 Cursor)622 void MSYS_SetCurrentCursor(UINT16 Cursor)
623 {
624 	SetCurrentCursorFromDatabase( Cursor );
625 }
626 
627 
MSYS_SetRegionUserData(MOUSE_REGION * const r,UINT32 const index,INT32 const userdata)628 void MSYS_SetRegionUserData(MOUSE_REGION* const r, UINT32 const index, INT32 const userdata)
629 {
630 	if (lengthof(r->user.data) <= index) throw std::logic_error("User data index is out of range");
631 	r->user.data[index] = userdata;
632 }
633 
634 
MSYS_GetRegionUserData(MOUSE_REGION const * const r,UINT32 const index)635 INT32 MSYS_GetRegionUserData(MOUSE_REGION const* const r, UINT32 const index)
636 {
637 	if (lengthof(r->user.data) <= index) throw std::logic_error("User data index is out of range");
638 	return r->user.data[index];
639 }
640 
641 
642 // This function will force a re-evaluation of mouse regions
643 // Usually used to force change of mouse cursor if panels switch, etc
RefreshMouseRegions()644 void RefreshMouseRegions( )
645 {
646 	MSYS_Action|=MSYS_DO_MOVE;
647 
648 	MSYS_UpdateMouseRegion( );
649 
650 }
651 
652 
SetFastHelpText(const ST::string & str)653 void MOUSE_REGION::SetFastHelpText(const ST::string& str)
654 {
655 	FastHelpText = ST::null;
656 
657 	if (!(uiFlags & MSYS_REGION_EXISTS)) return;
658 
659 	if (str.empty()) return;
660 
661 	FastHelpText = str.to_utf32();
662 
663 	/* ATE: We could be replacing already existing, active text so let's remove
664 	 * the region so it be rebuilt */
665 
666 	if (guiCurrentScreen == MAP_SCREEN) return;
667 
668 #ifdef _JA2_RENDER_DIRTY
669 	if (uiFlags & MSYS_GOT_BACKGROUND) FreeBackgroundRectPending(FastHelpRect);
670 #endif
671 
672 	uiFlags &= ~MSYS_GOT_BACKGROUND;
673 	uiFlags &= ~MSYS_FASTHELP_RESET;
674 }
675 
676 
GetNumberOfLinesInHeight(const ST::utf32_buffer & codepoints)677 static UINT32 GetNumberOfLinesInHeight(const ST::utf32_buffer& codepoints)
678 {
679 	UINT32 Lines = 1;
680 	for (const char32_t* i = codepoints.c_str(); *i != U'\0'; i++)
681 	{
682 		if (*i == U'\n') Lines++;
683 	}
684 	return Lines;
685 }
686 
687 
688 static UINT32 GetWidthOfString(const ST::utf32_buffer& codepoints);
689 static void DisplayHelpTokenizedString(const ST::utf32_buffer& codepoints, INT16 sx, INT16 sy);
690 
691 
DisplayFastHelp(MOUSE_REGION * const r)692 static void DisplayFastHelp(MOUSE_REGION* const r)
693 {
694 	if (!(r->uiFlags & MSYS_FASTHELP)) return;
695 
696 	INT32 const w = GetWidthOfString(r->FastHelpText) + 10;
697 	INT32 const h = GetNumberOfLinesInHeight(r->FastHelpText) * (GetFontHeight(FONT10ARIAL) + 1) + 8;
698 
699 	INT32 x = r->RegionTopLeftX + 10;
700 	if (x <  0)                x = 0;
701 	if (x >= SCREEN_WIDTH - w) x = SCREEN_WIDTH - w - 4;
702 
703 	INT32 y = r->RegionTopLeftY - h * 3 / 4;
704 	if (y <  0)                 y = 0;
705 	if (y >= SCREEN_HEIGHT - h) y = SCREEN_HEIGHT - h - 15;
706 
707 	if (!(r->uiFlags & MSYS_GOT_BACKGROUND))
708 	{
709 		r->FastHelpRect = RegisterBackgroundRect(BGND_FLAG_PERMANENT | BGND_FLAG_SAVERECT, x, y, w, h);
710 		r->uiFlags |= MSYS_GOT_BACKGROUND | MSYS_HAS_BACKRECT;
711 	}
712 	else
713 	{
714 		{ SGPVSurface::Lock l(FRAME_BUFFER);
715 			UINT16* const buf = l.Buffer<UINT16>();
716 			SetClippingRegionAndImageWidth(l.Pitch(), 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
717 			RectangleDraw(TRUE, x + 1, y + 1, x + w - 1, y + h - 1, Get16BPPColor(FROMRGB( 65,  57, 15)), buf);
718 			RectangleDraw(TRUE, x,     y,     x + w - 2, y + h - 2, Get16BPPColor(FROMRGB(227, 198, 88)), buf);
719 		}
720 		FRAME_BUFFER->ShadowRect(x + 2, y + 2, x + w - 3, y + h - 3);
721 		FRAME_BUFFER->ShadowRect(x + 2, y + 2, x + w - 3, y + h - 3);
722 
723 		DisplayHelpTokenizedString(r->FastHelpText, x + 5, y + 5);
724 		InvalidateRegion(x, y, x + w, y + h);
725 	}
726 }
727 
728 
GetWidthOfString(const ST::utf32_buffer & codepoints)729 static UINT32 GetWidthOfString(const ST::utf32_buffer& codepoints)
730 {
731 	SGPFont const bold_font   = FONT10ARIALBOLD;
732 	SGPFont const normal_font = FONT10ARIAL;
733 	UINT32     max_w       = 0;
734 	UINT32     w           = 0;
735 	for (const char32_t* i = codepoints.c_str();; ++i)
736 	{
737 		char32_t c = *i;
738 		SGPFont font;
739 		switch (c)
740 		{
741 			case U'\0':
742 				return MAX(w, max_w);
743 
744 			case U'\n':
745 				max_w = MAX(w, max_w);
746 				w     = 0;
747 				continue;
748 
749 			case U'|':
750 				c    = *++i;
751 				font = bold_font;
752 				break;
753 
754 			default:
755 				font = normal_font;
756 				break;
757 		}
758 		w += GetCharWidth(font, c);
759 	}
760 }
761 
762 
DisplayHelpTokenizedString(const ST::utf32_buffer & codepoints,INT16 const sx,INT16 const sy)763 static void DisplayHelpTokenizedString(const ST::utf32_buffer& codepoints, INT16 const sx, INT16 const sy)
764 {
765 	SGPFont const bold_font   = FONT10ARIALBOLD;
766 	SGPFont const normal_font = FONT10ARIAL;
767 	INT32   const h           = GetFontHeight(normal_font) + 1;
768 	INT32         x           = sx;
769 	INT32         y           = sy;
770 	for (const char32_t* i = codepoints.c_str();; ++i)
771 	{
772 		char32_t c = *i;
773 		SGPFont font;
774 		UINT8   foreground;
775 		switch (c)
776 		{
777 			case U'\0': return;
778 
779 			case U'\n':
780 				x  = sx;
781 				y += h;
782 				continue;
783 
784 			case U'|':
785 				c = *++i;
786 				font       = bold_font;
787 				foreground = 146;
788 				break;
789 
790 			default:
791 				font       = normal_font;
792 				foreground = FONT_BEIGE;
793 				break;
794 		}
795 		SetFontAttributes(font, foreground);
796 		x += MPrintChar(x, y, c);
797 	}
798 }
799 
RenderFastHelp()800 void RenderFastHelp()
801 {
802 	static UINT32 last_clock;
803 
804 	if (!gfRenderHilights) return;
805 
806 	UINT32 const current_clock = GetClock();
807 	UINT32 const time_delta    = current_clock - last_clock;
808 	last_clock = current_clock;
809 
810 	MOUSE_REGION* const r = MSYS_CurrRegion;
811 	if (!r || r->FastHelpText.empty()) return;
812 
813 	if (r->uiFlags & (MSYS_ALLOW_DISABLED_FASTHELP | MSYS_REGION_ENABLED))
814 	{
815 		if (r->FastHelpTimer == 0)
816 		{
817 			if (r->uiFlags & MSYS_MOUSE_IN_AREA)
818 			{
819 				r->uiFlags |= MSYS_FASTHELP;
820 			}
821 			else
822 			{
823 				r->uiFlags &= ~(MSYS_FASTHELP | MSYS_FASTHELP_RESET);
824 			}
825 			DisplayFastHelp(r);
826 		}
827 		else
828 		{
829 			if (r->uiFlags & MSYS_MOUSE_IN_AREA && r->ButtonState == 0)
830 			{
831 				r->FastHelpTimer -= time_delta;
832 				if (r->FastHelpTimer < 0) r->FastHelpTimer = 0;
833 			}
834 		}
835 	}
836 }
837 
838 
AllowDisabledRegionFastHelp(bool const allow)839 void MOUSE_REGION::AllowDisabledRegionFastHelp(bool const allow)
840 {
841 	if (allow)
842 	{
843 		uiFlags |= MSYS_ALLOW_DISABLED_FASTHELP;
844 	}
845 	else
846 	{
847 		uiFlags &= ~MSYS_ALLOW_DISABLED_FASTHELP;
848 	}
849 }
850