ref: df5ceec1c72376898da165c8564299d680bb0cb3
dir: /src/ft2_mouse.c/
// for finding memory leaks in debug mode with Visual Studio #if defined _DEBUG && defined _MSC_VER #include <crtdbg.h> #endif #include <stdio.h> #include <stdbool.h> #include "ft2_header.h" #include "ft2_gui.h" #include "ft2_video.h" #include "ft2_scopes.h" #include "ft2_help.h" #include "ft2_sample_ed.h" #include "ft2_inst_ed.h" #include "ft2_pattern_ed.h" #include "ft2_mouse.h" #include "ft2_config.h" #include "ft2_diskop.h" #include "ft2_gfxdata.h" #include "ft2_audioselector.h" #include "ft2_midi.h" static bool mouseBusyGfxBackwards; static int16_t mouseShape; static int32_t mouseModeGfxOffs, mouseBusyGfxFrame; static SDL_Cursor *cArrow, *cIBeam, *cBusy; void freeSDL2Cursors(void) { if (cArrow != NULL) { SDL_FreeCursor(cArrow); cArrow = NULL; } if (cIBeam != NULL) { SDL_FreeCursor(cIBeam); cIBeam = NULL; } if (cBusy != NULL) { SDL_FreeCursor(cBusy); cBusy = NULL; } } void createSDL2Cursors(void) { cArrow = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); cIBeam = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); cBusy = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); } void setMousePosToCenter(void) { if (video.fullscreen) { mouse.setPosX = video.displayW / 2; mouse.setPosY = video.displayH / 2; } else { mouse.setPosX = video.renderW / 2; mouse.setPosY = video.renderH / 2; } mouse.setPosFlag = true; } void animateBusyMouse(void) { if (config.mouseAnimType == MOUSE_BUSY_SHAPE_CLOCK) { if ((editor.framesPassed % 7) == 6) { if (mouseBusyGfxBackwards) { if (--mouseBusyGfxFrame <= 0) { mouseBusyGfxFrame = 0; mouseBusyGfxBackwards = false; } } else { if (++mouseBusyGfxFrame >= MOUSE_CLOCK_ANI_FRAMES-1) { mouseBusyGfxFrame = MOUSE_CLOCK_ANI_FRAMES - 1; mouseBusyGfxBackwards = true; } } changeSpriteData(SPRITE_MOUSE_POINTER, &mouseCursorBusyClock[(mouseBusyGfxFrame % MOUSE_CLOCK_ANI_FRAMES) * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)]); } } else { if ((editor.framesPassed % 5) == 4) { mouseBusyGfxFrame = (mouseBusyGfxFrame + 1) % MOUSE_GLASS_ANI_FRAMES; changeSpriteData(SPRITE_MOUSE_POINTER, &mouseCursorBusyGlass[mouseBusyGfxFrame * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)]); } } } void setMouseShape(int16_t shape) { const uint8_t *gfxPtr; if (editor.busy) { if (config.mouseAnimType == MOUSE_BUSY_SHAPE_CLOCK) gfxPtr = &mouseCursorBusyClock[(mouseBusyGfxFrame % MOUSE_GLASS_ANI_FRAMES) * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)]; else gfxPtr = &mouseCursorBusyGlass[(mouseBusyGfxFrame % MOUSE_CLOCK_ANI_FRAMES) * (MOUSE_CURSOR_W * MOUSE_CURSOR_H)]; } else { gfxPtr = &mouseCursors[mouseModeGfxOffs]; switch (shape) { case MOUSE_IDLE_SHAPE_NICE: gfxPtr += 0 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break; case MOUSE_IDLE_SHAPE_UGLY: gfxPtr += 1 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break; case MOUSE_IDLE_SHAPE_AWFUL: gfxPtr += 2 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break; case MOUSE_IDLE_SHAPE_USABLE: gfxPtr += 3 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break; case MOUSE_IDLE_TEXT_EDIT: gfxPtr += 12 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); break; default: return; } } mouseShape = shape; changeSpriteData(SPRITE_MOUSE_POINTER, gfxPtr); } static void setTextEditMouse(void) { setMouseShape(MOUSE_IDLE_TEXT_EDIT); mouse.xBias = -2; mouse.yBias = -6; if (config.specialFlags2 & HARDWARE_MOUSE && cIBeam != NULL) SDL_SetCursor(cIBeam); } static void clearTextEditMouse(void) { setMouseShape(config.mouseType); mouse.xBias = 0; mouse.yBias = 0; if (config.specialFlags2 & HARDWARE_MOUSE && cArrow != NULL) SDL_SetCursor(cArrow); } static void changeCursorIfOverTextBoxes(void) { int16_t i, mx, my; textBox_t *t; mouse.mouseOverTextBox = false; if (editor.busy || mouse.mode != MOUSE_MODE_NORMAL) return; mx = mouse.x; my = mouse.y; for (i = 0; i < NUM_TEXTBOXES; i++) { if (editor.ui.sysReqShown && i > 0) continue; t = &textBoxes[i]; if (!t->visible) continue; if (!t->changeMouseCursor && (!editor.editTextFlag || i != mouse.lastEditBox)) continue; // some kludge of sorts if (my >= t->y && my < t->y+t->h && mx >= t->x && mx < t->x+t->w) { mouse.mouseOverTextBox = true; setTextEditMouse(); return; } } // we're not inside a text edit box, set back mouse cursor if (i == NUM_TEXTBOXES && mouseShape == MOUSE_IDLE_TEXT_EDIT) clearTextEditMouse(); } void setMouseMode(uint8_t mode) { switch (mode) { case MOUSE_MODE_NORMAL: { mouse.mode = mode; mouseModeGfxOffs = 0 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); } break; case MOUSE_MODE_DELETE: { mouse.mode = mode; mouseModeGfxOffs = 4 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); } break; case MOUSE_MODE_RENAME: { mouse.mode = mode; mouseModeGfxOffs = 8 * (MOUSE_CURSOR_W * MOUSE_CURSOR_H); } break; default: return; } setMouseShape(config.mouseType); } void resetMouseBusyAnimation(void) { mouseBusyGfxBackwards = false; mouseBusyGfxFrame = 0; } void setMouseBusy(bool busy) // can be called from other threads { if (busy) { editor.ui.setMouseIdle = false; editor.ui.setMouseBusy = true; } else { editor.ui.setMouseBusy = false; editor.ui.setMouseIdle = true; } } void mouseAnimOn(void) { editor.ui.setMouseBusy = false; editor.ui.setMouseIdle = false; editor.busy = true; setMouseShape(config.mouseAnimType); if (config.specialFlags2 & HARDWARE_MOUSE && cBusy != NULL) SDL_SetCursor(cBusy); } void mouseAnimOff(void) { editor.ui.setMouseBusy = false; editor.ui.setMouseIdle = false; editor.busy = false; setMouseShape(config.mouseType); if (config.specialFlags2 & HARDWARE_MOUSE && cArrow != NULL) SDL_SetCursor(cArrow); } static void mouseWheelDecRow(void) { int16_t pattPos; if (songPlaying) return; pattPos = editor.pattPos - 1; if (pattPos < 0) pattPos = pattLens[editor.editPattern] - 1; setPos(-1, pattPos); } static void mouseWheelIncRow(void) { int16_t pattPos; if (songPlaying) return; pattPos = editor.pattPos + 1; if (pattPos > (pattLens[editor.editPattern] - 1)) pattPos = 0; setPos(-1, pattPos); } void mouseWheelHandler(bool directionUp) { if (editor.ui.sysReqShown || editor.editTextFlag) return; if (editor.ui.extended) { if (mouse.y <= 52) { if (mouse.x <= 111) directionUp ? decSongPos() : incSongPos(); else if (mouse.x >= 386) directionUp ? decCurIns() : incCurIns(); } else { directionUp ? mouseWheelDecRow() : mouseWheelIncRow(); } return; } if (mouse.y < 173) { // top screens if (editor.ui.helpScreenShown) { // help screen if (directionUp) { helpScrollUp(); helpScrollUp(); } else { helpScrollDown(); helpScrollDown(); } } else if (editor.ui.diskOpShown) { // disk op - 3x speed if (mouse.x <= 355) { if (directionUp) { pbDiskOpListUp(); pbDiskOpListUp(); pbDiskOpListUp(); } else { pbDiskOpListDown(); pbDiskOpListDown(); pbDiskOpListDown(); } } } else if (editor.ui.configScreenShown) { if (editor.currConfigScreen == CONFIG_SCREEN_IO_DEVICES) { // audio device selectors if (mouse.x >= 110 && mouse.x <= 355 && mouse.y <= 173) { if (mouse.y < 87) directionUp ? scrollAudOutputDevListUp() : scrollAudOutputDevListDown(); else directionUp ? scrollAudInputDevListUp() : scrollAudInputDevListDown(); } } else if (editor.currConfigScreen == CONFIG_SCREEN_MIDI_INPUT) { // midi input device selector if (mouse.x >= 110 && mouse.x <= 503 && mouse.y <= 173) directionUp ? scrollMidiInputDevListUp() : scrollMidiInputDevListDown(); } } if (!editor.ui.aboutScreenShown && !editor.ui.helpScreenShown && !editor.ui.configScreenShown && !editor.ui.nibblesShown) { if (mouse.x >= 421 && mouse.y <= 173) { if (mouse.y <= 93) directionUp ? decCurIns() : incCurIns(); else if (mouse.y >= 94) directionUp ? decCurSmp() : incCurSmp(); } else if (!editor.ui.diskOpShown && mouse.x <= 111 && mouse.y <= 76) { directionUp ? decSongPos() : incSongPos(); } } } else { // bottom screens if (editor.ui.sampleEditorShown) { if (mouse.y >= 174 && mouse.y <= 328) directionUp ? mouseZoomSampleDataIn() : mouseZoomSampleDataOut(); } else if (editor.ui.patternEditorShown) { directionUp ? mouseWheelDecRow() : mouseWheelIncRow(); } } } static bool testSamplerDataMouseDown(void) { if (editor.ui.sampleEditorShown && mouse.y >= 174 && mouse.y <= 327 && editor.ui.sampleDataOrLoopDrag == -1) { handleSampleDataMouseDown(false); return true; } return false; } static bool testPatternDataMouseDown(void) { uint16_t y1, y2; if (editor.ui.patternEditorShown) { y1 = editor.ui.extended ? 56 : 176; y2 = editor.ui.pattChanScrollShown ? 382 : 396; if (mouse.y >= y1 && mouse.y <= y2 && mouse.x >= 29 && mouse.x <= 602) { handlePatternDataMouseDown(false); return true; } } return false; } void mouseButtonUpHandler(uint8_t mouseButton) { #ifndef __APPLE__ if (!video.fullscreen) // release mouse button trap SDL_SetWindowGrab(video.window, SDL_FALSE); #endif if (mouseButton == SDL_BUTTON_LEFT) { mouse.leftButtonPressed = false; mouse.leftButtonReleased = true; if (editor.ui.leftLoopPinMoving) { setLeftLoopPinState(false); editor.ui.leftLoopPinMoving = false; } if (editor.ui.rightLoopPinMoving) { setRightLoopPinState(false); editor.ui.rightLoopPinMoving = false; } } else if (mouseButton == SDL_BUTTON_RIGHT) { mouse.rightButtonPressed = false; mouse.rightButtonReleased = true; if (editor.editSampleFlag) { // right mouse button released after hand-editing sample data if (instr[editor.curInstr] != NULL) fixSample(&instr[editor.curInstr]->samp[editor.curSmp]); resumeAudio(); if (editor.ui.sampleEditorShown) writeSample(true); setSongModifiedFlag(); editor.editSampleFlag = false; } } mouse.firstTimePressingButton = false; mouse.buttonCounter = 0; editor.textCursorBlinkCounter = 0; // if we used both mouse button at the same time and released *one*, don't release GUI object if ( mouse.leftButtonPressed && !mouse.rightButtonPressed) return; if (!mouse.leftButtonPressed && mouse.rightButtonPressed) return; if (editor.ui.sampleEditorShown) testSmpEdMouseUp(); mouse.lastX = 0; mouse.lastY = 0; editor.ui.sampleDataOrLoopDrag = -1; // check if we released a GUI object testDiskOpMouseRelease(); testPushButtonMouseRelease(true); testCheckBoxMouseRelease(); testScrollBarMouseRelease(); testRadioButtonMouseRelease(); // revert "delete/rename" mouse modes (disk op.) if (mouse.lastUsedObjectID != PB_DISKOP_DELETE && mouse.lastUsedObjectID != PB_DISKOP_RENAME) { if (mouse.mode != MOUSE_MODE_NORMAL) setMouseMode(MOUSE_MODE_NORMAL); } mouse.lastUsedObjectID = OBJECT_ID_NONE; mouse.lastUsedObjectType = OBJECT_NONE; } void mouseButtonDownHandler(uint8_t mouseButton) { #ifndef __APPLE__ if (!video.fullscreen) // trap mouse pointer while holding down left and/or right button SDL_SetWindowGrab(video.window, SDL_TRUE); #endif // if already holding left button and clicking right, don't do mouse down handling if (mouseButton == SDL_BUTTON_RIGHT && mouse.leftButtonPressed) { if (editor.ui.sampleDataOrLoopDrag == -1) { mouse.rightButtonPressed = true; mouse.rightButtonReleased = false; } // kludge - we must do scope solo/unmute all here if (!editor.ui.sysReqShown) testScopesMouseDown(); return; } // if already holding right button and clicking left, don't do mouse down handling if (mouseButton == SDL_BUTTON_LEFT && mouse.rightButtonPressed) { if (editor.ui.sampleDataOrLoopDrag == -1) { mouse.leftButtonPressed = true; mouse.leftButtonReleased = false; } // kludge - we must do scope solo/unmute all here if (!editor.ui.sysReqShown) testScopesMouseDown(); return; } if (mouseButton == SDL_BUTTON_LEFT) mouse.leftButtonPressed = true; else if (mouseButton == SDL_BUTTON_RIGHT) mouse.rightButtonPressed = true; mouse.leftButtonReleased = false; mouse.rightButtonReleased = false; // mouse 0,0 = open exit dialog if (mouse.x == 0 && mouse.y == 0 && quitBox(false) == 1) { editor.throwExit = true; return; } // don't do mouse down testing here if we already are using an object if (mouse.lastUsedObjectType != OBJECT_NONE) return; // kludge #2 if (mouse.lastUsedObjectType != OBJECT_PUSHBUTTON && mouse.lastUsedObjectID != OBJECT_ID_NONE) return; // kludge #3 if (!mouse.rightButtonPressed) mouse.lastUsedObjectID = OBJECT_ID_NONE; // check if we pressed a GUI object /* test objects like this - clickable things *never* overlap, so no need to test all ** other objects if we clicked on one already */ testInstrSwitcherMouseDown(); // kludge: allow right click to both change ins. and edit text if (testTextBoxMouseDown()) return; if (testPushButtonMouseDown()) return; if (testCheckBoxMouseDown()) return; if (testScrollBarMouseDown()) return; if (testRadioButtonMouseDown()) return; // from this point, we don't need to test more widgets if a system request box is shown if (editor.ui.sysReqShown) return; if (testInstrVolEnvMouseDown(false)) return; if (testInstrPanEnvMouseDown(false)) return; if (testDiskOpMouseDown(false)) return; if (testPianoKeysMouseDown(false)) return; if (testSamplerDataMouseDown()) return; if (testPatternDataMouseDown()) return; if (testScopesMouseDown()) return; if (testAudioDeviceListsMouseDown()) return; if (testMidiInputDeviceListMouseDown()) return; } void handleLastGUIObjectDown(void) { if (mouse.lastUsedObjectType == OBJECT_NONE) return; if (mouse.leftButtonPressed || mouse.rightButtonPressed) { if (mouse.lastUsedObjectID != OBJECT_ID_NONE) { switch (mouse.lastUsedObjectType) { case OBJECT_PUSHBUTTON: handlePushButtonsWhileMouseDown(); break; case OBJECT_RADIOBUTTON: handleRadioButtonsWhileMouseDown(); break; case OBJECT_CHECKBOX: handleCheckBoxesWhileMouseDown(); break; case OBJECT_SCROLLBAR: handleScrollBarsWhileMouseDown(); break; case OBJECT_TEXTBOX: handleTextBoxWhileMouseDown(); break; default: break; } } else { // test non-standard GUI elements switch (mouse.lastUsedObjectType) { case OBJECT_INSTRSWITCH: testInstrSwitcherMouseDown(); break; case OBJECT_PATTERNMARK: handlePatternDataMouseDown(true); break; case OBJECT_DISKOPLIST: testDiskOpMouseDown(true); break; case OBJECT_SMPDATA: handleSampleDataMouseDown(true); break; case OBJECT_PIANO: testPianoKeysMouseDown(true); break; case OBJECT_INSVOLENV: testInstrVolEnvMouseDown(true); break; case OBJECT_INSPANENV: testInstrPanEnvMouseDown(true); break; default: break; } } } } void updateMouseScaling(void) { double dScaleX, dScaleY; dScaleX = video.renderW / (double)SCREEN_W; dScaleY = video.renderH / (double)SCREEN_H; video.xScaleMul = (dScaleX == 0.0) ? 65536 : (uint32_t)round(65536.0 / dScaleX); video.yScaleMul = (dScaleY == 0.0) ? 65536 : (uint32_t)round(65536.0 / dScaleY); } void readMouseXY(void) { int16_t x, y; int32_t mx, my; if (mouse.setPosFlag) { mouse.setPosFlag = false; if (SDL_GetWindowFlags(video.window) & SDL_WINDOW_SHOWN) SDL_WarpMouseInWindow(video.window, mouse.setPosX, mouse.setPosY); return; } SDL_PumpEvents(); // gathers all pending input from devices into the event queue (less mouse lag) SDL_GetMouseState(&mx, &my); /* in centered fullscreen mode, trap the mouse inside the framed image ** and subtract the coords to match the OS mouse position (fixes touch from touchscreens) */ if (video.fullscreen && !(config.windowFlags & FILTERING)) { if (mx < video.renderX) { mx = video.renderX; SDL_WarpMouseInWindow(video.window, mx, my); } else if (mx >= video.renderX+video.renderW) { mx = (video.renderX + video.renderW) - 1; SDL_WarpMouseInWindow(video.window, mx, my); } if (my < video.renderY) { my = video.renderY; SDL_WarpMouseInWindow(video.window, mx, my); } else if (my >= video.renderY+video.renderH) { my = (video.renderY + video.renderH) - 1; SDL_WarpMouseInWindow(video.window, mx, my); } mx -= video.renderX; my -= video.renderY; } if (mx < 0) mx = 0; if (my < 0) mx = 0; // multiply coords by video scaling factors mx = (((uint32_t)mx * video.xScaleMul) + (1 << 15)) >> 16; // rounded my = (((uint32_t)my * video.yScaleMul) + (1 << 15)) >> 16; if (mx >= SCREEN_W) mx = SCREEN_W - 1; if (my >= SCREEN_H) my = SCREEN_H - 1; if (config.specialFlags2 & HARDWARE_MOUSE) { // hardware mouse (OS) mouse.x = (int16_t)mx; mouse.y = (int16_t)my; hideSprite(SPRITE_MOUSE_POINTER); } else { // software mouse (FT2 mouse) x = (int16_t)mx; y = (int16_t)my; mouse.x = x; mouse.y = y; // for text editing cursor (do this after clamp) x += mouse.xBias; y += mouse.yBias; if (x < 0) x = 0; if (y < 0) y = 0; setSpritePos(SPRITE_MOUSE_POINTER, x, y); } changeCursorIfOverTextBoxes(); }