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.
This commit is contained in:
Daniel Gibson 2021-06-20 17:19:23 +02:00
parent 00b58d1f0f
commit ae63021d00
11 changed files with 114 additions and 54 deletions

View file

@ -996,7 +996,6 @@ Activates or Deactivates a tool
*/
void idCommonLocal::ActivateTool( bool active ) {
com_editorActive = active;
Sys_GrabMouseCursor( !active );
}
/*

View file

@ -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();

View file

@ -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 ) {

View file

@ -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();

View file

@ -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);
/*

View file

@ -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:
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));
} 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)

View file

@ -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
}

View file

@ -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;

View file

@ -51,7 +51,6 @@ void MaterialEditorInit( void ) {
com_editors = EDITOR_MATERIAL;
Sys_GrabMouseCursor( false );
InitAfx();

View file

@ -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
}
}
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;

View file

@ -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;
}