Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Sunrise/Sunrise.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down Expand Up @@ -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" />
Expand Down
10 changes: 10 additions & 0 deletions Sunrise/src/core/settings/client/client_ui_settings_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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('{')) {
Expand Down
3 changes: 3 additions & 0 deletions Sunrise/src/core/settings/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions Sunrise/src/core/ui/hud/overlay.h
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
82 changes: 82 additions & 0 deletions Sunrise/src/core/ui/hud/overlays/ui_hud_startup_hint_overlay.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#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 "../../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;
/** 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;

/** 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;
/** 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.
* 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 {
const float deltaSeconds = ImGui::GetIO().DeltaTime;
if (!g_dismissed) {
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;
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. */
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 changes: 15 additions & 0 deletions Sunrise/src/core/ui/hud/overlays/ui_hud_startup_hint_overlay.h
Original file line number Diff line number Diff line change
@@ -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
37 changes: 31 additions & 6 deletions Sunrise/src/core/ui/hud/ui_hud_overlay_stack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -217,14 +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 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]) {
if (!g_enabled[index] || static_cast<Overlay>(index) == Overlay::startupHint) {
continue;
}
position.y += draw_overlay(kOverlays[index], position) + gap;
const ImVec2 size = draw_overlay(kOverlays[index], position);
if (!drawn) {
hintPosition.x += size.x + gap;
}
position.y += size.y + gap;
drawn = true;
}
constexpr auto hintIndex = static_cast<std::size_t>(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;
}

Expand Down
48 changes: 45 additions & 3 deletions Sunrise/src/core/ui/layout/ui_layout_render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,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"
Expand Down Expand Up @@ -58,6 +59,12 @@ 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";
/** 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.
Expand Down Expand Up @@ -119,8 +126,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) {
Expand All @@ -144,6 +158,31 @@ void draw_title() noexcept {
ImGui::SetCursorPosY(
titleY + ((std::max)(titleHeight - ImGui::GetTextLineHeight(), 0.0F) / kHalfExtent));
ImGui::TextDisabled(SUNRISE_VER_STRING);

// 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)};
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
Expand Down Expand Up @@ -186,7 +225,10 @@ 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 only ever hides, so a surface already closing stays closed.
if (draw_title()) {
runtime::hide();
}
ImGui::Separator();

const StateSnapshot state = snapshot();
Expand Down
7 changes: 7 additions & 0 deletions Sunrise/src/core/ui/runtime/ui_visibility_runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions Sunrise/src/core/ui/runtime/ui_visibility_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading