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