Kaynağa Gözat

feat: add startup overlay hint

saulo benigno 1 ay önce
ebeveyn
işleme
77dbb6bc38

+ 2 - 0
Sunrise/Sunrise.vcxproj

@@ -167,6 +167,7 @@
     <ClCompile Include="src\core\ui\hud\overlays\ui_hud_mission_script_overlay.cpp" />
     <ClCompile Include="src\core\ui\hud\overlays\ui_hud_sensor_events_overlay.cpp" />
     <ClCompile Include="src\core\ui\hud\overlays\ui_hud_session_overlay.cpp" />
+    <ClCompile Include="src\core\ui\hud\overlays\ui_hud_startup_hint_overlay.cpp" />
     <ClCompile Include="src\core\ui\hud\overlays\ui_hud_status_overlay.cpp" />
     <ClCompile Include="src\core\ui\hud\store\hud_settings_store.cpp" />
     <ClCompile Include="src\core\ui\textures\ui_texture_slots.cpp" />
@@ -1293,6 +1294,7 @@
     <ClInclude Include="src\core\ui\hud\overlays\ui_hud_mission_script_overlay.h" />
     <ClInclude Include="src\core\ui\hud\overlays\ui_hud_sensor_events_overlay.h" />
     <ClInclude Include="src\core\ui\hud\overlays\ui_hud_session_overlay.h" />
+    <ClInclude Include="src\core\ui\hud\overlays\ui_hud_startup_hint_overlay.h" />
     <ClInclude Include="src\core\ui\hud\overlays\ui_hud_status_overlay.h" />
     <ClInclude Include="src\core\ui\hud\store\hud_settings_store.h" />
     <ClInclude Include="src\core\ui\textures\ui_texture_slots.h" />

+ 10 - 0
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('{')) {

+ 3 - 0
Sunrise/src/core/settings/parser.h

@@ -27,6 +27,9 @@ public:
     /** 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;

+ 2 - 0
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. */

+ 81 - 0
Sunrise/src/core/ui/hud/overlays/ui_hud_startup_hint_overlay.cpp

@@ -0,0 +1,81 @@
+#include "ui_hud_startup_hint_overlay.h"
+
+#include <algorithm>
+#include <array>
+#include <cctype>
+#include <cstddef>
+#include <imgui.h>
+#include <string_view>
+
+#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<char, kKeyNameCapacity> key_label() noexcept {
+    std::array<char, kKeyNameCapacity> 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<char>(std::toupper(static_cast<unsigned char>(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<char, kKeyNameCapacity> 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

+ 15 - 0
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

+ 31 - 5
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<Entry, kOverlayCount> 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<Overlay>(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;