From 5af3e0d494e40f60da79a49cf444f2e993f4d1ac Mon Sep 17 00:00:00 2001 From: bicarus <202771338+bicarus-dev@users.noreply.github.com> Date: Thu, 2 Apr 2026 21:00:42 -0700 Subject: [PATCH] imgui: migrate to new event-based IO (#604) ## Link to GitHub Issue or related Pull Request, if one exists #287 ## Description of change Switch over to new event-based I/O system in ImGui so that we can keep up with ImGui updates. Unlike #287, this change does not modify how mouse position is handled. That continues to be supported by newer versions of ImGui. ## Testing wip --- src/spice2x/external/imgui/imconfig.h | 2 +- .../external/imgui/imgui_memory_editor.h | 422 +++++++++++------- src/spice2x/launcher/launcher.cpp | 2 +- src/spice2x/overlay/imgui/impl_spice.cpp | 245 ++++++---- src/spice2x/overlay/windows/iopanel_ddr.cpp | 4 + 5 files changed, 431 insertions(+), 244 deletions(-) diff --git a/src/spice2x/external/imgui/imconfig.h b/src/spice2x/external/imgui/imconfig.h index ff60d7f..be976a9 100644 --- a/src/spice2x/external/imgui/imconfig.h +++ b/src/spice2x/external/imgui/imconfig.h @@ -29,7 +29,7 @@ //---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names. //#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS -//#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87+ disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This is automatically done by IMGUI_DISABLE_OBSOLETE_FUNCTIONS. +#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87+ disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This is automatically done by IMGUI_DISABLE_OBSOLETE_FUNCTIONS. //---- Disable all of Dear ImGui or don't implement standard windows/tools. // It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp. diff --git a/src/spice2x/external/imgui/imgui_memory_editor.h b/src/spice2x/external/imgui/imgui_memory_editor.h index 27de524..8819ef3 100644 --- a/src/spice2x/external/imgui/imgui_memory_editor.h +++ b/src/spice2x/external/imgui/imgui_memory_editor.h @@ -1,6 +1,7 @@ // Mini memory editor for Dear ImGui (to embed in your game/tools) // Get latest version at http://www.github.com/ocornut/imgui_club -// +// Licensed under The MIT License (MIT) + // Right-click anywhere to access the Options menu! // You can adjust the keyboard repeat delay/rate in ImGuiIO. // The code assume a mono-space font for simplicity! @@ -39,10 +40,22 @@ // - v0.43 (2021/03/12): added OptFooterExtraHeight to allow for custom drawing at the bottom of the editor [@leiradel] // - v0.44 (2021/03/12): use ImGuiInputTextFlags_AlwaysOverwrite in 1.82 + fix hardcoded width. // - v0.50 (2021/11/12): various fixes for recent dear imgui versions (fixed misuse of clipper, relying on SetKeyboardFocusHere() handling scrolling from 1.85). added default size. +// - v0.51 (2024/02/22): fix for layout change in 1.89 when using IMGUI_DISABLE_OBSOLETE_FUNCTIONS. (#34) +// - v0.52 (2024/03/08): removed unnecessary GetKeyIndex() calls, they are a no-op since 1.87. +// - v0.53 (2024/05/27): fixed right-click popup from not appearing when using DrawContents(). warning fixes. (#35) +// - v0.54 (2024/07/29): allow ReadOnly mode to still select and preview data. (#46) [@DeltaGW2]) +// - v0.55 (2024/08/19): added BgColorFn to allow setting background colors independently from highlighted selection. (#27) [@StrikerX3] +// added MouseHoveredAddr public readable field. (#47, #27) [@StrikerX3] +// fixed a data preview crash with 1.91.0 WIP. fixed contiguous highlight color when using data preview. +// *BREAKING* added UserData field passed to all optional function handlers: ReadFn, WriteFn, HighlightFn, BgColorFn. (#50) [@silverweed] +// - v0.56 (2024/11/04): fixed MouseHovered, MouseHoveredAddr not being set when hovering a byte being edited. (#54) +// - v0.57 (2025/03/26): fixed warnings. using ImGui's ImSXX/ImUXX types instead of e.g. int32_t/uint32_t. (#56) +// - v0.58 (2025/03/31): fixed extraneous footer spacing (added in 0.51) breaking vertical auto-resize. (#53) +// - v0.59 (2025/04/08): fixed GotoAddrAndHighlight() not working if OptShowOptions is disabled. // -// Todo/Bugs: +// TODO: // - This is generally old/crappy code, it should work but isn't very good.. to be rewritten some day. -// - PageUp/PageDown are supported because we use _NoNav. This is a good test scenario for working out idioms of how to mix natural nav and our own... +// - PageUp/PageDown are not supported because we use _NoNav. This is a good test scenario for working out idioms of how to mix natural nav and our own... // - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text cursor appear at position 1 for one frame. // - Using InputText() is awkward and maybe overkill here, consider implementing something custom. @@ -51,15 +64,18 @@ #include // sprintf, scanf #include // uint8_t, etc. -#ifdef _MSC_VER -#define _PRISizeT "I" +#if defined(_MSC_VER) && !defined(snprintf) #define ImSnprintf _snprintf #else -#define _PRISizeT "z" #define ImSnprintf snprintf #endif +#if defined(_MSC_VER) && !defined(__clang__) +#define _PRISizeT "I" +#else +#define _PRISizeT "z" +#endif -#ifdef _MSC_VER +#if defined(_MSC_VER) || defined(_UCRT) #pragma warning (push) #pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe. #endif @@ -88,9 +104,17 @@ struct MemoryEditor int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr). float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets ImU32 HighlightColor; // // background color of highlighted bytes. - ImU8 (*ReadFn)(const ImU8* data, size_t off); // = 0 // optional handler to read bytes. - void (*WriteFn)(ImU8* data, size_t off, ImU8 d); // = 0 // optional handler to write bytes. - bool (*HighlightFn)(const ImU8* data, size_t off);//= 0 // optional handler to return Highlight property (to support non-contiguous highlighting). + + // Function handlers + ImU8 (*ReadFn)(const ImU8* mem, size_t off, void* user_data); // = 0 // optional handler to read bytes. + void (*WriteFn)(ImU8* mem, size_t off, ImU8 d, void* user_data); // = 0 // optional handler to write bytes. + bool (*HighlightFn)(const ImU8* mem, size_t off, void* user_data); // = 0 // optional handler to return Highlight property (to support non-contiguous highlighting). + ImU32 (*BgColorFn)(const ImU8* mem, size_t off, void* user_data); // = 0 // optional handler to return custom background color of individual bytes. + void* UserData; // = NULL // user data forwarded to the function handlers + + // Public read-only data + bool MouseHovered; // set when mouse is hovering a value. + size_t MouseHoveredAddr; // the address currently being hovered if MouseHovered is set. // [Internal State] bool ContentsWidthChanged; @@ -101,7 +125,7 @@ struct MemoryEditor char AddrInputBuf[32]; size_t GotoAddr; size_t HighlightMin, HighlightMax; - int PreviewEndianess; + int PreviewEndianness; ImGuiDataType PreviewDataType; MemoryEditor() @@ -120,9 +144,11 @@ struct MemoryEditor OptAddrDigitsCount = 0; OptFooterExtraHeight = 0.0f; HighlightColor = IM_COL32(255, 255, 255, 50); - ReadFn = NULL; - WriteFn = NULL; - HighlightFn = NULL; + ReadFn = nullptr; + WriteFn = nullptr; + HighlightFn = nullptr; + BgColorFn = nullptr; + UserData = nullptr; // State/Internals ContentsWidthChanged = false; @@ -131,8 +157,10 @@ struct MemoryEditor memset(DataInputBuf, 0, sizeof(DataInputBuf)); memset(AddrInputBuf, 0, sizeof(AddrInputBuf)); GotoAddr = (size_t)-1; + MouseHovered = false; + MouseHoveredAddr = 0; HighlightMin = HighlightMax = (size_t)-1; - PreviewEndianess = 0; + PreviewEndianness = 0; PreviewDataType = ImGuiDataType_S32; } @@ -145,16 +173,16 @@ struct MemoryEditor struct Sizes { - int AddrDigitsCount; - float LineHeight; - float GlyphWidth; - float HexCellWidth; - float SpacingBetweenMidCols; - float PosHexStart; - float PosHexEnd; - float PosAsciiStart; - float PosAsciiEnd; - float WindowWidth; + int AddrDigitsCount; // Number of digits required to represent maximum address. + float LineHeight; // Height of each line (no spacing). + float GlyphWidth; // Glyph width (assume mono-space). + float HexCellWidth; // Width of a hex edit cell ~2.5f * GlypHWidth. + float SpacingBetweenMidCols; // Spacing between each columns section (OptMidColsCount). + float OffsetHexMinX; + float OffsetHexMaxX; + float OffsetAsciiMinX; + float OffsetAsciiMaxX; + float WindowWidth; // Ideal window width. Sizes() { memset(this, 0, sizeof(*this)); } }; @@ -170,17 +198,17 @@ struct MemoryEditor s.GlyphWidth = ImGui::CalcTextSize("F").x + 1; // We assume the font is mono-space s.HexCellWidth = (float)(int)(s.GlyphWidth * 2.5f); // "FF " we include trailing space in the width to easily catch clicks everywhere s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing - s.PosHexStart = (s.AddrDigitsCount + 2) * s.GlyphWidth; - s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Cols); - s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd; + s.OffsetHexMinX = (s.AddrDigitsCount + 2) * s.GlyphWidth; + s.OffsetHexMaxX = s.OffsetHexMinX + (s.HexCellWidth * Cols); + s.OffsetAsciiMinX = s.OffsetAsciiMaxX = s.OffsetHexMaxX; if (OptShowAscii) { - s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1; + s.OffsetAsciiMinX = s.OffsetHexMaxX + s.GlyphWidth * 1; if (OptMidColsCount > 0) - s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols; - s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth; + s.OffsetAsciiMinX += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols; + s.OffsetAsciiMaxX = s.OffsetAsciiMinX + Cols * s.GlyphWidth; } - s.WindowWidth = s.PosAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth; + s.WindowWidth = s.OffsetAsciiMaxX + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth; } // Standalone Memory Editor window @@ -194,8 +222,6 @@ struct MemoryEditor Open = true; if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar)) { - if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right)) - ImGui::OpenPopup("context"); DrawContents(mem_data, mem_size, base_display_addr); if (ContentsWidthChanged) { @@ -217,6 +243,8 @@ struct MemoryEditor CalcSizes(s, mem_size, base_display_addr); ImGuiStyle& style = ImGui::GetStyle(); + const ImVec2 contents_pos_start = ImGui::GetCursorScreenPos(); + // We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window. // This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move. const float height_separator = style.ItemSpacing.y; @@ -225,20 +253,21 @@ struct MemoryEditor footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1; if (OptShowDataPreview) footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3; - ImGui::BeginChild("##scrolling", ImVec2(0, -footer_height), false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav); + ImGui::BeginChild("##scrolling", ImVec2(-FLT_MIN, -footer_height), ImGuiChildFlags_None, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav); ImDrawList* draw_list = ImGui::GetWindowDrawList(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); // We are not really using the clipper API correctly here, because we rely on visible_start_addr/visible_end_addr for our scrolling function. + const ImVec2 avail_size = ImGui::GetContentRegionAvail(); const int line_total_count = (int)((mem_size + Cols - 1) / Cols); ImGuiListClipper clipper; clipper.Begin(line_total_count, s.LineHeight); bool data_next = false; - if (ReadOnly || DataEditingAddr >= mem_size) + if (DataEditingAddr >= mem_size) DataEditingAddr = (size_t)-1; if (DataPreviewAddr >= mem_size) DataPreviewAddr = (size_t)-1; @@ -249,16 +278,16 @@ struct MemoryEditor if (DataEditingAddr != (size_t)-1) { // Move cursor but only apply on next frame so scrolling with be synchronized (because currently we can't change the scrolling while the window is being rendered) - if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)) && (ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; } - else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols) { data_editing_addr_next = DataEditingAddr + Cols; } - else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)) && (ptrdiff_t)DataEditingAddr > (ptrdiff_t)0) { data_editing_addr_next = DataEditingAddr - 1; } - else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; } + if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) && (ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; } + else if (ImGui::IsKeyPressed(ImGuiKey_DownArrow) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols){ data_editing_addr_next = DataEditingAddr + Cols; } + else if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow) && (ptrdiff_t)DataEditingAddr > (ptrdiff_t)0) { data_editing_addr_next = DataEditingAddr - 1; } + else if (ImGui::IsKeyPressed(ImGuiKey_RightArrow) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; } } // Draw vertical separator ImVec2 window_pos = ImGui::GetWindowPos(); if (OptShowAscii) - draw_list->AddLine(ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border)); + draw_list->AddLine(ImVec2(window_pos.x + s.OffsetAsciiMinX - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.OffsetAsciiMinX - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border)); const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text); const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text; @@ -268,36 +297,51 @@ struct MemoryEditor const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x"; const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x "; + MouseHovered = false; + MouseHoveredAddr = 0; + while (clipper.Step()) for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines { - size_t addr = (size_t)(line_i * Cols); + size_t addr = (size_t)line_i * Cols; ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr); // Draw Hexadecimal for (int n = 0; n < Cols && addr < mem_size; n++, addr++) { - float byte_pos_x = s.PosHexStart + s.HexCellWidth * n; + float byte_pos_x = s.OffsetHexMinX + s.HexCellWidth * n; if (OptMidColsCount > 0) byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols; ImGui::SameLine(byte_pos_x); - // Draw highlight - bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax); - bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr)); - bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size); + // Draw highlight or custom background color + const bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax); + const bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr, UserData)); + const bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size); + + ImU32 bg_color = 0; + bool is_next_byte_highlighted = false; if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview) { - ImVec2 pos = ImGui::GetCursorScreenPos(); - float highlight_width = s.GlyphWidth * 2; - bool is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1))); + is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1, UserData)) || (addr + 1 < DataPreviewAddr + preview_data_type_size)); + bg_color = HighlightColor; + } + else if (BgColorFn != nullptr) + { + is_next_byte_highlighted = (addr + 1 < mem_size) && ((BgColorFn(mem_data, addr + 1, UserData) & IM_COL32_A_MASK) != 0); + bg_color = BgColorFn(mem_data, addr, UserData); + } + if (bg_color != 0) + { + float bg_width = s.GlyphWidth * 2; if (is_next_byte_highlighted || (n + 1 == Cols)) { - highlight_width = s.HexCellWidth; + bg_width = s.HexCellWidth; if (OptMidColsCount > 0 && n > 0 && (n + 1) < Cols && ((n + 1) % OptMidColsCount) == 0) - highlight_width += s.SpacingBetweenMidCols; + bg_width += s.SpacingBetweenMidCols; } - draw_list->AddRectFilled(pos, ImVec2(pos.x + highlight_width, pos.y + s.LineHeight), HighlightColor); + ImVec2 pos = ImGui::GetCursorScreenPos(); + draw_list->AddRectFilled(pos, ImVec2(pos.x + bg_width, pos.y + s.LineHeight), bg_color); } if (DataEditingAddr == addr) @@ -308,17 +352,21 @@ struct MemoryEditor if (DataEditingTakeFocus) { ImGui::SetKeyboardFocusHere(0); - sprintf(AddrInputBuf, format_data, s.AddrDigitsCount, base_display_addr + addr); - sprintf(DataInputBuf, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); + ImSnprintf(AddrInputBuf, 32, format_data, s.AddrDigitsCount, base_display_addr + addr); + ImSnprintf(DataInputBuf, 32, format_byte, ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr]); } - struct UserData + struct InputTextUserData { // FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here. static int Callback(ImGuiInputTextCallbackData* data) { - UserData* user_data = (UserData*)data->UserData; + InputTextUserData* user_data = (InputTextUserData*)data->UserData; if (!data->HasSelection()) user_data->CursorPos = data->CursorPos; +#if IMGUI_VERSION_NUM < 19102 + if (data->Flags & ImGuiInputTextFlags_ReadOnly) + return 0; +#endif if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen) { // When not editing a byte, always refresh its InputText content pulled from underlying memory data @@ -334,39 +382,42 @@ struct MemoryEditor char CurrentBufOverwrite[3]; // Input int CursorPos; // Output }; - UserData user_data; - user_data.CursorPos = -1; - sprintf(user_data.CurrentBufOverwrite, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); + InputTextUserData input_text_user_data; + input_text_user_data.CursorPos = -1; + ImSnprintf(input_text_user_data.CurrentBufOverwrite, 3, format_byte, ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr]); ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways; -#if IMGUI_VERSION_NUM >= 18104 - flags |= ImGuiInputTextFlags_AlwaysOverwrite; -#else - flags |= ImGuiInputTextFlags_AlwaysInsertMode; -#endif + if (ReadOnly) + flags |= ImGuiInputTextFlags_ReadOnly; + flags |= ImGuiInputTextFlags_AlwaysOverwrite; // was ImGuiInputTextFlags_AlwaysInsertMode ImGui::SetNextItemWidth(s.GlyphWidth * 2); - if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf), flags, UserData::Callback, &user_data)) + if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf), flags, InputTextUserData::Callback, &input_text_user_data)) data_write = data_next = true; else if (!DataEditingTakeFocus && !ImGui::IsItemActive()) DataEditingAddr = data_editing_addr_next = (size_t)-1; DataEditingTakeFocus = false; - if (user_data.CursorPos >= 2) + if (input_text_user_data.CursorPos >= 2) data_write = data_next = true; if (data_editing_addr_next != (size_t)-1) data_write = data_next = false; unsigned int data_input_value = 0; - if (data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1) + if (!ReadOnly && data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1) { if (WriteFn) - WriteFn(mem_data, addr, (ImU8)data_input_value); + WriteFn(mem_data, addr, (ImU8)data_input_value, UserData); else mem_data[addr] = (ImU8)data_input_value; } + if (ImGui::IsItemHovered()) + { + MouseHovered = true; + MouseHoveredAddr = addr; + } ImGui::PopID(); } else { // NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on. - ImU8 b = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; + ImU8 b = ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr]; if (OptShowHexII) { @@ -386,10 +437,15 @@ struct MemoryEditor else ImGui::Text(format_byte_space, b); } - if (!ReadOnly && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) + if (ImGui::IsItemHovered()) { - DataEditingTakeFocus = true; - data_editing_addr_next = addr; + MouseHovered = true; + MouseHoveredAddr = addr; + if (ImGui::IsMouseClicked(0)) + { + DataEditingTakeFocus = true; + data_editing_addr_next = addr; + } } } } @@ -397,15 +453,24 @@ struct MemoryEditor if (OptShowAscii) { // Draw ASCII values - ImGui::SameLine(s.PosAsciiStart); + ImGui::SameLine(s.OffsetAsciiMinX); ImVec2 pos = ImGui::GetCursorScreenPos(); - addr = line_i * Cols; + addr = (size_t)line_i * Cols; + + const float mouse_off_x = ImGui::GetIO().MousePos.x - pos.x; + const size_t mouse_addr = (mouse_off_x >= 0.0f && mouse_off_x < s.OffsetAsciiMaxX - s.OffsetAsciiMinX) ? addr + (size_t)(mouse_off_x / s.GlyphWidth) : (size_t)-1; + ImGui::PushID(line_i); - if (ImGui::InvisibleButton("ascii", ImVec2(s.PosAsciiEnd - s.PosAsciiStart, s.LineHeight))) + if (ImGui::InvisibleButton("ascii", ImVec2(s.OffsetAsciiMaxX - s.OffsetAsciiMinX, s.LineHeight))) { - DataEditingAddr = DataPreviewAddr = addr + (size_t)((ImGui::GetIO().MousePos.x - pos.x) / s.GlyphWidth); + DataEditingAddr = DataPreviewAddr = mouse_addr; DataEditingTakeFocus = true; } + if (ImGui::IsItemHovered()) + { + MouseHovered = true; + MouseHoveredAddr = mouse_addr; + } ImGui::PopID(); for (int n = 0; n < Cols && addr < mem_size; n++, addr++) { @@ -414,7 +479,11 @@ struct MemoryEditor draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg)); draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg)); } - unsigned char c = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; + else if (BgColorFn) + { + draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), BgColorFn(mem_data, addr, UserData)); + } + unsigned char c = ReadFn ? ReadFn(mem_data, addr, UserData) : mem_data[addr]; char display_c = (c < 32 || c >= 128) ? '.' : c; draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1); pos.x += s.GlyphWidth; @@ -422,10 +491,14 @@ struct MemoryEditor } } ImGui::PopStyleVar(2); + const float child_width = ImGui::GetWindowSize().x; ImGui::EndChild(); // Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child) + ImVec2 backup_pos = ImGui::GetCursorScreenPos(); ImGui::SetCursorPosX(s.WindowWidth); + ImGui::Dummy(ImVec2(0.0f, 0.0f)); + ImGui::SetCursorScreenPos(backup_pos); if (data_next && DataEditingAddr + 1 < mem_size) { @@ -450,18 +523,28 @@ struct MemoryEditor ImGui::Separator(); DrawPreviewLine(s, mem_data, mem_size, base_display_addr); } - } - void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr) - { - IM_UNUSED(mem_data); - ImGuiStyle& style = ImGui::GetStyle(); - const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x"; + if (GotoAddr != (size_t)-1) + { + if (GotoAddr < mem_size) + { + ImGui::BeginChild("##scrolling"); + ImGui::SetScrollY((GotoAddr / Cols) * ImGui::GetTextLineHeight() - avail_size.y * 0.5f); + ImGui::EndChild(); + DataEditingAddr = DataPreviewAddr = GotoAddr; + DataEditingTakeFocus = true; + } + GotoAddr = (size_t)-1; + } - // Options menu - if (ImGui::Button("Options")) - ImGui::OpenPopup("context"); - if (ImGui::BeginPopup("context")) + const ImVec2 contents_pos_end(contents_pos_start.x + child_width, ImGui::GetCursorScreenPos().y); + //ImGui::GetForegroundDrawList()->AddRect(contents_pos_start, contents_pos_end, IM_COL32(255, 0, 0, 255)); + if (OptShowOptions) + if (ImGui::IsMouseHoveringRect(contents_pos_start, contents_pos_end)) + if (ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right)) + ImGui::OpenPopup("OptionsPopup"); + + if (ImGui::BeginPopup("OptionsPopup")) { ImGui::SetNextItemWidth(s.GlyphWidth * 7 + style.FramePadding.x * 2.0f); if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; if (Cols < 1) Cols = 1; } @@ -473,6 +556,17 @@ struct MemoryEditor ImGui::EndPopup(); } + } + + void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr) + { + IM_UNUSED(mem_data); + ImGuiStyle& style = ImGui::GetStyle(); + const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x"; + + // Options menu + if (ImGui::Button("Options")) + ImGui::OpenPopup("OptionsPopup"); ImGui::SameLine(); ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1); @@ -488,18 +582,11 @@ struct MemoryEditor } } - if (GotoAddr != (size_t)-1) - { - if (GotoAddr < mem_size) - { - ImGui::BeginChild("##scrolling"); - ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + (GotoAddr / Cols) * ImGui::GetTextLineHeight()); - ImGui::EndChild(); - DataEditingAddr = DataPreviewAddr = GotoAddr; - DataEditingTakeFocus = true; - } - GotoAddr = (size_t)-1; - } + //if (MouseHovered) + //{ + // ImGui::SameLine(); + // ImGui::Text("Hovered: %p", MouseHoveredAddr); + //} } void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size, size_t base_display_addr) @@ -511,16 +598,21 @@ struct MemoryEditor ImGui::Text("Preview as:"); ImGui::SameLine(); ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x); + + static const ImGuiDataType supported_data_types[] = { ImGuiDataType_S8, ImGuiDataType_U8, ImGuiDataType_S16, ImGuiDataType_U16, ImGuiDataType_S32, ImGuiDataType_U32, ImGuiDataType_S64, ImGuiDataType_U64, ImGuiDataType_Float, ImGuiDataType_Double }; if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest)) { - for (int n = 0; n < ImGuiDataType_COUNT; n++) - if (ImGui::Selectable(DataTypeGetDesc((ImGuiDataType)n), PreviewDataType == n)) - PreviewDataType = (ImGuiDataType)n; + for (int n = 0; n < IM_ARRAYSIZE(supported_data_types); n++) + { + ImGuiDataType data_type = supported_data_types[n]; + if (ImGui::Selectable(DataTypeGetDesc(data_type), PreviewDataType == data_type)) + PreviewDataType = data_type; + } ImGui::EndCombo(); } ImGui::SameLine(); ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x); - ImGui::Combo("##combo_endianess", &PreviewEndianess, "LE\0BE\0\0"); + ImGui::Combo("##combo_endianness", &PreviewEndianness, "LE\0BE\0\0"); char buf[128] = ""; float x = s.GlyphWidth * 6.0f; @@ -537,18 +629,19 @@ struct MemoryEditor ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); } - // Utilities for Data Preview + // Utilities for Data Preview (since we don't access imgui_internal.h) + // FIXME: This technically depends on ImGuiDataType order. const char* DataTypeGetDesc(ImGuiDataType data_type) const { const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" }; - IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); + IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(descs)); return descs[data_type]; } size_t DataTypeGetSize(ImGuiDataType data_type) const { const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double) }; - IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); + IM_ASSERT(data_type >= 0 && data_type < IM_ARRAYSIZE(sizes)); return sizes[data_type]; } @@ -561,18 +654,18 @@ struct MemoryEditor bool IsBigEndian() const { - uint16_t x = 1; + ImU16 x = 1; char c[2]; memcpy(c, &x, 2); return c[0] != 0; } - static void* EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian) + static void* EndiannessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian) { if (is_little_endian) { - uint8_t* dst = (uint8_t*)_dst; - uint8_t* src = (uint8_t*)_src + s - 1; + ImU8* dst = (ImU8*)_dst; + ImU8* src = (ImU8*)_src + s - 1; for (int i = 0, n = (int)s; i < n; ++i) memcpy(dst++, src--, 1); return _dst; @@ -583,7 +676,7 @@ struct MemoryEditor } } - static void* EndianessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian) + static void* EndiannessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian) { if (is_little_endian) { @@ -591,23 +684,23 @@ struct MemoryEditor } else { - uint8_t* dst = (uint8_t*)_dst; - uint8_t* src = (uint8_t*)_src + s - 1; + ImU8* dst = (ImU8*)_dst; + ImU8* src = (ImU8*)_src + s - 1; for (int i = 0, n = (int)s; i < n; ++i) memcpy(dst++, src--, 1); return _dst; } } - void* EndianessCopy(void* dst, void* src, size_t size) const + void* EndiannessCopy(void* dst, void* src, size_t size) const { - static void* (*fp)(void*, void*, size_t, int) = NULL; - if (fp == NULL) - fp = IsBigEndian() ? EndianessCopyBigEndian : EndianessCopyLittleEndian; - return fp(dst, src, size, PreviewEndianess); + static void* (*fp)(void*, void*, size_t, int) = nullptr; + if (fp == nullptr) + fp = IsBigEndian() ? EndiannessCopyBigEndian : EndiannessCopyLittleEndian; + return fp(dst, src, size, PreviewEndianness); } - const char* FormatBinary(const uint8_t* buf, int width) const + const char* FormatBinary(const ImU8* buf, int width) const { IM_ASSERT(width <= 64); size_t out_n = 0; @@ -627,19 +720,19 @@ struct MemoryEditor // [Internal] void DrawPreviewData(size_t addr, const ImU8* mem_data, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const { - uint8_t buf[8]; + ImU8 buf[8]; size_t elem_size = DataTypeGetSize(data_type); size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size; if (ReadFn) for (int i = 0, n = (int)size; i < n; ++i) - buf[i] = ReadFn(mem_data, addr + i); + buf[i] = ReadFn(mem_data, addr + i, UserData); else memcpy(buf, mem_data + addr, size); if (data_format == DataFormat_Bin) { - uint8_t binbuf[8]; - EndianessCopy(binbuf, buf, size); + ImU8 binbuf[8]; + EndiannessCopy(binbuf, buf, size); ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8)); return; } @@ -649,84 +742,85 @@ struct MemoryEditor { case ImGuiDataType_S8: { - int8_t int8 = 0; - EndianessCopy(&int8, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", int8); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", int8 & 0xFF); return; } + ImS8 data = 0; + EndiannessCopy(&data, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", data); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0xFF); return; } break; } case ImGuiDataType_U8: { - uint8_t uint8 = 0; - EndianessCopy(&uint8, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", uint8); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", uint8 & 0XFF); return; } + ImU8 data = 0; + EndiannessCopy(&data, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", data); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", data & 0XFF); return; } break; } case ImGuiDataType_S16: { - int16_t int16 = 0; - EndianessCopy(&int16, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", int16); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", int16 & 0xFFFF); return; } + ImS16 data = 0; + EndiannessCopy(&data, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", data); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; } break; } case ImGuiDataType_U16: { - uint16_t uint16 = 0; - EndianessCopy(&uint16, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", uint16); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", uint16 & 0xFFFF); return; } + ImU16 data = 0; + EndiannessCopy(&data, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", data); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", data & 0xFFFF); return; } break; } case ImGuiDataType_S32: { - int32_t int32 = 0; - EndianessCopy(&int32, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", int32); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", int32); return; } + ImS32 data = 0; + EndiannessCopy(&data, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", data); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; } break; } case ImGuiDataType_U32: { - uint32_t uint32 = 0; - EndianessCopy(&uint32, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", uint32); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", uint32); return; } + ImU32 data = 0; + EndiannessCopy(&data, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", data); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", data); return; } break; } case ImGuiDataType_S64: { - int64_t int64 = 0; - EndianessCopy(&int64, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)int64); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)int64); return; } + ImS64 data = 0; + EndiannessCopy(&data, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)data); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; } break; } case ImGuiDataType_U64: { - uint64_t uint64 = 0; - EndianessCopy(&uint64, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)uint64); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)uint64); return; } + ImU64 data = 0; + EndiannessCopy(&data, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)data); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)data); return; } break; } case ImGuiDataType_Float: { - float float32 = 0.0f; - EndianessCopy(&float32, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float32); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float32); return; } + float data = 0.0f; + EndiannessCopy(&data, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", data); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; } break; } case ImGuiDataType_Double: { - double float64 = 0.0; - EndianessCopy(&float64, buf, size); - if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float64); return; } - if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float64); return; } + double data = 0.0; + EndiannessCopy(&data, buf, size); + if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", data); return; } + if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", data); return; } break; } + default: case ImGuiDataType_COUNT: break; } // Switch diff --git a/src/spice2x/launcher/launcher.cpp b/src/spice2x/launcher/launcher.cpp index 89c955e..9693bd5 100644 --- a/src/spice2x/launcher/launcher.cpp +++ b/src/spice2x/launcher/launcher.cpp @@ -980,7 +980,7 @@ int main_implementation(int argc, char *argv[]) { // do not explicitly set GRAPHICS_9_ON_12_STATE to default here; must respect // legacy Graphics9On12 option from above if set } - if (options[launcher::Options::NoLegacy].value_bool() && !cfg::CONFIGURATOR_STANDALONE) { + if (options[launcher::Options::NoLegacy].value_bool() && !cfg_run) { rawinput::NOLEGACY = true; } if (options[launcher::Options::RichPresence].value_bool()) { diff --git a/src/spice2x/overlay/imgui/impl_spice.cpp b/src/spice2x/overlay/imgui/impl_spice.cpp index 5e68a65..721a2de 100644 --- a/src/spice2x/overlay/imgui/impl_spice.cpp +++ b/src/spice2x/overlay/imgui/impl_spice.cpp @@ -15,6 +15,15 @@ #include "util/utils.h" #include "hooks/graphics/graphics.h" +#define DEBUG_VERBOSE 0 + +#if DEBUG_VERBOSE +#define log_debug(module, format_str, ...) logger::push( \ + LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY) +#else +#define log_debug(module, format_str, ...) +#endif + #if !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) || \ !defined(IMGUI_DISABLE_DEFAULT_ALLOCATORS) || \ !defined(IMGUI_USE_BGRA_PACKED_COLOR) || \ @@ -33,6 +42,95 @@ static ImGuiMouseCursor g_LastMouseCursor = ImGuiMouseCursor_COUNT; static double g_LastMouseMovement = 0.f; static bool g_MouseCursorAutoHide = false; +constexpr size_t VKEY_MAX = 255; +static std::array g_KeysDown; +static std::array g_MouseDown; + +static ImGuiKey get_imgui_key(int vkey) { + switch (vkey) { + case VK_TAB: + return ImGuiKey_Tab; + case VK_LEFT: + return ImGuiKey_LeftArrow; + case VK_RIGHT: + return ImGuiKey_RightArrow; + case VK_UP: + return ImGuiKey_UpArrow; + case VK_DOWN: + return ImGuiKey_DownArrow; + case VK_PRIOR: + return ImGuiKey_PageUp; + case VK_NEXT: + return ImGuiKey_PageDown; + case VK_HOME: + return ImGuiKey_Home; + case VK_END: + return ImGuiKey_End; + case VK_INSERT: + return ImGuiKey_Insert; + case VK_DELETE: + return ImGuiKey_Delete; + case VK_BACK: + return ImGuiKey_Backspace; + case VK_SPACE: + return ImGuiKey_Space; + case VK_RETURN: + return ImGuiKey_Enter; + case VK_ESCAPE: + return ImGuiKey_Escape; + case VK_SHIFT: + case VK_LSHIFT: + return ImGuiKey_LeftShift; + case VK_RSHIFT: + return ImGuiKey_RightShift; + case VK_CONTROL: + case VK_LCONTROL: + return ImGuiKey_LeftCtrl; + case VK_RCONTROL: + return ImGuiKey_RightCtrl; + case 'A': + case 'a': + return ImGuiKey_A; + case 'C': + case 'c': + return ImGuiKey_C; + case 'V': + case 'v': + return ImGuiKey_V; + case 'X': + case 'x': + return ImGuiKey_X; + case 'Y': + case 'y': + return ImGuiKey_Y; + case 'Z': + case 'z': + return ImGuiKey_Z; + default: + return ImGuiKey_None; + } +} + +static ImGuiKey get_imgui_mod_key(int vkey) { + switch (vkey) { + case VK_SHIFT: + case VK_LSHIFT: + case VK_RSHIFT: + return ImGuiMod_Shift; + return ImGuiMod_Shift; + case VK_CONTROL: + case VK_LCONTROL: + case VK_RCONTROL: + return ImGuiMod_Ctrl; + case VK_MENU: + case VK_LMENU: + case VK_RMENU: + return ImGuiMod_Alt; + default: + return ImGuiMod_None; + } +} + bool ImGui_ImplSpice_Init(HWND hWnd) { log_misc("imgui_impl_spice", "init"); @@ -59,30 +157,6 @@ bool ImGui_ImplSpice_Init(HWND hWnd) { io.BackendPlatformName = "imgui_impl_spice"; io.ConfigErrorRecoveryEnableTooltip = true; - // keyboard mapping - io.KeyMap[ImGuiKey_Tab] = VK_TAB; - io.KeyMap[ImGuiKey_LeftArrow] = VK_LEFT; - io.KeyMap[ImGuiKey_RightArrow] = VK_RIGHT; - io.KeyMap[ImGuiKey_UpArrow] = VK_UP; - io.KeyMap[ImGuiKey_DownArrow] = VK_DOWN; - io.KeyMap[ImGuiKey_PageUp] = VK_PRIOR; - io.KeyMap[ImGuiKey_PageDown] = VK_NEXT; - io.KeyMap[ImGuiKey_Home] = VK_HOME; - io.KeyMap[ImGuiKey_End] = VK_END; - io.KeyMap[ImGuiKey_Insert] = VK_INSERT; - io.KeyMap[ImGuiKey_Delete] = VK_DELETE; - io.KeyMap[ImGuiKey_Backspace] = VK_BACK; - io.KeyMap[ImGuiKey_Space] = VK_SPACE; - io.KeyMap[ImGuiKey_Enter] = VK_RETURN; - io.KeyMap[ImGuiKey_Escape] = VK_ESCAPE; - io.KeyMap[ImGuiKey_KeypadEnter] = VK_RETURN; - io.KeyMap[ImGuiKey_A] = 'A'; - io.KeyMap[ImGuiKey_C] = 'C'; - io.KeyMap[ImGuiKey_V] = 'V'; - io.KeyMap[ImGuiKey_X] = 'X'; - io.KeyMap[ImGuiKey_Y] = 'Y'; - io.KeyMap[ImGuiKey_Z] = 'Z'; - // get display size ImGui_ImplSpice_UpdateDisplaySize(); @@ -248,7 +322,7 @@ static void ImGui_ImplSpice_UpdateMousePos() { } // delay press - io.MouseDown[0] = delay_touch++ >= delay_touch_target && last_touch_id == tp.id; + g_MouseDown[ImGuiMouseButton_Left] = delay_touch++ >= delay_touch_target && last_touch_id == tp.id; if (last_touch_id == ~0u) { last_touch_id = tp.id; } @@ -275,38 +349,26 @@ void ImGui_ImplSpice_NewFrame() { g_Time = current_time; // remember old state - BYTE KeysDownOld[sizeof(io.KeysDown)]; - for (size_t i = 0; i < sizeof(io.KeysDown); i++) { - KeysDownOld[i] = io.KeysDown[i] ? ~0 : 0; + std::array KeysDownOld; + for (size_t i = 0; i < VKEY_MAX; i++) { + KeysDownOld[i] = g_KeysDown[i] ? ~0 : 0; } - KeysDownOld[VK_SHIFT] |= KeysDownOld[VK_LSHIFT]; - KeysDownOld[VK_SHIFT] |= KeysDownOld[VK_RSHIFT]; + + const auto MouseDownOld = g_MouseDown; // reset keys state - io.MouseWheel = 0; - io.KeyCtrl = false; - io.KeyShift = false; - io.KeyAlt = false; - io.KeySuper = false; - memset(io.KeysDown, false, sizeof(io.KeysDown)); - memset(io.MouseDown, false, sizeof(io.MouseDown)); + g_MouseDown.fill(false); + g_KeysDown.fill(false); // early quit if window not in focus if (!superexit::has_focus() || rawinput::OS_WINDOW_ACTIVE) { return; } - // read keyboard modifiers inputs - io.KeyCtrl = (::GetKeyState(VK_CONTROL) & 0x8000) != 0; - io.KeyShift = (::GetKeyState(VK_SHIFT) & 0x8000) != 0; - io.KeyAlt = (::GetKeyState(VK_MENU) & 0x8000) != 0; - io.KeySuper = (::GetKeyState(VK_LWIN) & 0x8000) != 0; - io.KeySuper |= (::GetKeyState(VK_RWIN) & 0x8000) != 0; - // apply windows mouse buttons - io.MouseDown[0] |= (get_async_primary_mouse()) != 0; - io.MouseDown[1] |= (get_async_secondary_mouse()) != 0; - io.MouseDown[2] |= (GetAsyncKeyState(VK_MBUTTON)) != 0; + g_MouseDown[ImGuiMouseButton_Left] |= (get_async_primary_mouse()) != 0; + g_MouseDown[ImGuiMouseButton_Right] |= (get_async_secondary_mouse()) != 0; + g_MouseDown[ImGuiMouseButton_Middle] |= (GetAsyncKeyState(VK_MBUTTON)) != 0; // read new keys state static long mouse_wheel_last = 0; @@ -321,21 +383,21 @@ void ImGui_ImplSpice_NewFrame() { // mouse button triggers if (GetSystemMetrics(SM_SWAPBUTTON)) { if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) { - io.MouseDown[0] = true; + g_MouseDown[ImGuiMouseButton_Left] = true; } if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) { - io.MouseDown[1] = true; + g_MouseDown[ImGuiMouseButton_Right] = true; } } else { if (mouse->key_states[rawinput::MOUSEBTN_LEFT]) { - io.MouseDown[0] = true; + g_MouseDown[ImGuiMouseButton_Left] = true; } if (mouse->key_states[rawinput::MOUSEBTN_RIGHT]) { - io.MouseDown[1] = true; + g_MouseDown[ImGuiMouseButton_Right] = true; } } if (mouse->key_states[rawinput::MOUSEBTN_MIDDLE]) { - io.MouseDown[2] = true; + g_MouseDown[ImGuiMouseButton_Middle] = true; } // final mouse wheel value should be all devices combined @@ -346,33 +408,11 @@ void ImGui_ImplSpice_NewFrame() { case rawinput::KEYBOARD: { // iterate all virtual key codes - for (size_t vKey = 0; vKey < 256; vKey++) { - + for (size_t vKey = 0; vKey < VKEY_MAX; vKey++) { // get state (combined from all pages) auto &key_states = device.keyboardInfo->key_states; - bool state = false; for (size_t page_index = 0; page_index < 1024; page_index += 256) { - state |= key_states[page_index + vKey]; - } - - // trigger - io.KeysDown[vKey] |= state; - - // generate character input, but only if WM_CHAR didn't take over the - // functionality - if (!overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT && !KeysDownOld[vKey] && state) { - UCHAR buf[2]; - auto ret = ToAscii( - static_cast(vKey), - 0, - static_cast(KeysDownOld), - reinterpret_cast(buf), - 0); - if (ret > 0) { - for (int i = 0; i < ret; i++) { - overlay::OVERLAY->input_char(buf[i]); - } - } + g_KeysDown[vKey] |= key_states[page_index + vKey]; } } break; @@ -383,22 +423,71 @@ void ImGui_ImplSpice_NewFrame() { } } + // process keyboard input from all keyboard collapsed into one state (g_KeysDown) + for (size_t vKey = 0; vKey < VKEY_MAX; vKey++) { + const bool state = g_KeysDown[vKey]; + const auto imgui_key = get_imgui_key(vKey); + const auto changed = + (state && !KeysDownOld[vKey]) || + (!state && KeysDownOld[vKey]); + + if (imgui_key != ImGuiKey_None && changed) { + io.AddKeyEvent(imgui_key, state); + log_debug("imgui_impl_spice", "vkey {:#x} added as navigation event, state: {}", static_cast(vKey), state); + + // mod key must also be processed separately + const auto imgui_mod_key = get_imgui_mod_key(vKey); + if (imgui_mod_key != ImGuiMod_None) { + io.AddKeyEvent(imgui_mod_key, state); + } + } + + // generate character input, but only if WM_CHAR didn't take over the functionality + // only detecting rising edges here - this means holding a key won't work + // (it's better than repeating a character input every frame - cost we pay for providing input + // on top of rawinput instead of WM_CHAR) + if (!overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT && !KeysDownOld[vKey] && state) { + UCHAR buf[2]; + auto ret = ToAscii( + static_cast(vKey), + 0, + static_cast(KeysDownOld.data()), + reinterpret_cast(buf), + 0); + if (ret > 0) { + for (int i = 0; i < ret; i++) { + overlay::OVERLAY->input_char(buf[i]); + log_debug("imgui_impl_spice", "vkey {:#x} inputted as character", vKey); + } + } + } + } + // set mouse wheel auto wheel_diff = mouse_wheel - mouse_wheel_last; mouse_wheel_last = mouse_wheel; - io.MouseWheel = wheel_diff; + io.AddMouseWheelEvent(0, wheel_diff); // update OS mouse position const auto old_mouse_pos = io.MousePos; ImGui_ImplSpice_UpdateMousePos(); const auto new_mouse_pos = io.MousePos; + // update mouse buttons + // doing this after ImGui_ImplSpice_UpdateMousePos since it can set g_MouseDown for touch input + for (size_t i = 0; i < g_MouseDown.size(); i++) { + if (MouseDownOld[i] != g_MouseDown[i]) { + io.AddMouseButtonEvent(i, g_MouseDown[i]); + log_debug("imgui_impl_spice", "mouse button {} event", g_MouseDown[i]); + } + } + // automatically hide cursor if (g_MouseCursorAutoHide) { if (old_mouse_pos.x != new_mouse_pos.x || old_mouse_pos.y != new_mouse_pos.y || wheel_diff != 0 || - io.MouseDown[0] || io.MouseDown[1] || io.MouseDown[2]) { + g_MouseDown[ImGuiMouseButton_Left] || g_MouseDown[ImGuiMouseButton_Right] || g_MouseDown[ImGuiMouseButton_Middle]) { // mouse moved, update time and show the cursor g_LastMouseMovement = get_performance_milliseconds(); diff --git a/src/spice2x/overlay/windows/iopanel_ddr.cpp b/src/spice2x/overlay/windows/iopanel_ddr.cpp index 0bba6ae..0dc9539 100644 --- a/src/spice2x/overlay/windows/iopanel_ddr.cpp +++ b/src/spice2x/overlay/windows/iopanel_ddr.cpp @@ -40,13 +40,17 @@ namespace overlay::windows { ImGui::Dummy(ImVec2(12, 0)); ImGui::SameLine(); + ImGui::PushID("P1"); this->draw_buttons(0); + ImGui::PopID(); ImGui::SameLine(); ImGui::Dummy(ImVec2(12, 0)); ImGui::SameLine(); + ImGui::PushID("P2"); this->draw_buttons(1); + ImGui::PopID(); } void DDRIOPanel::draw_buttons(const int p) {