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");