From 973af4650c4e9a7e2f9e31da8be277e091c6ac8d Mon Sep 17 00:00:00 2001 From: SparkyTD <45818400+SparkyTD@users.noreply.github.com> Date: Fri, 10 May 2024 12:21:19 -0700 Subject: [PATCH] impr: Convert all hex editor popups to floating, movable windows (#1658) ### Problem description In previous versions of ImHex, all tool windows were implemented as static popups fixed in the upper left position of the hex view. This PR refactors all tool popups to use floating windows that can be dragged around by the user, or closed with a dedicated close button on the title bar. These popup also support a stylable transparency when the user is not hovering their mouse over the window. ### Implementation description I rewrote the logic in `ViewHexEditor::drawPopup()` to use a custom `ImGuiExt::BeginHoveringPopup` function for rendering the popup windows. This new function is an almost exact replica of the built-in `ImGui::BeginPopupModal`, except it does also displays the default window title bar with a close button. A second custom function, `ImGuiExt::PopupTitleBarButton` was also added for rendering small icon-based buttons into the title bar of the parent popup window. This new function was used to implement an optional "Pinning" feature that individual popup implementations can specify. If a window is pinned, it won't close automatically when its main action is executed. For example, the "Select" button on the Select dialog will close the popup by default, unless the window is pinned. ### Screenshots Popup dialogs before: ![image](https://github.com/WerWolv/ImHex/assets/45818400/7c253181-8284-4076-a066-089403554f0f) Popup dialogs after: https://github.com/WerWolv/ImHex/assets/45818400/99d1a628-8ac1-40ac-9146-9062091bb0db ### Additional things - When the user stops hovering their mouse over a popup window, it becomes semi-transparent, making it easier to see the content behind it - This PR also introduces the `styles.imhex.popup-alpha` style, making the transparency effect configurable, including the ability to disable the effect completely by setting `popup-alpha` to `1.0`. - Fixed a bug that caused some popup windows to ignore the Enter and the KeypadEnter keys. With this PR, all tool windows will execute their main action when the user presses either one of the two Enter keys, and will also close automatically unless the window is pinned. ### Possible changes and improvements - Should the transparency effect be disabled if a window is pinned? - Should the transparency factor be modifiable on the Settings/Interface page? - A keyboard shortcut could be added for quickly pinning / unpinning the current window. - Can the pin icon stay on the left, or should it be moved next to the close button, with a similar circular background? --------- Co-authored-by: WerWolv --- .../include/hex/ui/imgui_imhex_extensions.h | 4 + .../source/ui/imgui_imhex_extensions.cpp | 53 ++++++ .../hex_editor/popup_hex_editor_find.hpp | 2 + .../include/content/views/view_hex_editor.hpp | 11 ++ plugins/builtin/romfs/themes/classic.json | 3 +- plugins/builtin/romfs/themes/dark.json | 3 +- plugins/builtin/romfs/themes/light.json | 3 +- .../hex_editor/popup_hex_editor_find.cpp | 6 +- plugins/builtin/source/content/themes.cpp | 1 + .../source/content/views/view_hex_editor.cpp | 173 +++++++++++++----- 10 files changed, 208 insertions(+), 51 deletions(-) diff --git a/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h b/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h index 07de63df3..e28fced23 100644 --- a/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h +++ b/lib/libimhex/include/hex/ui/imgui_imhex_extensions.h @@ -161,6 +161,7 @@ namespace ImGuiExt { struct Styles { float WindowBlur = 0.0F; + float PopupWindowAlpha = 0.0F; // Alpha used by Popup tool windows when the user is not hovering over them } styles; }; @@ -304,6 +305,9 @@ namespace ImGuiExt { bool ToggleSwitch(const char *label, bool *v); bool ToggleSwitch(const char *label, bool v); + bool PopupTitleBarButton(const char* label, bool p_enabled); + void PopupTitleBarText(const char* text); + template constexpr ImGuiDataType getImGuiDataType() { if constexpr (std::same_as) return ImGuiDataType_U8; diff --git a/lib/libimhex/source/ui/imgui_imhex_extensions.cpp b/lib/libimhex/source/ui/imgui_imhex_extensions.cpp index 34d10bb9b..66ca25d29 100644 --- a/lib/libimhex/source/ui/imgui_imhex_extensions.cpp +++ b/lib/libimhex/source/ui/imgui_imhex_extensions.cpp @@ -1235,6 +1235,59 @@ namespace ImGuiExt { return ToggleSwitch(label, &v); } + bool PopupTitleBarButton(const char* label, bool p_enabled) + { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const ImGuiID id = window->GetID(label); + const ImRect title_rect = window->TitleBarRect(); + const ImVec2 size(g.FontSize, g.FontSize); // Button size matches font size for aesthetic consistency. + const ImVec2 pos = window->DC.CursorPos; + const ImVec2 max_pos = pos + size; + const ImRect bb(pos.x, title_rect.Min.y, max_pos.x, title_rect.Max.y); + + ImGui::PushClipRect(title_rect.Min, title_rect.Max, false); + + // Check for item addition (similar to how clipping is handled in the original button functions). + bool is_clipped = !ItemAdd(bb, id); + bool hovered, held; + bool pressed = ButtonBehavior(bb, id, &hovered, &held, ImGuiButtonFlags_None); + if (is_clipped) + { + ImGui::PopClipRect(); + return pressed; + } + + // const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); + // window->DrawList->AddCircleFilled(bb.GetCenter(), ImMax(2.0f, g.FontSize * 0.5f + 1.0f), bg_col); + + // Draw the label in the center + ImU32 text_col = GetColorU32(p_enabled || hovered ? ImGuiCol_Text : ImGuiCol_TextDisabled); + window->DrawList->AddText(bb.GetCenter() - ImVec2(g.FontSize * 0.45F, g.FontSize * 0.5F), text_col, label); + + // Return the button press state + ImGui::PopClipRect(); + return pressed; + } + + void PopupTitleBarText(const char* text) { + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + const ImRect title_rect = window->TitleBarRect(); + const ImVec2 size(g.FontSize, g.FontSize); // Button size matches font size for aesthetic consistency. + const ImVec2 pos = window->DC.CursorPos; + const ImVec2 max_pos = pos + size; + const ImRect bb(pos.x, title_rect.Min.y, max_pos.x, title_rect.Max.y); + + ImGui::PushClipRect(title_rect.Min, title_rect.Max, false); + + // Draw the label in the center + ImU32 text_col = GetColorU32(ImGuiCol_Text); + window->DrawList->AddText(bb.GetCenter() - ImVec2(g.FontSize * 0.45F, g.FontSize * 0.5F), text_col, text); + + // Return the button press state + ImGui::PopClipRect(); + } } namespace ImGui { diff --git a/plugins/builtin/include/content/popups/hex_editor/popup_hex_editor_find.hpp b/plugins/builtin/include/content/popups/hex_editor/popup_hex_editor_find.hpp index a5b205c28..4e7a2da25 100644 --- a/plugins/builtin/include/content/popups/hex_editor/popup_hex_editor_find.hpp +++ b/plugins/builtin/include/content/popups/hex_editor/popup_hex_editor_find.hpp @@ -54,6 +54,8 @@ namespace hex::plugin::builtin { TaskHolder m_searchTask; void processInputString(); + + [[nodiscard]] UnlocalizedString getTitle() const override; }; } // namespace hex::plugin::builtin \ No newline at end of file diff --git a/plugins/builtin/include/content/views/view_hex_editor.hpp b/plugins/builtin/include/content/views/view_hex_editor.hpp index 46735db9c..463b2e334 100644 --- a/plugins/builtin/include/content/views/view_hex_editor.hpp +++ b/plugins/builtin/include/content/views/view_hex_editor.hpp @@ -20,6 +20,14 @@ namespace hex::plugin::builtin { public: virtual ~Popup() = default; virtual void draw(ViewHexEditor *editor) = 0; + + [[nodiscard]] virtual UnlocalizedString getTitle() const { return {}; } + + [[nodiscard]] virtual bool canBePinned() const { return false; } + [[nodiscard]] bool isPinned() const { return m_isPinned; } + void setPinned(const bool pinned) { m_isPinned = pinned; } + private: + bool m_isPinned = false; }; [[nodiscard]] bool isAnyPopupOpen() const { @@ -71,6 +79,9 @@ namespace hex::plugin::builtin { ui::HexEditor m_hexEditor; bool m_shouldOpenPopup = false; + bool m_currentPopupHasHovered = false; // This flag prevents the popup from initially appearing with the transparency effect + bool m_currentPopupHover = false; + bool m_currentPopupDetached = false; std::unique_ptr m_currPopup; PerProvider> m_selectionStart, m_selectionEnd; diff --git a/plugins/builtin/romfs/themes/classic.json b/plugins/builtin/romfs/themes/classic.json index 8c1838e46..6c7d49be7 100644 --- a/plugins/builtin/romfs/themes/classic.json +++ b/plugins/builtin/romfs/themes/classic.json @@ -333,7 +333,8 @@ ] }, "imhex": { - "window-blur": 0.0 + "window-blur": 0.0, + "popup-alpha": 0.65 } } } \ No newline at end of file diff --git a/plugins/builtin/romfs/themes/dark.json b/plugins/builtin/romfs/themes/dark.json index e581cd82f..bb1cfd0de 100644 --- a/plugins/builtin/romfs/themes/dark.json +++ b/plugins/builtin/romfs/themes/dark.json @@ -333,7 +333,8 @@ ] }, "imhex": { - "window-blur": 0.0 + "window-blur": 0.0, + "popup-alpha": 0.65 } } } \ No newline at end of file diff --git a/plugins/builtin/romfs/themes/light.json b/plugins/builtin/romfs/themes/light.json index 06872b1af..e009a8c67 100644 --- a/plugins/builtin/romfs/themes/light.json +++ b/plugins/builtin/romfs/themes/light.json @@ -333,7 +333,8 @@ ] }, "imhex": { - "window-blur": 0.0 + "window-blur": 0.0, + "popup-alpha": 0.65 } } } \ No newline at end of file diff --git a/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_find.cpp b/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_find.cpp index 373e51aea..fab7aea08 100644 --- a/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_find.cpp +++ b/plugins/builtin/source/content/popups/hex_editor/popup_hex_editor_find.cpp @@ -29,8 +29,6 @@ namespace hex::plugin::builtin { } void PopupFind::draw(ViewHexEditor *editor) { - ImGui::TextUnformatted("hex.builtin.view.hex_editor.menu.file.search"_lang); - auto lastMode = *s_searchMode; if (ImGui::BeginTabBar("##find_tabs")) { if (ImGui::BeginTabItem("hex.builtin.view.hex_editor.search.hex"_lang)) { @@ -302,4 +300,8 @@ namespace hex::plugin::builtin { break; } } + + [[nodiscard]] UnlocalizedString PopupFind::getTitle() const { + return "hex.builtin.view.hex_editor.menu.file.search"; + } } \ No newline at end of file diff --git a/plugins/builtin/source/content/themes.cpp b/plugins/builtin/source/content/themes.cpp index 1e317b77a..196f85077 100644 --- a/plugins/builtin/source/content/themes.cpp +++ b/plugins/builtin/source/content/themes.cpp @@ -354,6 +354,7 @@ namespace hex::plugin::builtin { auto &style = ImGuiExt::GetCustomStyle(); const static ThemeManager::StyleMap ImHexStyleMap = { { "window-blur", { &style.WindowBlur, 0.0F, 1.0F, true } }, + { "popup-alpha", { &style.PopupWindowAlpha, 0.0F, 1.0F, false } }, }; ThemeManager::addStyleHandler("imhex", ImHexStyleMap); diff --git a/plugins/builtin/source/content/views/view_hex_editor.cpp b/plugins/builtin/source/content/views/view_hex_editor.cpp index 11d8c1c74..3fd1aeb27 100644 --- a/plugins/builtin/source/content/views/view_hex_editor.cpp +++ b/plugins/builtin/source/content/views/view_hex_editor.cpp @@ -31,7 +31,6 @@ namespace hex::plugin::builtin { class PopupGoto : public ViewHexEditor::Popup { public: void draw(ViewHexEditor *editor) override { - ImGui::TextUnformatted("hex.builtin.view.hex_editor.menu.file.goto"_lang); if (ImGui::BeginTabBar("goto_tabs")) { if (ImGui::BeginTabItem("hex.builtin.view.hex_editor.goto.offset.absolute"_lang)) { m_mode = Mode::Absolute; @@ -91,7 +90,8 @@ namespace hex::plugin::builtin { bool isOffsetValid = m_newAddress <= ImHexApi::Provider::get()->getActualSize(); bool executeGoto = false; - if (isOffsetValid && ImGui::IsItemFocused() && ImGui::IsKeyPressed(ImGuiKey_Enter)) { + + if (ImGui::IsWindowFocused() && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter))) { executeGoto = true; } @@ -108,12 +108,23 @@ namespace hex::plugin::builtin { if (executeGoto && m_newAddress.has_value()) { editor->setSelection(*m_newAddress, *m_newAddress); editor->jumpToSelection(); + + if (!this->isPinned()) + editor->closePopup(); } ImGui::EndTabBar(); } } + [[nodiscard]] UnlocalizedString getTitle() const override { + return "hex.builtin.view.hex_editor.menu.file.goto"; + } + + bool canBePinned() const override { + return true; + } + private: enum class Mode : u8 { Absolute, @@ -136,11 +147,15 @@ namespace hex::plugin::builtin { PopupSelect(u64 address, size_t size): m_region({address, size}) {} void draw(ViewHexEditor *editor) override { - ImGui::TextUnformatted("hex.builtin.view.hex_editor.menu.file.select"_lang); if (ImGui::BeginTabBar("select_tabs")) { if (ImGui::BeginTabItem("hex.builtin.view.hex_editor.select.offset.region"_lang)) { u64 inputA = m_region.getStartAddress(); u64 inputB = m_region.getEndAddress(); + + if (justOpened) { + ImGui::SetKeyboardFocusHere(); + justOpened = false; + } ImGuiExt::InputHexadecimal("hex.builtin.view.hex_editor.select.offset.begin"_lang, &inputA, ImGuiInputTextFlags_AutoSelectAll); ImGuiExt::InputHexadecimal("hex.builtin.view.hex_editor.select.offset.end"_lang, &inputB, ImGuiInputTextFlags_AutoSelectAll); @@ -155,6 +170,11 @@ namespace hex::plugin::builtin { if (ImGui::BeginTabItem("hex.builtin.view.hex_editor.select.offset.size"_lang)) { u64 inputA = m_region.getStartAddress(); u64 inputB = m_region.getSize(); + + if (justOpened) { + ImGui::SetKeyboardFocusHere(); + justOpened = false; + } ImGuiExt::InputHexadecimal("hex.builtin.view.hex_editor.select.offset.begin"_lang, &inputA, ImGuiInputTextFlags_AutoSelectAll); ImGuiExt::InputHexadecimal("hex.builtin.view.hex_editor.select.offset.size"_lang, &inputB, ImGuiInputTextFlags_AutoSelectAll); @@ -165,16 +185,12 @@ namespace hex::plugin::builtin { ImGui::EndTabItem(); } - const auto provider = ImHexApi::Provider::get(); - bool isOffsetValid = m_region.getStartAddress() <= m_region.getEndAddress() && - m_region.getEndAddress() < provider->getActualSize(); + if (ImGui::Button("hex.builtin.view.hex_editor.select.select"_lang) || (ImGui::IsWindowFocused() && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter)))) { + editor->setSelection(m_region.getStartAddress(), m_region.getEndAddress()); + editor->jumpToSelection(); - ImGui::BeginDisabled(!isOffsetValid); - { - if (ImGui::Button("hex.builtin.view.hex_editor.select.select"_lang) || (ImGui::IsItemFocused() && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_Enter)))) { - editor->setSelection(m_region.getStartAddress(), m_region.getEndAddress()); - editor->jumpToSelection(); - } + if (!this->isPinned()) + editor->closePopup(); } ImGui::EndDisabled(); @@ -182,8 +198,17 @@ namespace hex::plugin::builtin { } } + [[nodiscard]] UnlocalizedString getTitle() const override { + return "hex.builtin.view.hex_editor.menu.file.select"; + } + + [[nodiscard]] bool canBePinned() const override { + return true; + } + private: Region m_region = { 0, 1 }; + bool justOpened = true; }; class PopupBaseAddress : public ViewHexEditor::Popup { @@ -191,8 +216,6 @@ namespace hex::plugin::builtin { explicit PopupBaseAddress(u64 baseAddress) : m_baseAddress(baseAddress) { } void draw(ViewHexEditor *editor) override { - ImGui::TextUnformatted("hex.builtin.view.hex_editor.menu.edit.set_base"_lang); - ImGuiExt::InputHexadecimal("##base_address", &m_baseAddress); if (ImGui::IsItemFocused() && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter))) { setBaseAddress(m_baseAddress); @@ -210,6 +233,10 @@ namespace hex::plugin::builtin { ); } + [[nodiscard]] UnlocalizedString getTitle() const override { + return "hex.builtin.view.hex_editor.menu.edit.set_base"; + } + private: static void setBaseAddress(u64 baseAddress) { if (ImHexApi::Provider::isValid()) @@ -225,8 +252,6 @@ namespace hex::plugin::builtin { explicit PopupPageSize(u64 pageSize) : m_pageSize(pageSize) { } void draw(ViewHexEditor *editor) override { - ImGui::TextUnformatted("hex.builtin.view.hex_editor.menu.edit.set_page_size"_lang); - ImGuiExt::InputHexadecimal("##page_size", &m_pageSize); if (ImGui::IsItemFocused() && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter))) { setPageSize(m_pageSize); @@ -244,6 +269,10 @@ namespace hex::plugin::builtin { ); } + [[nodiscard]] UnlocalizedString getTitle() const override { + return "hex.builtin.view.hex_editor.menu.edit.set_page_size"; + } + private: static void setPageSize(u64 pageSize) { if (ImHexApi::Provider::isValid()) { @@ -263,8 +292,6 @@ namespace hex::plugin::builtin { explicit PopupResize(u64 currSize) : m_size(currSize) {} void draw(ViewHexEditor *editor) override { - ImGui::TextUnformatted("hex.builtin.view.hex_editor.menu.edit.resize"_lang); - ImGuiExt::InputHexadecimal("##resize", &m_size); if (ImGui::IsItemFocused() && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter))) { this->resize(m_size); @@ -281,6 +308,10 @@ namespace hex::plugin::builtin { }); } + [[nodiscard]] UnlocalizedString getTitle() const override { + return "hex.builtin.view.hex_editor.menu.edit.resize"; + } + private: static void resize(size_t newSize) { if (ImHexApi::Provider::isValid()) @@ -296,8 +327,6 @@ namespace hex::plugin::builtin { PopupInsert(u64 address, size_t size) : m_address(address), m_size(size) {} void draw(ViewHexEditor *editor) override { - ImGui::TextUnformatted("hex.builtin.view.hex_editor.menu.edit.insert"_lang); - ImGuiExt::InputHexadecimal("hex.ui.common.address"_lang, &m_address); ImGuiExt::InputHexadecimal("hex.ui.common.size"_lang, &m_size); @@ -309,6 +338,15 @@ namespace hex::plugin::builtin { [&]{ editor->closePopup(); }); + + if (ImGui::IsWindowFocused() && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter))) { + insert(m_address, m_size); + editor->closePopup(); + } + } + + [[nodiscard]] UnlocalizedString getTitle() const override { + return "hex.builtin.view.hex_editor.menu.edit.insert"; } private: @@ -327,8 +365,6 @@ namespace hex::plugin::builtin { PopupRemove(u64 address, size_t size) : m_address(address), m_size(size) {} void draw(ViewHexEditor *editor) override { - ImGui::TextUnformatted("hex.builtin.view.hex_editor.menu.edit.remove"_lang); - ImGuiExt::InputHexadecimal("hex.ui.common.address"_lang, &m_address); ImGuiExt::InputHexadecimal("hex.ui.common.size"_lang, &m_size); @@ -340,6 +376,15 @@ namespace hex::plugin::builtin { [&]{ editor->closePopup(); }); + + if (ImGui::IsWindowFocused() && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter))) { + remove(m_address, m_size); + editor->closePopup(); + } + } + + [[nodiscard]] UnlocalizedString getTitle() const override { + return "hex.builtin.view.hex_editor.menu.edit.remove"; } private: @@ -358,8 +403,6 @@ namespace hex::plugin::builtin { PopupFill(u64 address, size_t size) : m_address(address), m_size(size) {} void draw(ViewHexEditor *editor) override { - ImGui::TextUnformatted("hex.builtin.view.hex_editor.menu.edit.fill"_lang); - ImGuiExt::InputHexadecimal("hex.ui.common.address"_lang, &m_address); ImGuiExt::InputHexadecimal("hex.ui.common.size"_lang, &m_size); @@ -375,6 +418,15 @@ namespace hex::plugin::builtin { [&] { editor->closePopup(); }); + + if (ImGui::IsWindowFocused() && (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_KeypadEnter))) { + fill(m_address, m_size, m_input); + editor->closePopup(); + } + } + + [[nodiscard]] UnlocalizedString getTitle() const override { + return "hex.builtin.view.hex_editor.menu.edit.fill"; } private: @@ -509,37 +561,66 @@ namespace hex::plugin::builtin { } void ViewHexEditor::drawPopup() { - // Popup windows - if (m_shouldOpenPopup) { - m_shouldOpenPopup = false; - ImGui::OpenPopup("##hex_editor_popup"); + bool open = true; + + ImGui::SetNextWindowPos(ImGui::GetWindowPos() + ImGui::GetWindowContentRegionMin() - ImGui::GetStyle().WindowPadding, ImGuiCond_Once); + const auto configuredAlpha = ImGuiExt::GetCustomStyle().PopupWindowAlpha; + bool alphaIsChanged = false; + if (m_currPopup != nullptr && !m_currentPopupHover && m_currentPopupHasHovered && m_currentPopupDetached && configuredAlpha < 0.99F && configuredAlpha > 0.01F) { + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, configuredAlpha); + alphaIsChanged = true; } - static bool justOpened = true; + if (m_currPopup != nullptr) { + if (ImGui::Begin(hex::format("##{}", m_currPopup->getTitle().get()).c_str(), &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDocking)) { + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + this->closePopup(); + } else { + float titleOffset = 7_scaled; - ImGui::SetNextWindowPos(ImGui::GetWindowPos() + ImGui::GetWindowContentRegionMin() - ImGui::GetStyle().WindowPadding, ImGuiCond_Appearing); - if (ImGui::BeginPopup("##hex_editor_popup", ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize |ImGuiWindowFlags_NoTitleBar)) { - // Force close the popup when user is editing an input - if (ImGui::IsKeyPressed(ImGuiKey_Escape)){ - ImGui::CloseCurrentPopup(); + const ImVec2 originalCursorPos = ImGui::GetCursorPos(); + if (m_currPopup->canBePinned()) { + titleOffset += 16_scaled; + ImGui::SetCursorPos(ImVec2(5_scaled, 0.0F)); + bool pinned = m_currPopup->isPinned(); + if (ImGuiExt::PopupTitleBarButton(pinned ? ICON_VS_PINNED : ICON_VS_PIN, pinned)) { + m_currPopup->setPinned(!pinned); + } + } + + const auto popupTitle = m_currPopup->getTitle(); + if (!popupTitle.empty()) { + ImGui::SetCursorPos(ImVec2(titleOffset, 0.0F)); + ImGuiExt::PopupTitleBarText(Lang(popupTitle)); + } + + ImGui::SetCursorPos(originalCursorPos); + + if (ImGui::IsWindowAppearing()) { + ImGui::SetKeyboardFocusHere(); + m_currentPopupHasHovered = false; + } + + m_currentPopupHover = ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); + m_currentPopupDetached = !ImGui::GetCurrentWindow()->ViewportOwned; + m_currentPopupHasHovered |= m_currentPopupHover; + + m_currPopup->draw(this); + } + } else { + this->closePopup(); } - if (justOpened) { - ImGui::SetKeyboardFocusHere(); - justOpened = false; + if ((m_currPopup != nullptr && !m_currPopup->isPinned() && !ImGui::IsWindowFocused() && !ImGui::IsWindowHovered()) || !open) { + this->closePopup(); } - if (m_currPopup != nullptr) - m_currPopup->draw(this); - else - ImGui::CloseCurrentPopup(); - - ImGui::EndPopup(); - } else { - this->closePopup(); - justOpened = true; + ImGui::End(); } + if (alphaIsChanged) + ImGui::PopStyleVar(); + // Right click menu if (ImGui::IsMouseDown(ImGuiMouseButton_Right) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) && !ImGui::IsAnyItemHovered()) RequestOpenPopup::post("hex.builtin.menu.edit");