From ae63021d0060a61374657e507a43fcd12a0d0606 Mon Sep 17 00:00:00 2001 From: Daniel Gibson Date: Sun, 20 Jun 2021 17:19:23 +0200 Subject: [PATCH] Add absolute mouse mode and refactor mouse grabbing code There were lots of places in the code that called Sys_GrabInput(), some of them each frame. Most of this is unified in events.cpp now, in handleMouseGrab() which is called once per frame by Sys_GenerateEvents() - this makes reasoning about when the mouse is grabbed and when not a lot easier. Sys_GrabInput(false) still is called in a few places, before operations that tend to take long (like loading a map or vid_restart), but (hopefully) not regularly anymore. The other big change is that the game now uses SDLs absolute mouse mode for fullscreen menus (except the PDA which is an ugly hack), so the ingame cursor is at the same position as the system cursor, which especially helps when debugging with `in_nograb 1` and should also help if someone wants to integrate an additional GUI toolkit like Dear ImGui. --- neo/framework/Common.cpp | 1 - neo/framework/Console.cpp | 1 - neo/framework/Session.cpp | 2 + neo/framework/async/AsyncNetwork.cpp | 2 - neo/renderer/tr_local.h | 7 +- neo/sys/events.cpp | 105 ++++++++++++++++---- neo/sys/glimp.cpp | 26 ++--- neo/sys/sys_public.h | 3 +- neo/tools/materialeditor/MaterialEditor.cpp | 1 - neo/ui/UserInterface.cpp | 18 +++- neo/ui/Window.cpp | 2 +- 11 files changed, 114 insertions(+), 54 deletions(-) diff --git a/neo/framework/Common.cpp b/neo/framework/Common.cpp index c12a4192..25637641 100644 --- a/neo/framework/Common.cpp +++ b/neo/framework/Common.cpp @@ -996,7 +996,6 @@ Activates or Deactivates a tool */ void idCommonLocal::ActivateTool( bool active ) { com_editorActive = active; - Sys_GrabMouseCursor( !active ); } /* diff --git a/neo/framework/Console.cpp b/neo/framework/Console.cpp index 7da453c1..82710743 100644 --- a/neo/framework/Console.cpp +++ b/neo/framework/Console.cpp @@ -826,7 +826,6 @@ bool idConsoleLocal::ProcessEvent( const sysEvent_t *event, bool forceAccept ) { // a down event will toggle the destination lines if ( keyCatching ) { Close(); - Sys_GrabMouseCursor( true ); cvarSystem->SetCVarBool( "ui_chat", false ); } else { consoleField.Clear(); diff --git a/neo/framework/Session.cpp b/neo/framework/Session.cpp index da4c6d65..9d5bcf0f 100644 --- a/neo/framework/Session.cpp +++ b/neo/framework/Session.cpp @@ -2644,6 +2644,7 @@ void idSessionLocal::Frame() { return; } +#if 0 // handled via Sys_GenerateEvents() -> handleMouseGrab() // if the console is down, we don't need to hold // the mouse cursor if ( console->Active() || com_editorActive ) { @@ -2651,6 +2652,7 @@ void idSessionLocal::Frame() { } else { Sys_GrabMouseCursor( true ); } +#endif // save the screenshot and audio from the last draw if needed if ( aviCaptureMode ) { diff --git a/neo/framework/async/AsyncNetwork.cpp b/neo/framework/async/AsyncNetwork.cpp index f44f5046..6f8a0cf2 100644 --- a/neo/framework/async/AsyncNetwork.cpp +++ b/neo/framework/async/AsyncNetwork.cpp @@ -172,10 +172,8 @@ idAsyncNetwork::RunFrame */ void idAsyncNetwork::RunFrame( void ) { if ( console->Active() ) { - Sys_GrabMouseCursor( false ); usercmdGen->InhibitUsercmd( INHIBIT_ASYNC, true ); } else { - Sys_GrabMouseCursor( true ); usercmdGen->InhibitUsercmd( INHIBIT_ASYNC, false ); } client.RunFrame(); diff --git a/neo/renderer/tr_local.h b/neo/renderer/tr_local.h index 3828fc9c..2ace6284 100644 --- a/neo/renderer/tr_local.h +++ b/neo/renderer/tr_local.h @@ -1108,10 +1108,9 @@ void GLimp_DeactivateContext( void ); // being immediate returns, which lets us guage how much time is // being spent inside OpenGL. -const int GRAB_ENABLE = (1 << 0); -const int GRAB_REENABLE = (1 << 1); -const int GRAB_HIDECURSOR = (1 << 2); -const int GRAB_SETSTATE = (1 << 3); +const int GRAB_GRABMOUSE = (1 << 0); +const int GRAB_HIDECURSOR = (1 << 1); +const int GRAB_RELATIVEMOUSE = (1 << 2); void GLimp_GrabInput(int flags); /* diff --git a/neo/sys/events.cpp b/neo/sys/events.cpp index 3bc05ead..6cc04516 100644 --- a/neo/sys/events.cpp +++ b/neo/sys/events.cpp @@ -32,8 +32,9 @@ If you have questions concerning this license or the applicable additional terms #include "idlib/containers/List.h" #include "idlib/Heap.h" #include "framework/Common.h" +#include "framework/Console.h" #include "framework/KeyInput.h" -#include "framework/Session.h" +#include "framework/Session_local.h" #include "renderer/RenderSystem.h" #include "renderer/tr_local.h" @@ -72,9 +73,15 @@ static idCVar in_kbd("in_kbd", _in_kbdNames[0], CVAR_SYSTEM | CVAR_ARCHIVE | CVA static idCVar in_ignoreConsoleKey("in_ignoreConsoleKey", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_NOCHEAT | CVAR_BOOL, "Console only opens with Shift+Esc, not ` or ^ etc"); +static idCVar in_nograb("in_nograb", "0", CVAR_SYSTEM | CVAR_NOCHEAT, "prevents input grabbing"); static idCVar in_grabKeyboard("in_grabKeyboard", "0", CVAR_SYSTEM | CVAR_ARCHIVE | CVAR_NOCHEAT | CVAR_BOOL, "if enabled, grabs all keyboard input if mouse is grabbed (so keyboard shortcuts from the OS like Alt-Tab or Windows Key won't work)"); +// set in handleMouseGrab(), used in Sys_GetEvent() to decide what kind of internal mouse event to generate +static bool in_relativeMouseMode = true; +// set in Sys_GetEvent() on window focus gained/lost events +static bool in_hasFocus = true; + struct kbd_poll_t { int key; bool state; @@ -648,15 +655,13 @@ unsigned char Sys_MapCharForKey(int key) { /* =============== Sys_GrabMouseCursor +Note: Usually grabbing is handled in idCommonLocal::Frame() -> Sys_GenerateEvents() -> handleMouseGrab() + This function should only be used to release the mouse before long operations where + common->Frame() won't be called for a while =============== */ void Sys_GrabMouseCursor(bool grabIt) { - int flags; - - if (grabIt) - flags = GRAB_ENABLE | GRAB_HIDECURSOR | GRAB_SETSTATE; - else - flags = GRAB_SETSTATE; + int flags = grabIt ? (GRAB_GRABMOUSE | GRAB_HIDECURSOR | GRAB_RELATIVEMOUSE) : 0; GLimp_GrabInput(flags); } @@ -722,15 +727,14 @@ sysEvent_t Sys_GetEvent() { } // new context because visual studio complains about newmod and currentmod not initialized because of the case SDL_WINDOWEVENT_FOCUS_LOST - common->ActivateTool( false ); - GLimp_GrabInput(GRAB_ENABLE | GRAB_REENABLE | GRAB_HIDECURSOR); // FIXME: not sure this is still needed after the ActivateTool()-call + in_hasFocus = true; // start playing the game sound world again (when coming from editor) session->SetPlayingSoundWorld(); break; case SDL_WINDOWEVENT_FOCUS_LOST: - GLimp_GrabInput(0); + in_hasFocus = false; break; } @@ -738,10 +742,8 @@ sysEvent_t Sys_GetEvent() { #else case SDL_ACTIVEEVENT: { - int flags = 0; - if (ev.active.gain) { - flags = GRAB_ENABLE | GRAB_REENABLE | GRAB_HIDECURSOR; + in_hasFocus = true; // unset modifier, in case alt-tab was used to leave window and ALT is still set // as that can cause fullscreen-toggling when pressing enter... @@ -751,9 +753,9 @@ sysEvent_t Sys_GetEvent() { newmod |= KMOD_CAPS; SDL_SetModState((SDLMod)newmod); + } else { + in_hasFocus = false; } - - GLimp_GrabInput(flags); } continue; // handle next event @@ -876,12 +878,18 @@ sysEvent_t Sys_GetEvent() { #endif case SDL_MOUSEMOTION: - res.evType = SE_MOUSE; - res.evValue = ev.motion.xrel; - res.evValue2 = ev.motion.yrel; + if ( in_relativeMouseMode ) { + res.evType = SE_MOUSE; + res.evValue = ev.motion.xrel; + res.evValue2 = ev.motion.yrel; - mouse_polls.Append(mouse_poll_t(M_DELTAX, ev.motion.xrel)); - mouse_polls.Append(mouse_poll_t(M_DELTAY, ev.motion.yrel)); + mouse_polls.Append(mouse_poll_t(M_DELTAX, ev.motion.xrel)); + mouse_polls.Append(mouse_poll_t(M_DELTAY, ev.motion.yrel)); + } else { + res.evType = SE_MOUSE_ABS; + res.evValue = ev.motion.x; + res.evValue2 = ev.motion.y; + } return res; @@ -892,7 +900,7 @@ sysEvent_t Sys_GetEvent() { if (ev.wheel.y > 0) { res.evValue = K_MWHEELUP; mouse_polls.Append(mouse_poll_t(M_DELTAZ, 1)); - } else { + } else if (ev.wheel.y < 0) { res.evValue = K_MWHEELDOWN; mouse_polls.Append(mouse_poll_t(M_DELTAZ, -1)); } @@ -990,12 +998,67 @@ void Sys_ClearEvents() { mouse_polls.SetNum(0, false); } +static void handleMouseGrab() { + + // these are the defaults for when the window does *not* have focus + // (don't grab in any way) + bool showCursor = true; + bool grabMouse = false; + bool relativeMouse = false; + + // if com_editorActive, release everything, just like when we have no focus + if ( in_hasFocus && !com_editorActive ) { + // Note: this generally handles fullscreen menus, but not the PDA, because the PDA + // is an ugly hack in gamecode that doesn't go through sessLocal.guiActive. + // It goes through weapon input code or sth? That's also the reason only + // leftclick (fire) works there (no mousewheel..) + // So the PDA will continue to use relative mouse events to set its cursor position. + const bool menuActive = ( sessLocal.GetActiveMenu() != NULL ); + + if ( menuActive ) { + showCursor = false; + relativeMouse = false; + grabMouse = false; // TODO: or still grab to window? (maybe only if in exclusive fullscreen mode?) + } else if ( console->Active() ) { + showCursor = true; + relativeMouse = grabMouse = false; + } else { // in game + showCursor = false; + grabMouse = relativeMouse = true; + } + + in_relativeMouseMode = relativeMouse; + + // if in_nograb is set, in_relativeMouseMode and relativeMouse can disagree + // (=> don't enable relative mouse mode in SDL, but still use relative mouse events + // in the game, unless we'd use absolute mousemode anyway) + if ( in_nograb.GetBool() ) { + grabMouse = relativeMouse = false; + } + } else { + in_relativeMouseMode = false; + } + + int flags = 0; + if ( !showCursor ) + flags |= GRAB_HIDECURSOR; + if ( grabMouse ) + flags |= GRAB_GRABMOUSE; + if ( relativeMouse ) + flags |= GRAB_RELATIVEMOUSE; + + GLimp_GrabInput( flags ); +} + /* ================ Sys_GenerateEvents ================ */ void Sys_GenerateEvents() { + + handleMouseGrab(); + char *s = Sys_ConsoleInput(); if (s) diff --git a/neo/sys/glimp.cpp b/neo/sys/glimp.cpp index 662c871e..ae392d76 100644 --- a/neo/sys/glimp.cpp +++ b/neo/sys/glimp.cpp @@ -97,11 +97,8 @@ If you have questions concerning this license or the applicable additional terms #endif // _WIN32 and ID_ALLOW_TOOLS -idCVar in_nograb("in_nograb", "0", CVAR_SYSTEM | CVAR_NOCHEAT, "prevents input grabbing"); idCVar r_waylandcompat("r_waylandcompat", "0", CVAR_SYSTEM | CVAR_NOCHEAT | CVAR_ARCHIVE, "wayland compatible framebuffer"); -static bool grabbed = false; - #if SDL_VERSION_ATLEAST(2, 0, 0) static SDL_Window *window = NULL; static SDL_GLContext context = NULL; @@ -644,28 +641,19 @@ GLExtension_t GLimp_ExtensionPointer(const char *name) { } void GLimp_GrabInput(int flags) { - bool grab = flags & GRAB_ENABLE; - - if (grab && (flags & GRAB_REENABLE)) - grab = false; - - if (flags & GRAB_SETSTATE) - grabbed = grab; - - if (in_nograb.GetBool()) - grab = false; - if (!window) { common->Warning("GLimp_GrabInput called without window"); return; } #if SDL_VERSION_ATLEAST(2, 0, 0) - SDL_ShowCursor(flags & GRAB_HIDECURSOR ? SDL_DISABLE : SDL_ENABLE); - SDL_SetRelativeMouseMode((grab && (flags & GRAB_HIDECURSOR)) ? SDL_TRUE : SDL_FALSE); - SDL_SetWindowGrab(window, grab ? SDL_TRUE : SDL_FALSE); + SDL_ShowCursor( (flags & GRAB_HIDECURSOR) ? SDL_DISABLE : SDL_ENABLE ); + SDL_SetRelativeMouseMode( (flags & GRAB_RELATIVEMOUSE) ? SDL_TRUE : SDL_FALSE ); + SDL_SetWindowGrab( window, (flags & GRAB_GRABMOUSE) ? SDL_TRUE : SDL_FALSE ); #else - SDL_ShowCursor(flags & GRAB_HIDECURSOR ? SDL_DISABLE : SDL_ENABLE); - SDL_WM_GrabInput(grab ? SDL_GRAB_ON : SDL_GRAB_OFF); + SDL_ShowCursor( (flags & GRAB_HIDECURSOR) ? SDL_DISABLE : SDL_ENABLE ); + // ignore GRAB_GRABMOUSE, SDL1.2 doesn't support grabbing without relative mode + // so only grab if we want relative mode + SDL_WM_GrabInput( (flags & GRAB_RELATIVEMOUSE) ? SDL_GRAB_ON : SDL_GRAB_OFF ); #endif } diff --git a/neo/sys/sys_public.h b/neo/sys/sys_public.h index 9d9105d3..bc985836 100644 --- a/neo/sys/sys_public.h +++ b/neo/sys/sys_public.h @@ -57,7 +57,8 @@ typedef enum { SE_NONE, // evTime is still valid SE_KEY, // evValue is a key code, evValue2 is the down flag SE_CHAR, // evValue is an ascii char - SE_MOUSE, // evValue and evValue2 are reletive signed x / y moves + SE_MOUSE, // evValue and evValue2 are relative signed x / y moves + SE_MOUSE_ABS, // evValue and evValue2 are absolute x / y coordinates in the window SE_JOYSTICK_AXIS, // evValue is an axis number and evValue2 is the current state (-127 to 127) SE_CONSOLE // evPtr is a char*, from typing something at a non-game console } sysEventType_t; diff --git a/neo/tools/materialeditor/MaterialEditor.cpp b/neo/tools/materialeditor/MaterialEditor.cpp index b4461659..523927f2 100644 --- a/neo/tools/materialeditor/MaterialEditor.cpp +++ b/neo/tools/materialeditor/MaterialEditor.cpp @@ -51,7 +51,6 @@ void MaterialEditorInit( void ) { com_editors = EDITOR_MATERIAL; - Sys_GrabMouseCursor( false ); InitAfx(); diff --git a/neo/ui/UserInterface.cpp b/neo/ui/UserInterface.cpp index 2937de75..032b3c4b 100644 --- a/neo/ui/UserInterface.cpp +++ b/neo/ui/UserInterface.cpp @@ -344,7 +344,7 @@ const char *idUserInterfaceLocal::HandleEvent( const sysEvent_t *event, int _tim return ret; } - if ( event->evType == SE_MOUSE ) { + if ( event->evType == SE_MOUSE || event->evType == SE_MOUSE_ABS ) { if ( !desktop || (desktop->GetFlags() & WIN_MENUGUI) ) { // DG: this is a fullscreen GUI, scale the mousedelta added to cursorX/Y // by 640/w, because the GUI pretends that everything is 640x480 @@ -374,8 +374,20 @@ const char *idUserInterfaceLocal::HandleEvent( const sysEvent_t *event, int _tim } } - cursorX += event->evValue * (float(VIRTUAL_WIDTH)/w); - cursorY += event->evValue2 * (float(VIRTUAL_HEIGHT)/h); + if( event->evType == SE_MOUSE ) { + cursorX += event->evValue * (float(VIRTUAL_WIDTH)/w); + cursorY += event->evValue2 * (float(VIRTUAL_HEIGHT)/h); + } else { // SE_MOUSE_ABS + // Note: In case of scaling to 4:3, w and h are already scaled down + // to the 4:3 size that fits into the real resolution. + // Otherwise xOffset/yOffset will just be 0 + float xOffset = (renderSystem->GetScreenWidth() - w) * 0.5f; + float yOffset = (renderSystem->GetScreenHeight() - h) * 0.5f; + // offset the mouse coordinates into 4:3 area and scale down to 640x480 + // yes, result could be negative, doesn't matter, code below checks that anyway + cursorX = (event->evValue - xOffset) * (float(VIRTUAL_WIDTH)/w); + cursorY = (event->evValue2 - yOffset) * (float(VIRTUAL_HEIGHT)/h); + } } else { // not a fullscreen GUI but some ingame thing - no scaling needed cursorX += event->evValue; diff --git a/neo/ui/Window.cpp b/neo/ui/Window.cpp index 2bdf96f1..3948cea3 100644 --- a/neo/ui/Window.cpp +++ b/neo/ui/Window.cpp @@ -927,7 +927,7 @@ const char *idWindow::HandleEvent(const sysEvent_t *event, bool *updateVisuals) } } - } else if (event->evType == SE_MOUSE) { + } else if (event->evType == SE_MOUSE || event->evType == SE_MOUSE_ABS) { if (updateVisuals) { *updateVisuals = true; }