1 // for finding memory leaks in debug mode with Visual Studio
2 #if defined _DEBUG && defined _MSC_VER
3 #include <crtdbg.h>
4 #endif
5
6 #include <stdio.h>
7 #include <stdbool.h>
8 #include "ft2_header.h"
9 #include "ft2_gui.h"
10 #include "ft2_video.h"
11 #include "scopes/ft2_scopes.h"
12 #include "ft2_help.h"
13 #include "ft2_sample_ed.h"
14 #include "ft2_inst_ed.h"
15 #include "ft2_pattern_ed.h"
16 #include "ft2_mouse.h"
17 #include "ft2_config.h"
18 #include "ft2_diskop.h"
19 #include "ft2_audioselector.h"
20 #include "ft2_midi.h"
21 #include "ft2_bmp.h"
22 #include "ft2_structs.h"
23
24 #define NUM_CURSORS 6
25
26 mouse_t mouse; // globalized
27
28 static bool mouseBusyGfxBackwards;
29 static int16_t mouseShape;
30 static int32_t mouseModeGfxOffs, mouseBusyGfxFrame;
31 static SDL_Cursor *cursors[NUM_CURSORS];
32
setSystemCursor(SDL_Cursor * cur)33 static bool setSystemCursor(SDL_Cursor *cur)
34 {
35 if (cur == NULL)
36 {
37 SDL_SetCursor(SDL_GetDefaultCursor());
38 return false;
39 }
40
41 SDL_SetCursor(cur);
42 return true;
43 }
44
freeMouseCursors(void)45 void freeMouseCursors(void)
46 {
47 SDL_SetCursor(SDL_GetDefaultCursor());
48 for (int32_t i = 0; i < NUM_CURSORS; i++)
49 {
50 if (cursors[i] != NULL)
51 {
52 SDL_FreeCursor(cursors[i]);
53 cursors[i] = NULL;
54 }
55 }
56 }
57
createMouseCursors(void)58 bool createMouseCursors(void) // creates scaled SDL surfaces for current mouse pointer shape
59 {
60 freeMouseCursors();
61
62 const uint8_t *cursorsSrc = bmp.mouseCursors;
63 switch (config.mouseType)
64 {
65 case MOUSE_IDLE_SHAPE_NICE: cursorsSrc += 0 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
66 case MOUSE_IDLE_SHAPE_UGLY: cursorsSrc += 1 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
67 case MOUSE_IDLE_SHAPE_AWFUL: cursorsSrc += 2 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
68 case MOUSE_IDLE_SHAPE_USABLE: cursorsSrc += 3 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
69 default: break;
70 }
71
72 for (int32_t i = 0; i < NUM_CURSORS; i++)
73 {
74 const int32_t scaleFactor = video.yScale;
75
76 SDL_Surface *surface = SDL_CreateRGBSurface(0, MOUSE_CURSOR_W*scaleFactor, MOUSE_CURSOR_H*scaleFactor, 32, 0, 0, 0, 0);
77 if (surface == NULL)
78 {
79 freeMouseCursors();
80 config.specialFlags2 &= ~HARDWARE_MOUSE; // enable software mouse
81 SDL_ShowCursor(SDL_FALSE);
82 return false;
83 }
84
85 const uint32_t colorkey = SDL_MapRGB(surface->format, 0x00, 0xFF, 0x00); // colorkey
86 const uint32_t fg = SDL_MapRGB(surface->format, 0xFF, 0xFF, 0xFF); // foreground
87 const uint32_t border = SDL_MapRGB(surface->format, 0x00, 0x00, 0x00); // border
88
89 SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_NONE);
90 SDL_SetColorKey(surface, SDL_TRUE, colorkey);
91 SDL_SetSurfaceRLE(surface, SDL_TRUE);
92
93 const uint8_t *srcPixels8;
94 if (i == 3) // text edit cursor
95 srcPixels8 = &bmp.mouseCursors[12 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)];
96 else if (i == 4) // mouse busy (wall clock)
97 srcPixels8 = &bmp.mouseCursorBusyClock[2 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)]; // pick a good still-frame
98 else if (i == 5) // mouse busy (hourglass)
99 srcPixels8 = &bmp.mouseCursorBusyGlass[2 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)]; // pick a good still-frame
100 else // normal idle cursor + disk op. "delete/rename" cursors
101 srcPixels8 = &cursorsSrc[i * (4 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H))];
102
103 SDL_LockSurface(surface);
104
105 uint32_t *dstPixels32 = (uint32_t *)surface->pixels;
106 for (int32_t k = 0; k < surface->w*surface->h; k++) // fill surface with colorkey pixels
107 dstPixels32[k] = colorkey;
108
109 // blit upscaled cursor to surface
110 for (int32_t y = 0; y < MOUSE_CURSOR_H; y++)
111 {
112 uint32_t *outX = &dstPixels32[(y * scaleFactor) * surface->w];
113 for (int32_t yScale = 0; yScale < scaleFactor; yScale++)
114 {
115 const uint8_t *srcPtr = &srcPixels8[y * MOUSE_CURSOR_W];
116 for (int32_t x = 0; x < MOUSE_CURSOR_W; x++)
117 {
118 const uint8_t srcPix = srcPtr[x];
119 if (srcPix != PAL_TRANSPR)
120 {
121 uint32_t pixel = colorkey;
122 if (srcPix == PAL_MOUSEPT)
123 pixel = fg;
124 else if (srcPix == PAL_BCKGRND)
125 pixel = border;
126
127 for (int32_t xScale = 0; xScale < scaleFactor; xScale++)
128 outX[xScale] = pixel;
129 }
130
131 outX += scaleFactor;
132 }
133 }
134 }
135 SDL_UnlockSurface(surface);
136
137 uint32_t hotX = 0;
138 uint32_t hotY = 0;
139
140 if (i == 3) // text edit cursor bias
141 {
142 hotX = 2 * video.xScale;
143 hotY = 6 * video.yScale;
144 }
145
146 cursors[i] = SDL_CreateColorCursor(surface, hotX, hotY);
147 if (cursors[i] == NULL)
148 {
149 SDL_FreeSurface(surface);
150 freeMouseCursors();
151 config.specialFlags2 &= ~HARDWARE_MOUSE; // enable software mouse
152 SDL_ShowCursor(SDL_FALSE);
153 return false;
154 }
155
156 SDL_FreeSurface(surface);
157 }
158
159 if (config.specialFlags2 & HARDWARE_MOUSE)
160 {
161 if (mouse.mode == MOUSE_MODE_NORMAL) setSystemCursor(cursors[0]);
162 else if (mouse.mode == MOUSE_MODE_DELETE) setSystemCursor(cursors[1]);
163 else if (mouse.mode == MOUSE_MODE_RENAME) setSystemCursor(cursors[2]);
164
165 SDL_ShowCursor(SDL_TRUE);
166 }
167 else
168 {
169 SDL_ShowCursor(SDL_FALSE);
170 }
171
172 return true;
173 }
174
setMousePosToCenter(void)175 void setMousePosToCenter(void)
176 {
177 if (video.fullscreen)
178 {
179 mouse.setPosX = video.displayW >> 1;
180 mouse.setPosY = video.displayH >> 1;
181 }
182 else
183 {
184 mouse.setPosX = video.renderW >> 1;
185 mouse.setPosY = video.renderH >> 1;
186 }
187
188 mouse.setPosFlag = true;
189 }
190
animateBusyMouse(void)191 void animateBusyMouse(void)
192 {
193 if (config.mouseAnimType == MOUSE_BUSY_SHAPE_CLOCK)
194 {
195 if (config.specialFlags2 & HARDWARE_MOUSE)
196 {
197 setSystemCursor(cursors[4]);
198 return;
199 }
200
201 if ((editor.framesPassed % 7) == 6)
202 {
203 if (mouseBusyGfxBackwards)
204 {
205 if (--mouseBusyGfxFrame <= 0)
206 {
207 mouseBusyGfxFrame = 0;
208 mouseBusyGfxBackwards = false;
209 }
210 }
211 else
212 {
213 if (++mouseBusyGfxFrame >= MOUSE_CLOCK_ANI_FRAMES-1)
214 {
215 mouseBusyGfxFrame = MOUSE_CLOCK_ANI_FRAMES - 1;
216 mouseBusyGfxBackwards = true;
217 }
218 }
219
220 changeSpriteData(SPRITE_MOUSE_POINTER,
221 &bmp.mouseCursorBusyClock[(mouseBusyGfxFrame % MOUSE_CLOCK_ANI_FRAMES) * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)]);
222 }
223 }
224 else
225 {
226 if (config.specialFlags2 & HARDWARE_MOUSE)
227 {
228 setSystemCursor(cursors[5]);
229 return;
230 }
231
232 if ((editor.framesPassed % 5) == 4)
233 {
234 mouseBusyGfxFrame = (mouseBusyGfxFrame + 1) % MOUSE_GLASS_ANI_FRAMES;
235
236 changeSpriteData(SPRITE_MOUSE_POINTER,
237 &bmp.mouseCursorBusyGlass[mouseBusyGfxFrame * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)]);
238 }
239 }
240 }
241
setMouseShape(int16_t shape)242 void setMouseShape(int16_t shape)
243 {
244 const uint8_t *gfxPtr;
245
246 if (editor.busy)
247 {
248 if (config.mouseAnimType == MOUSE_BUSY_SHAPE_CLOCK)
249 gfxPtr = &bmp.mouseCursorBusyClock[(mouseBusyGfxFrame % MOUSE_GLASS_ANI_FRAMES) * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)];
250 else
251 gfxPtr = &bmp.mouseCursorBusyGlass[(mouseBusyGfxFrame % MOUSE_CLOCK_ANI_FRAMES) * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)];
252 }
253 else
254 {
255 gfxPtr = &bmp.mouseCursors[mouseModeGfxOffs];
256 switch (shape)
257 {
258 case MOUSE_IDLE_SHAPE_NICE: gfxPtr += 0 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
259 case MOUSE_IDLE_SHAPE_UGLY: gfxPtr += 1 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
260 case MOUSE_IDLE_SHAPE_AWFUL: gfxPtr += 2 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
261 case MOUSE_IDLE_SHAPE_USABLE: gfxPtr += 3 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
262 case MOUSE_IDLE_TEXT_EDIT: gfxPtr += 12 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break;
263 default: return;
264 }
265 }
266
267 mouseShape = shape;
268 changeSpriteData(SPRITE_MOUSE_POINTER, gfxPtr);
269
270 if (config.specialFlags2 & HARDWARE_MOUSE)
271 {
272 if (mouse.mode == MOUSE_MODE_NORMAL) setSystemCursor(cursors[0]);
273 else if (mouse.mode == MOUSE_MODE_DELETE) setSystemCursor(cursors[1]);
274 else if (mouse.mode == MOUSE_MODE_RENAME) setSystemCursor(cursors[2]);
275 }
276 }
277
setTextEditMouse(void)278 static void setTextEditMouse(void)
279 {
280 setMouseShape(MOUSE_IDLE_TEXT_EDIT);
281 mouse.xBias = -2;
282 mouse.yBias = -6;
283
284 if (config.specialFlags2 & HARDWARE_MOUSE)
285 setSystemCursor(cursors[3]);
286 }
287
clearTextEditMouse(void)288 static void clearTextEditMouse(void)
289 {
290 setMouseShape(config.mouseType);
291 mouse.xBias = 0;
292 mouse.yBias = 0;
293
294 if (config.specialFlags2 & HARDWARE_MOUSE)
295 setSystemCursor(cursors[0]);
296 }
297
changeCursorIfOverTextBoxes(void)298 static void changeCursorIfOverTextBoxes(void)
299 {
300 int32_t i;
301
302 mouse.mouseOverTextBox = false;
303 if (editor.busy || mouse.mode != MOUSE_MODE_NORMAL)
304 return;
305
306 const int32_t mx = mouse.x;
307 const int32_t my = mouse.y;
308
309 textBox_t *t = textBoxes;
310 for (i = 0; i < NUM_TEXTBOXES; i++, t++)
311 {
312 if (ui.sysReqShown && i != 0) // Sys. Req can only have one (special) text box
313 continue;
314
315 if (!t->visible)
316 continue;
317
318 if (!t->changeMouseCursor && (!editor.editTextFlag || i != mouse.lastEditBox))
319 continue; // some kludge of sorts
320
321 if (my >= t->y && my < t->y+t->h && mx >= t->x && mx < t->x+t->w)
322 {
323 mouse.mouseOverTextBox = true;
324
325 if (mouseShape != MOUSE_IDLE_TEXT_EDIT)
326 setTextEditMouse();
327
328 return;
329 }
330 }
331
332 // we're not inside a text edit box, set back mouse cursor
333 if (i == NUM_TEXTBOXES && mouseShape == MOUSE_IDLE_TEXT_EDIT)
334 clearTextEditMouse();
335 }
336
setMouseMode(uint8_t mode)337 void setMouseMode(uint8_t mode)
338 {
339 switch (mode)
340 {
341 case MOUSE_MODE_NORMAL: { mouse.mode = mode; mouseModeGfxOffs = 0 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); } break;
342 case MOUSE_MODE_DELETE: { mouse.mode = mode; mouseModeGfxOffs = 4 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); } break;
343 case MOUSE_MODE_RENAME: { mouse.mode = mode; mouseModeGfxOffs = 8 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); } break;
344
345 default: return;
346 }
347
348 setMouseShape(config.mouseType);
349 }
350
resetMouseBusyAnimation(void)351 void resetMouseBusyAnimation(void)
352 {
353 mouseBusyGfxBackwards = false;
354 mouseBusyGfxFrame = 0;
355 }
356
setMouseBusy(bool busy)357 void setMouseBusy(bool busy) // can be called from other threads
358 {
359 if (busy)
360 {
361 ui.setMouseIdle = false;
362 ui.setMouseBusy = true;
363 }
364 else
365 {
366 ui.setMouseBusy = false;
367 ui.setMouseIdle = true;
368 }
369 }
370
mouseAnimOn(void)371 void mouseAnimOn(void)
372 {
373 ui.setMouseBusy = false;
374 ui.setMouseIdle = false;
375
376 editor.busy = true;
377 setMouseShape(config.mouseAnimType);
378 }
379
mouseAnimOff(void)380 void mouseAnimOff(void)
381 {
382 ui.setMouseBusy = false;
383 ui.setMouseIdle = false;
384
385 editor.busy = false;
386 setMouseShape(config.mouseType);
387 }
388
mouseWheelDecRow(void)389 static void mouseWheelDecRow(void)
390 {
391 if (songPlaying)
392 return;
393
394 int16_t row = editor.row - 1;
395 if (row < 0)
396 row = patternNumRows[editor.editPattern] - 1;
397
398 setPos(-1, row, true);
399 }
400
mouseWheelIncRow(void)401 static void mouseWheelIncRow(void)
402 {
403 if (songPlaying)
404 return;
405
406 int16_t row = editor.row + 1;
407 if (row >= patternNumRows[editor.editPattern])
408 row = 0;
409
410 setPos(-1, row, true);
411 }
412
mouseWheelHandler(bool directionUp)413 void mouseWheelHandler(bool directionUp)
414 {
415 if (ui.sysReqShown || editor.editTextFlag)
416 return;
417
418 if (ui.extended)
419 {
420 if (mouse.y <= 52)
421 {
422 if (mouse.x <= 111) directionUp ? decSongPos() : incSongPos();
423 else if (mouse.x >= 386) directionUp ? decCurIns() : incCurIns();
424 }
425 else
426 {
427 directionUp ? mouseWheelDecRow() : mouseWheelIncRow();
428 }
429
430 return;
431 }
432
433 if (mouse.y < 173)
434 {
435 // top screens
436
437 if (ui.helpScreenShown)
438 {
439 // help screen
440
441 if (directionUp)
442 {
443 helpScrollUp();
444 helpScrollUp();
445 }
446 else
447 {
448 helpScrollDown();
449 helpScrollDown();
450 }
451 }
452 else if (ui.diskOpShown)
453 {
454 // disk op - 3x speed
455 if (mouse.x <= 355)
456 {
457 if (directionUp)
458 {
459 pbDiskOpListUp();
460 pbDiskOpListUp();
461 pbDiskOpListUp();
462 }
463 else
464 {
465 pbDiskOpListDown();
466 pbDiskOpListDown();
467 pbDiskOpListDown();
468 }
469 }
470 }
471 else if (ui.configScreenShown)
472 {
473 if (editor.currConfigScreen == CONFIG_SCREEN_IO_DEVICES)
474 {
475 // audio device selectors
476 if (mouse.x >= 110 && mouse.x <= 355 && mouse.y <= 173)
477 {
478 if (mouse.y < 87)
479 directionUp ? scrollAudOutputDevListUp() : scrollAudOutputDevListDown();
480 else
481 directionUp ? scrollAudInputDevListUp() : scrollAudInputDevListDown();
482 }
483 }
484 #ifdef HAS_MIDI
485 else if (editor.currConfigScreen == CONFIG_SCREEN_MIDI_INPUT)
486 {
487 // midi input device selector
488 if (mouse.x >= 110 && mouse.x <= 503 && mouse.y <= 173)
489 directionUp ? scrollMidiInputDevListUp() : scrollMidiInputDevListDown();
490 }
491 #endif
492 }
493
494 if (!ui.aboutScreenShown && !ui.helpScreenShown &&
495 !ui.configScreenShown && !ui.nibblesShown)
496 {
497 if (mouse.x >= 421 && mouse.y <= 173)
498 {
499 if (mouse.y <= 93) directionUp ? decCurIns() : incCurIns();
500 else if (mouse.y >= 94) directionUp ? decCurSmp() : incCurSmp();
501 }
502 else if (!ui.diskOpShown && mouse.x <= 111 && mouse.y <= 76)
503 {
504 directionUp ? decSongPos() : incSongPos();
505 }
506 }
507 }
508 else
509 {
510 // bottom screens
511
512 if (ui.sampleEditorShown)
513 {
514 if (mouse.y >= 174 && mouse.y <= 328)
515 directionUp ? mouseZoomSampleDataIn() : mouseZoomSampleDataOut();
516 }
517 else if (ui.patternEditorShown)
518 {
519 directionUp ? mouseWheelDecRow() : mouseWheelIncRow();
520 }
521 }
522 }
523
testSamplerDataMouseDown(void)524 static bool testSamplerDataMouseDown(void)
525 {
526 if (ui.sampleEditorShown && mouse.y >= 174 && mouse.y <= 327 && ui.sampleDataOrLoopDrag == -1)
527 {
528 handleSampleDataMouseDown(false);
529 return true;
530 }
531
532 return false;
533 }
534
testPatternDataMouseDown(void)535 static bool testPatternDataMouseDown(void)
536 {
537 if (ui.patternEditorShown)
538 {
539 const int32_t y1 = ui.extended ? 56 : 176;
540 const int32_t y2 = ui.pattChanScrollShown ? 382 : 396;
541
542 if (mouse.y >= y1 && mouse.y <= y2 && mouse.x >= 29 && mouse.x <= 602)
543 {
544 handlePatternDataMouseDown(false);
545 return true;
546 }
547 }
548
549 return false;
550 }
551
mouseButtonUpHandler(uint8_t mouseButton)552 void mouseButtonUpHandler(uint8_t mouseButton)
553 {
554 if (mouseButton == SDL_BUTTON_LEFT)
555 {
556 mouse.leftButtonPressed = false;
557 mouse.leftButtonReleased = true;
558
559 if (ui.leftLoopPinMoving)
560 {
561 setLeftLoopPinState(false);
562 ui.leftLoopPinMoving = false;
563 }
564
565 if (ui.rightLoopPinMoving)
566 {
567 setRightLoopPinState(false);
568 ui.rightLoopPinMoving = false;
569 }
570 }
571 else if (mouseButton == SDL_BUTTON_RIGHT)
572 {
573 mouse.rightButtonPressed = false;
574 mouse.rightButtonReleased = true;
575
576 if (editor.editSampleFlag)
577 {
578 // right mouse button released after hand-editing sample data
579 if (instr[editor.curInstr] != NULL)
580 fixSample(&instr[editor.curInstr]->smp[editor.curSmp]);
581
582 resumeAudio();
583
584 if (ui.sampleEditorShown)
585 writeSample(true);
586
587 setSongModifiedFlag();
588
589 editor.editSampleFlag = false;
590 }
591 }
592
593 mouse.firstTimePressingButton = false;
594 mouse.buttonCounter = 0;
595 editor.textCursorBlinkCounter = 0;
596
597 // if we used both mouse button at the same time and released *one*, don't release GUI object
598 if ( mouse.leftButtonPressed && !mouse.rightButtonPressed) return;
599 if (!mouse.leftButtonPressed && mouse.rightButtonPressed) return;
600
601 if (ui.sampleEditorShown)
602 testSmpEdMouseUp();
603
604 mouse.lastX = 0;
605 mouse.lastY = 0;
606
607 ui.sampleDataOrLoopDrag = -1;
608
609 // check if we released a GUI object
610 testDiskOpMouseRelease();
611 testPushButtonMouseRelease(true);
612 testCheckBoxMouseRelease();
613 testScrollBarMouseRelease();
614 testRadioButtonMouseRelease();
615
616 // revert "delete/rename" mouse modes (disk op.)
617 if (mouse.mode != MOUSE_MODE_NORMAL)
618 {
619 if (mouse.lastUsedObjectID != PB_DISKOP_DELETE && mouse.lastUsedObjectID != PB_DISKOP_RENAME)
620 setMouseMode(MOUSE_MODE_NORMAL);
621 }
622
623 mouse.lastUsedObjectID = OBJECT_ID_NONE;
624 mouse.lastUsedObjectType = OBJECT_NONE;
625 }
626
mouseButtonDownHandler(uint8_t mouseButton)627 void mouseButtonDownHandler(uint8_t mouseButton)
628 {
629 // if already holding left button and clicking right, don't do mouse down handling
630 if (mouseButton == SDL_BUTTON_RIGHT && mouse.leftButtonPressed)
631 {
632 if (ui.sampleDataOrLoopDrag == -1)
633 {
634 mouse.rightButtonPressed = true;
635 mouse.rightButtonReleased = false;
636 }
637
638 // kludge - we must do scope solo/unmute all here
639 if (!ui.sysReqShown)
640 testScopesMouseDown();
641
642 return;
643 }
644
645 // if already holding right button and clicking left, don't do mouse down handling
646 if (mouseButton == SDL_BUTTON_LEFT && mouse.rightButtonPressed)
647 {
648 if (ui.sampleDataOrLoopDrag == -1)
649 {
650 mouse.leftButtonPressed = true;
651 mouse.leftButtonReleased = false;
652 }
653
654 // kludge - we must do scope solo/unmute all here
655 if (!ui.sysReqShown)
656 testScopesMouseDown();
657
658 return;
659 }
660
661 // mouse 0,0 = open exit dialog
662 if (mouse.x == 0 && mouse.y == 0)
663 {
664 if (quitBox(false) == 1)
665 editor.throwExit = true;
666
667 // release button presses from okBox()
668 mouse.leftButtonPressed = false;
669 mouse.rightButtonPressed = false;
670 mouse.leftButtonReleased = false;
671 mouse.rightButtonReleased = false;
672
673 return;
674 }
675
676 if (mouseButton == SDL_BUTTON_LEFT)
677 mouse.leftButtonPressed = true;
678 else if (mouseButton == SDL_BUTTON_RIGHT)
679 mouse.rightButtonPressed = true;
680
681 mouse.leftButtonReleased = false;
682 mouse.rightButtonReleased = false;
683
684 // don't do mouse down testing here if we already are using an object
685 if (mouse.lastUsedObjectType != OBJECT_NONE)
686 return;
687
688 // kludge #2
689 if (mouse.lastUsedObjectType != OBJECT_PUSHBUTTON && mouse.lastUsedObjectID != OBJECT_ID_NONE)
690 return;
691
692 // kludge #3 :(
693 if (!mouse.rightButtonPressed)
694 mouse.lastUsedObjectID = OBJECT_ID_NONE;
695
696 // check if we pressed a GUI object
697
698 /* test objects like this - clickable things *never* overlap, so no need to test all
699 ** other objects if we clicked on one already
700 */
701
702 testInstrSwitcherMouseDown(); // kludge: allow right click to both change ins. and edit text
703
704 if (testTextBoxMouseDown()) return;
705 if (testPushButtonMouseDown()) return;
706 if (testCheckBoxMouseDown()) return;
707 if (testScrollBarMouseDown()) return;
708 if (testRadioButtonMouseDown()) return;
709
710 // at this point, we don't need to test more widgets if a system request is shown
711 if (ui.sysReqShown)
712 return;
713
714 if (testInstrVolEnvMouseDown(false)) return;
715 if (testInstrPanEnvMouseDown(false)) return;
716 if (testDiskOpMouseDown(false)) return;
717 if (testPianoKeysMouseDown(false)) return;
718 if (testSamplerDataMouseDown()) return;
719 if (testPatternDataMouseDown()) return;
720 if (testScopesMouseDown()) return;
721 if (testAudioDeviceListsMouseDown()) return;
722
723 #ifdef HAS_MIDI
724 if (testMidiInputDeviceListMouseDown()) return;
725 #endif
726 }
727
sendMouseButtonUpEvent(uint8_t button)728 static void sendMouseButtonUpEvent(uint8_t button)
729 {
730 SDL_Event event;
731
732 memset(&event, 0, sizeof (event));
733 event.type = SDL_MOUSEBUTTONUP;
734 event.button.button = button;
735
736 SDL_PushEvent(&event);
737 }
738
handleLastGUIObjectDown(void)739 void handleLastGUIObjectDown(void)
740 {
741 if (mouse.lastUsedObjectType == OBJECT_NONE)
742 return;
743
744 if (mouse.leftButtonPressed || mouse.rightButtonPressed)
745 {
746 if (mouse.lastUsedObjectID != OBJECT_ID_NONE)
747 {
748 switch (mouse.lastUsedObjectType)
749 {
750 case OBJECT_PUSHBUTTON: handlePushButtonsWhileMouseDown(); break;
751 case OBJECT_RADIOBUTTON: handleRadioButtonsWhileMouseDown(); break;
752 case OBJECT_CHECKBOX: handleCheckBoxesWhileMouseDown(); break;
753 case OBJECT_SCROLLBAR: handleScrollBarsWhileMouseDown(); break;
754 case OBJECT_TEXTBOX: handleTextBoxWhileMouseDown(); break;
755 default: break;
756 }
757 }
758 else
759 {
760 // test non-standard GUI elements
761 switch (mouse.lastUsedObjectType)
762 {
763 case OBJECT_INSTRSWITCH: testInstrSwitcherMouseDown(); break;
764 case OBJECT_PATTERNMARK: handlePatternDataMouseDown(true); break;
765 case OBJECT_DISKOPLIST: testDiskOpMouseDown(true); break;
766 case OBJECT_SMPDATA: handleSampleDataMouseDown(true); break;
767 case OBJECT_PIANO: testPianoKeysMouseDown(true); break;
768 case OBJECT_INSVOLENV: testInstrVolEnvMouseDown(true); break;
769 case OBJECT_INSPANENV: testInstrPanEnvMouseDown(true); break;
770 default: break;
771 }
772 }
773
774 /* Hack to send "mouse button up" events if we released the mouse button(s)
775 ** outside of the window...
776 */
777 if (mouse.x < 0 || mouse.x >= SCREEN_W || mouse.y < 0 || mouse.y >= SCREEN_H)
778 {
779 if (mouse.leftButtonPressed && !(mouse.buttonState & SDL_BUTTON_LMASK))
780 sendMouseButtonUpEvent(SDL_BUTTON_LEFT);
781
782 if (mouse.rightButtonPressed && !(mouse.buttonState & SDL_BUTTON_RMASK))
783 sendMouseButtonUpEvent(SDL_BUTTON_RIGHT);
784 }
785 }
786 }
787
updateMouseScaling(void)788 void updateMouseScaling(void)
789 {
790 if (video.renderW > 0.0) video.fMouseXMul = (float)SCREEN_W / video.renderW;
791 if (video.renderH > 0.0) video.fMouseYMul = (float)SCREEN_H / video.renderH;
792 }
793
readMouseXY(void)794 void readMouseXY(void)
795 {
796 int32_t mx, my, windowX, windowY;
797
798 if (mouse.setPosFlag)
799 {
800 if (!video.windowHidden)
801 SDL_WarpMouseInWindow(video.window, mouse.setPosX, mouse.setPosY);
802
803 mouse.setPosFlag = false;
804 return;
805 }
806
807 if (video.useDesktopMouseCoords)
808 {
809 mouse.buttonState = SDL_GetGlobalMouseState(&mx, &my);
810
811 // convert desktop coords to window coords
812 SDL_GetWindowPosition(video.window, &windowX, &windowY);
813 mx -= windowX;
814 my -= windowY;
815 }
816 else
817 {
818 // special mode for KMSDRM (XXX: Confirm that this still works...)
819 mouse.buttonState = SDL_GetMouseState(&mx, &my);
820 }
821
822 if (video.fullscreen)
823 {
824 // centered fullscreen mode (not stretched) needs further coord translation
825 if (!(config.specialFlags2 & STRETCH_IMAGE))
826 {
827 // if software mouse is enabled, warp mouse inside render space
828 if (!(config.specialFlags2 & HARDWARE_MOUSE))
829 {
830 bool warpMouse = false;
831
832 if (mx < video.renderX)
833 {
834 mx = video.renderX;
835 warpMouse = true;
836 }
837 else if (mx >= video.renderX+video.renderW)
838 {
839 mx = (video.renderX + video.renderW) - 1;
840 warpMouse = true;
841 }
842
843 if (my < video.renderY)
844 {
845 my = video.renderY;
846 warpMouse = true;
847 }
848 else if (my >= video.renderY+video.renderH)
849 {
850 my = (video.renderY + video.renderH) - 1;
851 warpMouse = true;
852 }
853
854 if (warpMouse)
855 SDL_WarpMouseInWindow(video.window, mx, my);
856 }
857
858 // convert fullscreen coords to window (centered image) coords
859 mx -= video.renderX;
860 my -= video.renderY;
861 }
862 }
863
864 // multiply coords by video upscaling factors (don't round)
865 mouse.x = (int32_t)(mx * video.fMouseXMul);
866 mouse.y = (int32_t)(my * video.fMouseYMul);
867
868 if (config.specialFlags2 & HARDWARE_MOUSE)
869 {
870 // hardware mouse mode (OS)
871 hideSprite(SPRITE_MOUSE_POINTER);
872 }
873 else
874 {
875 // software mouse mode (FT2 mouse)
876 setSpritePos(SPRITE_MOUSE_POINTER, mouse.x + mouse.xBias, mouse.y + mouse.yBias);
877 }
878
879 changeCursorIfOverTextBoxes();
880 }
881