From 17ba90f021679fc8dc49d2c7d713c1cb5ff213f2 Mon Sep 17 00:00:00 2001 From: saulo benigno Date: Sun, 20 Sep 2026 18:42:15 -0300 Subject: [PATCH 1/5] feat: improve main overlay close behavior --- .../src/core/ui/layout/ui_layout_render.cpp | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/Sunrise/src/core/ui/layout/ui_layout_render.cpp b/Sunrise/src/core/ui/layout/ui_layout_render.cpp index 7ec406ced..82e77bdab 100644 --- a/Sunrise/src/core/ui/layout/ui_layout_render.cpp +++ b/Sunrise/src/core/ui/layout/ui_layout_render.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "../../../../resources/resource.h" @@ -9,6 +10,7 @@ #include "../components/logo/ui_logo_component.h" #include "../components/section/ui_section_component.h" #include "../modules/registry/ui_module_registry.h" +#include "../runtime/ui_visibility_runtime.h" #include "../scaling/dpi/ui_dpi_scaling.h" #include "navigation/ui_layout_navigation.h" #include "ui_layout_lifecycle.h" @@ -58,6 +60,8 @@ constexpr float kTitleTextRatio = 1.5F; constexpr float kHalfExtent = 2.0F; /** The surface names the tool with the same wordmark the HUD card carries. */ constexpr char kTitle[] = "SUNRISE"; +/** The hidden close action label is scoped by the main window. */ +constexpr char kCloseWidgetId[] = "##close"; /** * Copies one display name into null-terminated component storage. @@ -119,8 +123,15 @@ void draw_companion_windows() noexcept { } } -/** Draws the animated logo, then the name and version, on one title row. */ -void draw_title() noexcept { +/** + * Draws the animated logo, then the name and version, on one title row, with a close action + * at its far end. + * @return True when the close action was pressed. + */ +[[nodiscard]] bool draw_title() noexcept { + // The row edges are taken before it opens, so the close action can sit at its far end. + const ImVec2 rowOrigin = ImGui::GetCursorScreenPos(); + const float rowRight = rowOrigin.x + ImGui::GetContentRegionAvail().x; const float extent = scaling::dpi::pixels(kTitleLogoExtent); const bool logoDrawn = components::logo::draw(extent); if (logoDrawn) { @@ -144,6 +155,16 @@ void draw_title() noexcept { ImGui::SetCursorPosY( titleY + ((std::max)(titleHeight - ImGui::GetTextLineHeight(), 0.0F) / kHalfExtent)); ImGui::TextDisabled(SUNRISE_VER_STRING); + + // The same Dear ImGui title-bar close button the Activity Host tool windows carry, at the + // right edge of the title row and centered on it. It takes no layout space, so the row + // and the separator below keep their places. + const float buttonExtent = ImGui::GetFontSize(); + const float rowHeight = logoDrawn ? extent : titleHeight; + const ImVec2 buttonPosition{ + rowRight - buttonExtent, + rowOrigin.y + ((std::max)(rowHeight - buttonExtent, 0.0F) / kHalfExtent)}; + return ImGui::CloseButton(ImGui::GetID(kCloseWidgetId), buttonPosition); } } // namespace @@ -186,7 +207,11 @@ bool render(bool visible) noexcept { ImGui::PushStyleVar(ImGuiStyleVar_Alpha, progress); const bool submitContents = ImGui::Begin("Sunrise", nullptr, kMainWindowFlags); if (submitContents) { - draw_title(); + // The close action is one press of the configured toggle key, so both paths share the + // same visibility state. A surface already closing ignores it, or it would reopen. + if (draw_title() && visible) { + (void)runtime::toggle_for_key(runtime::snapshot().toggleVirtualKey); + } ImGui::Separator(); const StateSnapshot state = snapshot(); From 77dbb6bc389e1106132b9b7d5828baba99019f5d Mon Sep 17 00:00:00 2001 From: saulo benigno Date: Sun, 20 Sep 2026 19:00:09 -0300 Subject: [PATCH 2/5] feat: add startup overlay hint --- Sunrise/Sunrise.vcxproj | 2 + .../client/client_ui_settings_parser.cpp | 10 +++ Sunrise/src/core/settings/parser.h | 3 + Sunrise/src/core/ui/hud/overlay.h | 2 + .../overlays/ui_hud_startup_hint_overlay.cpp | 81 +++++++++++++++++++ .../overlays/ui_hud_startup_hint_overlay.h | 15 ++++ .../src/core/ui/hud/ui_hud_overlay_stack.cpp | 36 +++++++-- 7 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 Sunrise/src/core/ui/hud/overlays/ui_hud_startup_hint_overlay.cpp create mode 100644 Sunrise/src/core/ui/hud/overlays/ui_hud_startup_hint_overlay.h diff --git a/Sunrise/Sunrise.vcxproj b/Sunrise/Sunrise.vcxproj index 6cd0414ae..a180603f2 100644 --- a/Sunrise/Sunrise.vcxproj +++ b/Sunrise/Sunrise.vcxproj @@ -167,6 +167,7 @@ + @@ -1293,6 +1294,7 @@ + diff --git a/Sunrise/src/core/settings/client/client_ui_settings_parser.cpp b/Sunrise/src/core/settings/client/client_ui_settings_parser.cpp index 9892275b8..629a83b7f 100644 --- a/Sunrise/src/core/settings/client/client_ui_settings_parser.cpp +++ b/Sunrise/src/core/settings/client/client_ui_settings_parser.cpp @@ -46,6 +46,16 @@ bool Parser::ui_toggle_key_value(std::string_view name, UINT& output) noexcept { return false; } +/** Names one Windows SDK virtual key with the setting that selects it. */ +std::string_view Parser::ui_toggle_key_name(UINT virtualKey) noexcept { + for (const KeyName& entry : kToggleKeys) { + if (entry.virtualKey == virtualKey) { + return entry.name; + } + } + return {}; +} + /** Parses the in-game UI boot and input policy. */ bool Parser::client_ui_settings(ui::runtime::Settings& output) noexcept { if (!consume('{')) { diff --git a/Sunrise/src/core/settings/parser.h b/Sunrise/src/core/settings/parser.h index 37d3a57a6..7b306d386 100644 --- a/Sunrise/src/core/settings/parser.h +++ b/Sunrise/src/core/settings/parser.h @@ -27,6 +27,9 @@ class Parser { /** Reads only the root version; a missing version is zero. */ [[nodiscard]] bool parse_version(std::uint32_t& output) noexcept; + /** @return The settings name of a supported menu toggle key, or empty when it is not one. */ + [[nodiscard]] static std::string_view ui_toggle_key_name(UINT virtualKey) noexcept; + private: /** Parses the Core settings object. */ [[nodiscard]] bool core(Settings& output) noexcept; diff --git a/Sunrise/src/core/ui/hud/overlay.h b/Sunrise/src/core/ui/hud/overlay.h index 9e8382fa3..57fe2f768 100644 --- a/Sunrise/src/core/ui/hud/overlay.h +++ b/Sunrise/src/core/ui/hud/overlay.h @@ -8,6 +8,8 @@ namespace sunrise::core::ui::hud { enum class Overlay : std::uint8_t { /** The Sunrise name, version and animated logo. */ logoCard, + /** How to open the menu, shown once beside the card shortly after start. */ + startupHint, /** Where the player is: activity, bubble, slice set and closest spawn. */ currentStatus, /** The instances of the session the player is in. */ diff --git a/Sunrise/src/core/ui/hud/overlays/ui_hud_startup_hint_overlay.cpp b/Sunrise/src/core/ui/hud/overlays/ui_hud_startup_hint_overlay.cpp new file mode 100644 index 000000000..e3ba04cf6 --- /dev/null +++ b/Sunrise/src/core/ui/hud/overlays/ui_hud_startup_hint_overlay.cpp @@ -0,0 +1,81 @@ +#include "ui_hud_startup_hint_overlay.h" + +#include +#include +#include +#include +#include +#include + +#include "../../../settings/parser.h" +#include "../../animation/transition/ui_transition_animation.h" +#include "../../runtime/ui_visibility_runtime.h" + +namespace sunrise::core::ui::hud::overlays::startup_hint { +namespace { + +/** 2 seconds let the game settle before the hint asks for attention. */ +constexpr float kDelaySeconds = 2.0F; +/** 30 seconds is long enough to notice the hint and short enough to clear itself. */ +constexpr float kVisibleSeconds = 30.0F; +/** Fixed animation key. Every visibility-lane user needs its own, so keep these distinct. */ +constexpr ImGuiID kOverlayAnimationId = 4; +/** Response rates for fading in and out, slower than the menus so the hint stays subtle. */ +constexpr animation::transition::Rates kVisibilityRates{8.0F, 6.0F}; +/** A hidden hint has finished its transition and draws nothing. */ +constexpr float kHiddenProgress = 0.0F; +/** Longest supported toggle key name, plus the null. */ +constexpr std::size_t kKeyNameCapacity = 16; + +/** Seconds since the first HUD frame, counted until the hint is dismissed. */ +float g_elapsedSeconds = 0.0F; +/** Set once the surface opened or the stay ran out. The hint never returns in this run. */ +bool g_dismissed = false; + +/** + * Names the configured toggle key in upper case, so it stands out from the muted text around it. + * The key always comes from the parser's table, because the settings reject any other one. + * @return Null-terminated key name. + */ +[[nodiscard]] std::array key_label() noexcept { + std::array label{}; + const std::string_view name = + settings::parser::Parser::ui_toggle_key_name(runtime::snapshot().toggleVirtualKey); + const std::size_t length = (std::min)(name.size(), label.size() - 1); + for (std::size_t index = 0; index < length; ++index) { + label[index] = static_cast(std::toupper(static_cast(name[index]))); + } + return label; +} + +} // namespace + +/** Advances the hint's one-time lifetime by this frame. */ +float progress() noexcept { + if (!g_dismissed) { + g_elapsedSeconds += ImGui::GetIO().DeltaTime; + // An open surface has taught the key, and a hint that came back later would nag. + if (runtime::snapshot().visible || g_elapsedSeconds >= kDelaySeconds + kVisibleSeconds) { + g_dismissed = true; + } + } + const bool shown = !g_dismissed && g_elapsedSeconds >= kDelaySeconds; + return animation::transition::update(kOverlayAnimationId, + animation::transition::Lane::visibility, + shown, + kVisibilityRates, + kHiddenProgress); +} + +/** Draws the startup hint inside the overlay window the stack has already started. */ +void draw() noexcept { + const std::array key = key_label(); + // The sentence is muted like the card's version line; the key is white like its wordmark. + ImGui::TextDisabled("Press"); + ImGui::SameLine(); + ImGui::TextUnformatted(key.data()); + ImGui::SameLine(); + ImGui::TextDisabled("to open the Sunrise overlay"); +} + +} // namespace sunrise::core::ui::hud::overlays::startup_hint diff --git a/Sunrise/src/core/ui/hud/overlays/ui_hud_startup_hint_overlay.h b/Sunrise/src/core/ui/hud/overlays/ui_hud_startup_hint_overlay.h new file mode 100644 index 000000000..dbf2e4a5c --- /dev/null +++ b/Sunrise/src/core/ui/hud/overlays/ui_hud_startup_hint_overlay.h @@ -0,0 +1,15 @@ +#pragma once + +namespace sunrise::core::ui::hud::overlays::startup_hint { + +/** + * Advances the hint's one-time lifetime by this frame: a short wait after the first HUD frame, a + * bounded stay, and an early end once the main surface opens. It never comes back in the same run. + * @return Current fade, from 0 (nothing to draw) to 1 (fully shown). + */ +[[nodiscard]] float progress() noexcept; + +/** Draws the startup hint inside the overlay window the stack has already started. */ +void draw() noexcept; + +} // namespace sunrise::core::ui::hud::overlays::startup_hint diff --git a/Sunrise/src/core/ui/hud/ui_hud_overlay_stack.cpp b/Sunrise/src/core/ui/hud/ui_hud_overlay_stack.cpp index f5fbae483..8d153fccc 100644 --- a/Sunrise/src/core/ui/hud/ui_hud_overlay_stack.cpp +++ b/Sunrise/src/core/ui/hud/ui_hud_overlay_stack.cpp @@ -13,6 +13,7 @@ #include "overlays/ui_hud_mission_script_overlay.h" #include "overlays/ui_hud_sensor_events_overlay.h" #include "overlays/ui_hud_session_overlay.h" +#include "overlays/ui_hud_startup_hint_overlay.h" #include "overlays/ui_hud_status_overlay.h" #include "store/hud_settings_store.h" @@ -48,6 +49,11 @@ constexpr std::size_t kSwitchCount = kOverlayCount + kStatusLineCount; /** Every overlay, in Overlay order. The menu lists them and the corner stacks them in it. */ constexpr std::array kOverlays{ Entry{"Sunrise Card", "sunrise_card", "##sunrise_hud_card", &overlays::logo::draw, true}, + Entry{"Startup Hint", + "startup_hint", + "##sunrise_hud_startup_hint", + &overlays::startup_hint::draw, + true}, // Diagnostic overlays start off because an ordinary run does not need them on screen. Entry{ "Current Status", "current_status", "##sunrise_hud_status", &overlays::status::draw, false}, @@ -136,18 +142,18 @@ void save_switches() noexcept { * Draws one overlay at a fixed position. * @param entry Overlay to draw. * @param position Top-left corner, in final framebuffer pixels. - * @return Height the window took, which is known only after its content is submitted. + * @return Size the window took, which is known only after its content is submitted. */ -[[nodiscard]] float draw_overlay(const Entry& entry, const ImVec2& position) noexcept { +[[nodiscard]] ImVec2 draw_overlay(const Entry& entry, const ImVec2& position) noexcept { ImGui::SetNextWindowPos(position, ImGuiCond_Always); const bool submitContents = ImGui::Begin(entry.windowId, nullptr, kOverlayFlags); if (submitContents) { entry.draw(); } // Read inside the window, because the size belongs to it and not to the caller's window. - const float height = ImGui::GetWindowSize().y; + const ImVec2 size = ImGui::GetWindowSize(); ImGui::End(); - return height; + return size; } } // namespace @@ -217,12 +223,32 @@ bool draw(bool interfaceEnabled) noexcept { const float margin = scaling::dpi::pixels(kViewportMargin); const float gap = scaling::dpi::pixels(kOverlayGap); ImVec2 position{viewport->WorkPos.x + margin, viewport->WorkPos.y + margin}; + // The startup hint sits beside the Sunrise card on its row, or on the corner while the card + // is off, so it never pushes the overlays stacked below down. + ImVec2 hintPosition = position; bool drawn = false; for (std::size_t index = 0; index < kOverlayCount; ++index) { if (!g_enabled[index]) { continue; } - position.y += draw_overlay(kOverlays[index], position) + gap; + const auto overlay = static_cast(index); + if (overlay == Overlay::startupHint) { + const float progress = overlays::startup_hint::progress(); + if (progress <= 0.0F) { + continue; + } + // One style alpha fades the hint and everything drawn inside it together. + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, progress); + (void)draw_overlay(kOverlays[index], hintPosition); + ImGui::PopStyleVar(); + drawn = true; + continue; + } + const ImVec2 size = draw_overlay(kOverlays[index], position); + if (overlay == Overlay::logoCard) { + hintPosition.x += size.x + gap; + } + position.y += size.y + gap; drawn = true; } return drawn; From 50a902081863eddbaa8dd111505b74f915332ab6 Mon Sep 17 00:00:00 2001 From: saulo benigno Date: Sun, 20 Sep 2026 19:00:54 -0300 Subject: [PATCH 3/5] docs: document how to open the overlay --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 268955ab8..b7d141564 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,14 @@ Destiny 2 Offline Preservation Mod - [Documentation](https://projectsunrise.dev/docs/) - [Discord](https://discord.gg/22JS6et5k9) +## Opening the overlay + +After launching Destiny 2, press Insert to open or close the Sunrise overlay + +Insert is the default key and can be changed in `bin\x64\Sunrise\settings.json` + +See the [FAQ](https://projectsunrise.dev/faq/) for more information + ## Features - Load into any Destination (matchmade activities are currently broken) - Script Missions From a2a1ba4c7447f0980f1a5db0eb663988b28448c0 Mon Sep 17 00:00:00 2001 From: saulo benigno Date: Sun, 20 Sep 2026 19:44:02 -0300 Subject: [PATCH 4/5] fix: avoid startup hint overlap --- .../src/core/ui/hud/ui_hud_overlay_stack.cpp | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/Sunrise/src/core/ui/hud/ui_hud_overlay_stack.cpp b/Sunrise/src/core/ui/hud/ui_hud_overlay_stack.cpp index 8d153fccc..a6eb2c31d 100644 --- a/Sunrise/src/core/ui/hud/ui_hud_overlay_stack.cpp +++ b/Sunrise/src/core/ui/hud/ui_hud_overlay_stack.cpp @@ -223,34 +223,33 @@ bool draw(bool interfaceEnabled) noexcept { const float margin = scaling::dpi::pixels(kViewportMargin); const float gap = scaling::dpi::pixels(kOverlayGap); ImVec2 position{viewport->WorkPos.x + margin, viewport->WorkPos.y + margin}; - // The startup hint sits beside the Sunrise card on its row, or on the corner while the card - // is off, so it never pushes the overlays stacked below down. + // The startup hint sits beside whichever overlay owns the top row, or on the corner when + // none does. It is drawn after the stack, once that row's width is known, and never moves + // the overlays stacked below. ImVec2 hintPosition = position; bool drawn = false; for (std::size_t index = 0; index < kOverlayCount; ++index) { - if (!g_enabled[index]) { - continue; - } - const auto overlay = static_cast(index); - if (overlay == Overlay::startupHint) { - const float progress = overlays::startup_hint::progress(); - if (progress <= 0.0F) { - continue; - } - // One style alpha fades the hint and everything drawn inside it together. - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, progress); - (void)draw_overlay(kOverlays[index], hintPosition); - ImGui::PopStyleVar(); - drawn = true; + if (!g_enabled[index] || static_cast(index) == Overlay::startupHint) { continue; } const ImVec2 size = draw_overlay(kOverlays[index], position); - if (overlay == Overlay::logoCard) { + if (!drawn) { hintPosition.x += size.x + gap; } position.y += size.y + gap; drawn = true; } + constexpr auto hintIndex = static_cast(Overlay::startupHint); + if (g_enabled[hintIndex]) { + const float progress = overlays::startup_hint::progress(); + if (progress > 0.0F) { + // One style alpha fades the hint and everything drawn inside it together. + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, progress); + (void)draw_overlay(kOverlays[hintIndex], hintPosition); + ImGui::PopStyleVar(); + drawn = true; + } + } return drawn; } From e80f8cdd81759c3ad8daa736a1e81a0940502e65 Mon Sep 17 00:00:00 2001 From: saulo benigno Date: Sun, 27 Sep 2026 15:01:28 -0300 Subject: [PATCH 5/5] fix: address main overlay UX review --- .../overlays/ui_hud_startup_hint_overlay.cpp | 27 +++++++------- .../src/core/ui/layout/ui_layout_render.cpp | 35 ++++++++++++++----- .../core/ui/runtime/ui_visibility_runtime.cpp | 7 ++++ .../core/ui/runtime/ui_visibility_runtime.h | 3 ++ 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/Sunrise/src/core/ui/hud/overlays/ui_hud_startup_hint_overlay.cpp b/Sunrise/src/core/ui/hud/overlays/ui_hud_startup_hint_overlay.cpp index e3ba04cf6..e685916fc 100644 --- a/Sunrise/src/core/ui/hud/overlays/ui_hud_startup_hint_overlay.cpp +++ b/Sunrise/src/core/ui/hud/overlays/ui_hud_startup_hint_overlay.cpp @@ -8,7 +8,6 @@ #include #include "../../../settings/parser.h" -#include "../../animation/transition/ui_transition_animation.h" #include "../../runtime/ui_visibility_runtime.h" namespace sunrise::core::ui::hud::overlays::startup_hint { @@ -18,12 +17,12 @@ namespace { constexpr float kDelaySeconds = 2.0F; /** 30 seconds is long enough to notice the hint and short enough to clear itself. */ constexpr float kVisibleSeconds = 30.0F; -/** Fixed animation key. Every visibility-lane user needs its own, so keep these distinct. */ -constexpr ImGuiID kOverlayAnimationId = 4; -/** Response rates for fading in and out, slower than the menus so the hint stays subtle. */ -constexpr animation::transition::Rates kVisibilityRates{8.0F, 6.0F}; -/** A hidden hint has finished its transition and draws nothing. */ -constexpr float kHiddenProgress = 0.0F; +/** Half a second to fade in or out, slower than the menus so the hint stays subtle. */ +constexpr float kFadeSeconds = 0.5F; +/** A hidden hint has finished fading out and draws nothing. */ +constexpr float kHiddenOpacity = 0.0F; +/** A shown hint has finished fading in. */ +constexpr float kShownOpacity = 1.0F; /** Longest supported toggle key name, plus the null. */ constexpr std::size_t kKeyNameCapacity = 16; @@ -31,6 +30,8 @@ constexpr std::size_t kKeyNameCapacity = 16; float g_elapsedSeconds = 0.0F; /** Set once the surface opened or the stay ran out. The hint never returns in this run. */ bool g_dismissed = false; +/** The hint's own fade, so it needs no shared animation key. */ +float g_opacity = kHiddenOpacity; /** * Names the configured toggle key in upper case, so it stands out from the muted text around it. @@ -52,19 +53,19 @@ bool g_dismissed = false; /** Advances the hint's one-time lifetime by this frame. */ float progress() noexcept { + const float deltaSeconds = ImGui::GetIO().DeltaTime; if (!g_dismissed) { - g_elapsedSeconds += ImGui::GetIO().DeltaTime; + g_elapsedSeconds += deltaSeconds; // An open surface has taught the key, and a hint that came back later would nag. if (runtime::snapshot().visible || g_elapsedSeconds >= kDelaySeconds + kVisibleSeconds) { g_dismissed = true; } } const bool shown = !g_dismissed && g_elapsedSeconds >= kDelaySeconds; - return animation::transition::update(kOverlayAnimationId, - animation::transition::Lane::visibility, - shown, - kVisibilityRates, - kHiddenProgress); + const float step = deltaSeconds / kFadeSeconds; + g_opacity = shown ? (std::min)(g_opacity + step, kShownOpacity) + : (std::max)(g_opacity - step, kHiddenOpacity); + return g_opacity; } /** Draws the startup hint inside the overlay window the stack has already started. */ diff --git a/Sunrise/src/core/ui/layout/ui_layout_render.cpp b/Sunrise/src/core/ui/layout/ui_layout_render.cpp index 82e77bdab..4096e6c61 100644 --- a/Sunrise/src/core/ui/layout/ui_layout_render.cpp +++ b/Sunrise/src/core/ui/layout/ui_layout_render.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include "../../../../resources/resource.h" @@ -62,6 +61,10 @@ constexpr float kHalfExtent = 2.0F; constexpr char kTitle[] = "SUNRISE"; /** The hidden close action label is scoped by the main window. */ constexpr char kCloseWidgetId[] = "##close"; +/** The close cross spans this fraction of its button, so it reads as a glyph and not a frame. */ +constexpr float kCloseCrossRatio = 0.5F; +/** One pixel keeps the close cross as thin as the body text. */ +constexpr float kCloseCrossThickness = 1.0F; /** * Copies one display name into null-terminated component storage. @@ -156,15 +159,30 @@ void draw_companion_windows() noexcept { titleY + ((std::max)(titleHeight - ImGui::GetTextLineHeight(), 0.0F) / kHalfExtent)); ImGui::TextDisabled(SUNRISE_VER_STRING); - // The same Dear ImGui title-bar close button the Activity Host tool windows carry, at the - // right edge of the title row and centered on it. It takes no layout space, so the row - // and the separator below keep their places. + // A cross at the right edge of the title row, centered on it. It stays on the row the logo + // opened, like the version, so the row and the separator below keep their places. + ImGui::SameLine(); const float buttonExtent = ImGui::GetFontSize(); const float rowHeight = logoDrawn ? extent : titleHeight; const ImVec2 buttonPosition{ rowRight - buttonExtent, rowOrigin.y + ((std::max)(rowHeight - buttonExtent, 0.0F) / kHalfExtent)}; - return ImGui::CloseButton(ImGui::GetID(kCloseWidgetId), buttonPosition); + ImGui::SetCursorScreenPos(buttonPosition); + const bool pressed = ImGui::InvisibleButton(kCloseWidgetId, {buttonExtent, buttonExtent}); + + // Muted like the version until hovered, then white like the title. + const ImU32 color = + ImGui::GetColorU32(ImGui::IsItemHovered() ? ImGuiCol_Text : ImGuiCol_TextDisabled); + const float half = buttonExtent * kCloseCrossRatio / kHalfExtent; + const ImVec2 center{buttonPosition.x + (buttonExtent / kHalfExtent), + buttonPosition.y + (buttonExtent / kHalfExtent)}; + ImDrawList* drawList = ImGui::GetWindowDrawList(); + const float thickness = scaling::dpi::pixels(kCloseCrossThickness); + drawList->AddLine( + {center.x - half, center.y - half}, {center.x + half, center.y + half}, color, thickness); + drawList->AddLine( + {center.x + half, center.y - half}, {center.x - half, center.y + half}, color, thickness); + return pressed; } } // namespace @@ -207,10 +225,9 @@ bool render(bool visible) noexcept { ImGui::PushStyleVar(ImGuiStyleVar_Alpha, progress); const bool submitContents = ImGui::Begin("Sunrise", nullptr, kMainWindowFlags); if (submitContents) { - // The close action is one press of the configured toggle key, so both paths share the - // same visibility state. A surface already closing ignores it, or it would reopen. - if (draw_title() && visible) { - (void)runtime::toggle_for_key(runtime::snapshot().toggleVirtualKey); + // The close action only ever hides, so a surface already closing stays closed. + if (draw_title()) { + runtime::hide(); } ImGui::Separator(); diff --git a/Sunrise/src/core/ui/runtime/ui_visibility_runtime.cpp b/Sunrise/src/core/ui/runtime/ui_visibility_runtime.cpp index d7129db47..bb52796f1 100644 --- a/Sunrise/src/core/ui/runtime/ui_visibility_runtime.cpp +++ b/Sunrise/src/core/ui/runtime/ui_visibility_runtime.cpp @@ -63,4 +63,11 @@ bool toggle_for_key(UINT virtualKey) noexcept { return handled; } +/** Closes the menu. A menu that is already closed or closing stays that way. */ +void hide() noexcept { + AcquireSRWLockExclusive(&g_visibilityLock); + g_state.visible = false; + ReleaseSRWLockExclusive(&g_visibilityLock); +} + } // namespace sunrise::core::ui::runtime diff --git a/Sunrise/src/core/ui/runtime/ui_visibility_runtime.h b/Sunrise/src/core/ui/runtime/ui_visibility_runtime.h index 49d371b6b..8f1cd1c8b 100644 --- a/Sunrise/src/core/ui/runtime/ui_visibility_runtime.h +++ b/Sunrise/src/core/ui/runtime/ui_visibility_runtime.h @@ -35,4 +35,7 @@ void shutdown() noexcept; */ [[nodiscard]] bool toggle_for_key(UINT virtualKey) noexcept; +/** Closes the menu. A menu that is already closed or closing stays that way. */ +void hide() noexcept; + } // namespace sunrise::core::ui::runtime