From 6d508eac17cbe2cdb46d489510f417019e7fa85b Mon Sep 17 00:00:00 2001 From: Daniel Gibson Date: Wed, 22 May 2024 16:39:37 +0200 Subject: [PATCH] Some functions that will be used by the ImGui keybinding menu --- neo/sys/events.cpp | 70 +++++++++++++++++---- neo/sys/sys_imgui.cpp | 139 ++++++++++++++++++++++++++++++++++++++++-- neo/sys/sys_imgui.h | 13 ++++ neo/sys/sys_public.h | 2 + 4 files changed, 209 insertions(+), 15 deletions(-) diff --git a/neo/sys/events.cpp b/neo/sys/events.cpp index cc4519a1..ddeaae1a 100644 --- a/neo/sys/events.cpp +++ b/neo/sys/events.cpp @@ -278,6 +278,28 @@ static bool utf8ToISO8859_1(const char* inbuf, char* outbuf, size_t outsize) { } #endif // SDL2 +// start button isn't bindable, but I want to use its name in the imgui-based menu +const char* D3_GetGamepadStartButtonName() { + int layout = joy_gamepadLayout.GetInteger(); + if ( layout == -1 ) { + layout = gamepadType; + } + + switch( layout ) { + default: + common->Warning( "joy_gamepadLayout has invalid value %d !\n", joy_gamepadLayout.GetInteger() ); + // fall-through + case D3_GAMEPAD_PLAYSTATION_OLD: + case D3_GAMEPAD_XINPUT: + return "Pad Start"; + case D3_GAMEPAD_NINTENDO: + return "Pad (+)"; + + case D3_GAMEPAD_PLAYSTATION: + return "Pad Options"; + } +} + const char* Sys_GetLocalizedJoyKeyName( int key ) { // Note: trying to keep the returned names short, because the Doom3 binding window doesn't have much space for names.. @@ -409,28 +431,41 @@ const char* Sys_GetLocalizedJoyKeyName( int key ) { return NULL; } -// returns localized name of the key (between K_FIRST_SCANCODE and K_LAST_SCANCODE), -// regarding the current keyboard layout - if that name is in ASCII or corresponds -// to a "High-ASCII" char supported by Doom3. -// Otherwise return same name as Sys_GetScancodeName() -// !! Returned string is only valid until next call to this function !! -const char* Sys_GetLocalizedScancodeName( int key ) { +static const char* getLocalizedScancodeName( int key, bool useUtf8 ) +{ if ( key >= K_FIRST_SCANCODE && key <= K_LAST_SCANCODE ) { int scIdx = key - K_FIRST_SCANCODE; #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_Scancode sc = ( SDL_Scancode ) scancodemappings[scIdx].sdlScancode; SDL_Keycode k = SDL_GetKeyFromScancode( sc ); if ( k >= 0xA1 && k <= 0xFF ) { - // luckily, the "High-ASCII" (ISO-8559-1) chars supported by Doom3 - // have the same values as the corresponding SDL_Keycodes. - static char oneCharStr[2] = {0, 0}; - oneCharStr[0] = (unsigned char)k; - return oneCharStr; + static char shortStr[3] = {}; + if ( useUtf8 ) { + // SDL_Keycodes are unicode chars (where applicable), + // so at least for Latin-1 turn them directly into UTF-8 + if ( k >= 0xE0 && k <= 0xFE && k != 0xF7 ) { + // turn lowercase chars into their uppercase equivalents + k -= 32; + } + shortStr[0] = (unsigned char)( 0xC0 + (k >> 6) ); + shortStr[1] = (unsigned char)( 0x80 + (k & 0x3F) ); + return shortStr; + } else { + // luckily, the "High-ASCII" (ISO-8559-1) chars supported by Doom3 + // have the same values as the corresponding SDL_Keycodes. + shortStr[0] = (unsigned char)k; + shortStr[1] = 0; + return shortStr; + } } else if ( k != SDLK_UNKNOWN ) { const char *ret = SDL_GetKeyName( k ); // the keyname from SDL2 is in UTF-8, which Doom3 can't print, // so only return the name if it's ASCII, otherwise fall back to "SC_bla" if ( ret && *ret != '\0' ) { + if( useUtf8 ) { + return ret; + } + if( isAscii( ret ) ) { return ret; } @@ -448,6 +483,19 @@ const char* Sys_GetLocalizedScancodeName( int key ) { return NULL; } +// returns localized name of the key (between K_FIRST_SCANCODE and K_LAST_SCANCODE), +// regarding the current keyboard layout - if that name is in ASCII or corresponds +// to a "High-ASCII" char supported by Doom3. +// Otherwise return same name as Sys_GetScancodeName() +// !! Returned string is only valid until next call to this function !! +const char* Sys_GetLocalizedScancodeName( int key ) { + return getLocalizedScancodeName( key, false ); +} + +const char* Sys_GetLocalizedScancodeNameUTF8( int key ) { + return getLocalizedScancodeName( key, true ); +} + // returns keyNum_t (K_SC_* constant) for given scancode name (like "SC_A") // only makes sense to call it if name starts with "SC_" (or "sc_") // returns -1 if not found diff --git a/neo/sys/sys_imgui.cpp b/neo/sys/sys_imgui.cpp index a84d0777..55de241f 100644 --- a/neo/sys/sys_imgui.cpp +++ b/neo/sys/sys_imgui.cpp @@ -1,4 +1,6 @@ +#define IMGUI_DEFINE_MATH_OPERATORS + #include #include "sys_imgui.h" @@ -14,6 +16,7 @@ typedef char* (*MY_XGETDEFAULTFUN)(Display*, const char*, const char*); #include "../libs/imgui/backends/imgui_impl_sdl2.h" #include "framework/Common.h" +#include "framework/KeyInput.h" #include "renderer/qgl.h" #include "renderer/tr_local.h" // glconfig @@ -31,6 +34,82 @@ ImGuiContext* imguiCtx = NULL; static bool haveNewFrame = false; static int openImguiWindows = 0; // or-ed enum D3ImGuiWindow values +// was there a key down or button (mouse/gamepad) down event this frame? +// used to make the warning overlay disappear +static bool hadKeyDownEvent = false; + +static idStr warningOverlayText; +static double warningOverlayStartTime = -100.0; +static ImVec2 warningOverlayStartPos; + +static void UpdateWarningOverlay() +{ + double timeNow = ImGui::GetTime(); + if ( timeNow - warningOverlayStartTime > 4.0f ) { + warningOverlayStartTime = -100.0f; + return; + } + + // also hide if a key was pressed or maybe even if the mouse was moved (too much) + ImVec2 mdv = ImGui::GetMousePos() - warningOverlayStartPos; // Mouse Delta Vector + float mouseDelta = sqrtf( mdv.x * mdv.x + mdv.y * mdv.y ); + const float fontSize = ImGui::GetFontSize(); + if ( mouseDelta > fontSize * 4.0f || hadKeyDownEvent ) { + warningOverlayStartTime = -100.0f; + return; + } + + ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); + ImGui::PushStyleColor( ImGuiCol_WindowBg, ImVec4(1.0f, 0.4f, 0.4f, 0.4f) ); + float padSize = fontSize * 2.0f; + ImGui::PushStyleVar( ImGuiStyleVar_WindowPadding, ImVec2(padSize, padSize) ); + + int winFlags = ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove + | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_AlwaysAutoResize; + ImGui::Begin("WarningOverlay", NULL, winFlags); + + ImDrawList* drawList = ImGui::GetWindowDrawList(); + ImVec2 points[] = { + {0, 40}, {40, 40}, {20, 0}, // triangle + {20, 12}, {20, 28}, // line + {20, 33} // dot + }; + + float iconScale = 1.0f; // TODO: global scale also used for fontsize + + ImVec2 offset = ImGui::GetWindowPos() + ImVec2(fontSize, fontSize); + for ( ImVec2& v : points ) { + v.x = roundf( v.x * iconScale ); + v.y = roundf( v.y * iconScale ); + v += offset; + } + + ImU32 color = ImGui::GetColorU32( ImVec4(0.1f, 0.1f, 0.1f, 1.0f) ); + + drawList->AddTriangle( points[0], points[1], points[2], color, roundf( iconScale * 4.0f ) ); + + drawList->AddPolyline( points+3, 2, color, 0, roundf( iconScale * 3.0f ) ); + + float dotRadius = 2.0f * iconScale; + drawList->AddEllipseFilled( points[5], ImVec2(dotRadius, dotRadius), color, 0, 6 ); + + ImGui::Indent( 40.0f * iconScale ); + ImGui::TextUnformatted( warningOverlayText.c_str() ); + + ImGui::End(); + + ImGui::PopStyleVar(); // WindowPadding + ImGui::PopStyleColor(); // WindowBg +} + +void ShowWarningOverlay( const char* text ) +{ + warningOverlayText = text; + warningOverlayStartTime = ImGui::GetTime(); + warningOverlayStartPos = ImGui::GetMousePos(); +} + + static float GetDefaultDPI() { SDL_Window* win = sdlWindow; @@ -203,6 +282,8 @@ void NewFrame() ImGui::NewFrame(); haveNewFrame = true; + UpdateWarningOverlay(); + if (openImguiWindows & D3_ImGuiWin_Settings) { Com_DrawDhewm3SettingsMenu(); } @@ -215,6 +296,8 @@ void NewFrame() } } +bool keybindModeEnabled = false; + // called with every SDL event by Sys_GetEvent() // returns true if ImGui has handled the event (so it shouldn't be handled by D3) bool ProcessEvent(const void* sdlEvent) @@ -228,14 +311,42 @@ bool ProcessEvent(const void* sdlEvent) // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. - if( ImGui_ImplSDL2_ProcessEvent( ev ) ) { + + bool imguiUsedEvent = ImGui_ImplSDL2_ProcessEvent( ev ); + if ( keybindModeEnabled ) { + // in keybind mode, all input events are passed to Doom3 so it can translate them + // to internal events and we can access and use them to create a new binding + return false; + } + + if ( ImGui::IsWindowFocused( ImGuiFocusedFlags_AnyWindow ) + && (ev->type == SDL_CONTROLLERAXISMOTION || ev->type == SDL_CONTROLLERBUTTONDOWN) ) { + // don't pass on controller axis events to avoid moving the mouse cursor + // and controller button events to avoid emulating mouse clicks + return true; + } + + switch( ev->type ) { + // Hack: detect if any key was pressed to close the warning overlay + case SDL_CONTROLLERBUTTONDOWN: + case SDL_MOUSEWHEEL: + case SDL_MOUSEBUTTONDOWN: + case SDL_KEYDOWN: + // TODO: controller trigger? + hadKeyDownEvent = true; + } + if( imguiUsedEvent ) { ImGuiIO& io = ImGui::GetIO(); + if ( io.WantCaptureMouse ) { switch( ev->type ) { case SDL_MOUSEMOTION: case SDL_MOUSEWHEEL: case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: + + // Note: still pass button up events to the engine, so if they were pressed down + // before an imgui window got focus they don't remain pressed indefinitely + //case SDL_MOUSEBUTTONUP: return true; } } @@ -243,15 +354,30 @@ bool ProcessEvent(const void* sdlEvent) if ( io.WantCaptureKeyboard ) { switch( ev->type ) { case SDL_TEXTINPUT: - case SDL_KEYDOWN: - case SDL_KEYUP: return true; + case SDL_KEYDOWN: + //case SDL_KEYUP: NOTE: see above why key up events are passed to the engine + if ( ev->key.keysym.sym < SDLK_F1 || ev->key.keysym.sym > SDLK_F12) { + // F1 - F12 are passed to the engine so its shortcuts + // (like quickload or screenshot) still work + // Doom3's menu does the same + return true; + } } } + } + return false; } +void SetKeyBindMode( bool enable ) +{ + keybindModeEnabled = enable; + // make sure no keys are registered as down, neither when entering nor when exiting keybind mode + idKeyInput::ClearStates(); +} + void EndFrame() { if (openImguiWindows == 0 && !haveNewFrame) @@ -297,6 +423,11 @@ void EndFrame() if ( curArrayBuffer != 0 ) { qglBindBufferARB( GL_ARRAY_BUFFER_ARB, curArrayBuffer ); } + + // reset this at the end of each frame, will be set again by ProcessEvent() + if ( hadKeyDownEvent ) { + hadKeyDownEvent = false; + } } diff --git a/neo/sys/sys_imgui.h b/neo/sys/sys_imgui.h index f0473c3f..f7997aad 100644 --- a/neo/sys/sys_imgui.h +++ b/neo/sys/sys_imgui.h @@ -41,6 +41,10 @@ extern int GetOpenWindowsMask(); // returns true if ImGui has handled the event (so it shouldn't be handled by D3) extern bool ProcessEvent(const void* sdlEvent); +// for binding keys from an ImGui-based menu: send input events to dhewm3 +// even if ImGui window has focus +extern void SetKeyBindMode( bool enable ); + // NewFrame() is called once per D3 frame, after all events have been gotten // => ProcessEvent() has already been called (probably multiple times) extern void NewFrame(); @@ -52,6 +56,11 @@ extern void EndFrame(); extern float GetScale(); extern void SetScale( float scale ); +// show a red overlay-window at the center of the screen that contains +// a warning symbol (triangle with !) and the given text +// disappears after a few seconds or when a key is pressed or the mouse is moved +extern void ShowWarningOverlay( const char* text ); + #else // IMGUI_DISABLE - just stub out everything inline bool IsImguiEnabled() @@ -69,6 +78,8 @@ inline void Shutdown() {} inline bool ProcessEvent(const void* sdlEvent) { return false; } +inline void SetKeyBindMode( bool enable ) {} + inline void NewFrame() {} inline void EndFrame() {} @@ -82,6 +93,8 @@ inline int GetOpenWindowsMask() { return 0; } inline float GetScale() { return 1.0f; } inline void SetScale( float scale ) {} +inline void ShowWarningOverlay( const char* text ) {} + #endif }} //namespace D3::ImGuiHooks diff --git a/neo/sys/sys_public.h b/neo/sys/sys_public.h index 7b24e769..dbff09df 100644 --- a/neo/sys/sys_public.h +++ b/neo/sys/sys_public.h @@ -218,6 +218,8 @@ const char* Sys_GetScancodeName( int key ); // Otherwise return same name as Sys_GetScancodeName() // !! Returned string is only valid until next call to this function !! const char* Sys_GetLocalizedScancodeName( int key ); +// the same, but using UTF-8 instead of "High-ASCII" +const char* Sys_GetLocalizedScancodeNameUTF8( int key ); // returns keyNum_t (K_SC_* constant) for given scancode name (like "SC_A") int Sys_GetKeynumForScancodeName( const char* name );