RmlUi improvements (#663)

* rmlui audio

* fix menu select sound

* Fixes #662

* fix reset logic and fix popup getting stuck closed

* fix X button on menu popup

* rmlui achievements, and fix open/close bug

* presets, achievements css, and menu sounds toggle

* forgor

* fix b button causing audio when menu not visible
This commit is contained in:
qwertyquerty
2026-05-04 09:25:13 -07:00
committed by GitHub
parent 62a88f1e9a
commit e49be12297
31 changed files with 826 additions and 470 deletions
+4 -4
View File
@@ -1449,8 +1449,6 @@ set(DUSK_FILES
src/dusk/imgui/ImGuiMenuTools.hpp
src/dusk/imgui/ImGuiPreLaunchWindow.cpp
src/dusk/imgui/ImGuiPreLaunchWindow.hpp
src/dusk/imgui/ImGuiFirstRunPreset.hpp
src/dusk/imgui/ImGuiFirstRunPreset.cpp
src/dusk/imgui/ImGuiProcessOverlay.cpp
src/dusk/imgui/ImGuiCameraOverlay.cpp
src/dusk/imgui/ImGuiHeapOverlay.cpp
@@ -1461,8 +1459,6 @@ set(DUSK_FILES
src/dusk/imgui/ImGuiSaveEditor.cpp
src/dusk/imgui/ImGuiStateShare.hpp
src/dusk/imgui/ImGuiStateShare.cpp
src/dusk/imgui/ImGuiAchievements.hpp
src/dusk/imgui/ImGuiAchievements.cpp
src/dusk/ui/bool_button.cpp
src/dusk/ui/bool_button.hpp
src/dusk/ui/button.cpp
@@ -1473,6 +1469,10 @@ set(DUSK_FILES
src/dusk/ui/controller_config.hpp
src/dusk/ui/document.cpp
src/dusk/ui/document.hpp
src/dusk/ui/achievements.cpp
src/dusk/ui/achievements.hpp
src/dusk/ui/preset.cpp
src/dusk/ui/preset.hpp
src/dusk/ui/editor.cpp
src/dusk/ui/editor.hpp
src/dusk/ui/event.cpp
+1
View File
@@ -56,6 +56,7 @@ struct UserSettings {
ConfigVar<int> fanfareVolume;
ConfigVar<bool> enableReverb;
ConfigVar<bool> enableHrtf;
ConfigVar<bool> menuSounds;
} audio;
// Game settings
+13
View File
@@ -5,6 +5,7 @@
#include "Z2AudioLib/Z2EnvSeMgr.h"
#include "Z2AudioLib/Z2LinkMgr.h"
#include "dusk/audio.h"
#include "dusk/settings.h"
class mDoAud_zelAudio_c : public Z2AudioMgr {
public:
@@ -132,6 +133,18 @@ inline void mDoAud_seStart(u32 i_sfxID, const Vec* i_sePos, u32 param_2, s8 i_re
-1.0f, -1.0f, 0);
}
#if TARGET_PC
inline void mDoAud_seStartMenu(u32 i_sfxID) {
if (!mDoAud_zelAudio_c::isInitFlag()) {
return;
}
if (!dusk::getSettings().audio.menuSounds.getValue()) {
return;
}
mDoAud_seStart(i_sfxID, nullptr, 0, 0);
}
#endif
inline void mDoAud_seStartLevel(u32 i_sfxID, const Vec* i_sePos, u32 param_2, s8 i_reverb) {
DUSK_AUDIO_SKIP()
Z2AudioMgr::getInterface()->seStartLevel(i_sfxID, i_sePos, param_2, i_reverb, 1.0f, 1.0f,
+133
View File
@@ -256,3 +256,136 @@ icon.warning {
decorator: text("&#xe002;" center center);
color: #ffcc00;
}
.achievement-row {
display: flex;
align-items: flex-start;
gap: 10dp;
padding: 12dp 0;
border-bottom: 1dp rgba(146, 135, 91, 30%);
}
.achievement-info {
display: block;
flex: 1 1 0;
min-width: 0;
}
.achievement-header {
display: flex;
align-items: center;
}
.achievement-name {
flex: 1;
font-weight: bold;
}
.achievement-name.unlocked {
color: #ffa826;
}
.achievement-badge {
font-size: 14dp;
opacity: 0.7;
}
.achievement-badge.unlocked {
color: #44cc55;
opacity: 1;
}
.achievement-badge.locked {
color: #cc4444;
opacity: 1;
}
.achievement-desc {
display: block;
color: rgba(224, 219, 200, 55%);
font-size: 16dp;
margin: 4dp 0 0 0;
}
.achievement-progress {
display: block;
font-size: 13dp;
color: rgba(224, 219, 200, 45%);
}
progressbar {
display: block;
width: 100%;
height: 6dp;
border-radius: 3dp;
background-color: rgba(255, 255, 255, 10%);
margin: 6dp 0 2dp 0;
}
progressbar.progress-done fill {
background-color: #44aa22;
border-radius: 3dp;
}
progressbar.progress-ongoing fill {
background-color: #2255bb;
border-radius: 3dp;
}
button.achievement-clear {
flex: 0 0 auto;
align-self: center;
font-size: 14dp;
padding: 2dp 8dp;
opacity: 0.45;
}
.preset-dialog {
display: flex;
flex-flow: column;
padding: 32dp;
gap: 20dp;
flex: 1 1 0;
}
.preset-title {
display: block;
font-family: "Fira Sans Condensed";
font-weight: bold;
font-size: 30dp;
text-align: center;
}
.preset-intro {
display: block;
font-size: 16dp;
text-align: center;
color: rgba(224, 219, 200, 65%);
}
.preset-grid {
display: flex;
flex-direction: row;
gap: 20dp;
flex: 1 1 0;
align-items: flex-start;
}
.preset-col {
display: flex;
flex-flow: column;
gap: 12dp;
flex: 1 1 0;
}
button.preset-btn {
font-size: 22dp;
padding: 20dp 16dp;
}
.preset-desc {
display: block;
font-size: 15dp;
color: rgba(224, 219, 200, 55%);
text-align: center;
}
-245
View File
@@ -1,245 +0,0 @@
#include "ImGuiAchievements.hpp"
#include "ImGuiConfig.hpp"
#include "dusk/achievements.h"
#include "dusk/settings.h"
#include "fmt/format.h"
#include "imgui.h"
namespace dusk {
void ImGuiAchievements::notify(std::string name) {
if (m_notifyTimer <= 0.f) {
m_notifyName = std::move(name);
m_notifyTimer = NOTIFY_DURATION;
} else {
m_notifyQueue.push(std::move(name));
}
}
void ImGuiAchievements::showNotification() {
if (!getSettings().game.enableAchievementNotifications.getValue()) {
return;
}
if (m_notifyTimer <= 0.f) {
if (m_notifyQueue.empty()) {
return;
}
m_notifyName = std::move(m_notifyQueue.front());
m_notifyQueue.pop();
m_notifyTimer = NOTIFY_DURATION;
}
m_notifyTimer -= ImGui::GetIO().DeltaTime;
const float alpha = std::min({
m_notifyTimer / NOTIFY_FADE_TIME,
(NOTIFY_DURATION - m_notifyTimer) / NOTIFY_FADE_TIME,
1.0f
});
const ImGuiViewport* viewport = ImGui::GetMainViewport();
const float padding = 12.0f;
ImGui::SetNextWindowPos(
ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - padding, viewport->WorkPos.y + padding),
ImGuiCond_Always, ImVec2(1.0f, 0.0f)
);
ImGui::SetNextWindowBgAlpha(alpha * 0.92f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.06f, 0.01f, alpha * 0.92f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.1f, alpha));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, alpha));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.0f, 10.0f));
constexpr ImGuiWindowFlags flags =
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs;
if (ImGui::Begin("##achievement_notify", nullptr, flags)) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.82f, 0.1f, alpha));
ImGui::TextUnformatted("Achievement Unlocked!");
ImGui::PopStyleColor();
ImGui::Spacing();
ImGui::TextUnformatted(m_notifyName.c_str());
}
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(3);
}
void ImGuiAchievements::draw(bool& open) {
showNotification();
if (!open) {
return;
}
ImGui::SetNextWindowSizeConstraints(ImVec2(800, 200), ImVec2(1280, 900));
ImGui::SetNextWindowSize(ImVec2(800, 480), ImGuiCond_FirstUseEver);
if (!ImGui::Begin(
"Achievements", &open,
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav)
)
{
ImGui::End();
return;
}
const auto achievements = AchievementSystem::get().getAchievements();
int unlocked = 0;
for (const auto& a : achievements) {
if (a.unlocked) {
++unlocked;
}
}
ImGui::Text("%d / %d achievements unlocked", unlocked, (int)achievements.size());
ImGui::SameLine();
config::ImGuiCheckbox("Notifications", getSettings().game.enableAchievementNotifications);
ImGui::Separator();
static const struct {
AchievementCategory cat;
const char* label;
ImVec4 color;
} ACHIEVEMENT_CATEGORIES[] = {
{AchievementCategory::Story, "Story", ImVec4(1.0f, 0.82f, 0.1f, 1.0f)},
{AchievementCategory::Collection, "Collection", ImVec4(0.3f, 0.85f, 0.4f, 1.0f)},
{AchievementCategory::Challenge, "Challenge", ImVec4(1.0f, 0.65f, 0.15f, 1.0f)},
{AchievementCategory::Minigame, "Minigame", ImVec4(0.5f, 0.85f, 1.0f, 1.0f)},
{AchievementCategory::Misc, "Misc", ImVec4(0.65f, 0.65f, 0.65f, 1.0f)},
{AchievementCategory::Glitched, "Glitched", ImVec4(0.75f, 0.4f, 1.0f, 1.0f)},
};
const float footerHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
if (ImGui::BeginTabBar("##achievement_tabs", ImGuiTabBarFlags_FittingPolicyScroll)) {
for (const auto& catInfo : ACHIEVEMENT_CATEGORIES) {
int catTotal = 0, catUnlocked = 0;
for (const auto& a : achievements) {
if (a.category == catInfo.cat) {
++catTotal;
if (a.unlocked) {
++catUnlocked;
}
}
}
if (catTotal == 0) {
continue;
}
const std::string tabLabel = fmt::format("{} ({}/{})###{}", catInfo.label, catUnlocked, catTotal, catInfo.label);
ImGui::PushStyleColor(ImGuiCol_Text, catInfo.color);
const bool tabOpen = ImGui::BeginTabItem(tabLabel.c_str());
ImGui::PopStyleColor();
if (tabOpen) {
ImGui::BeginChild(
"##cat_list",
ImVec2(0, -footerHeight),
ImGuiChildFlags_None,
ImGuiWindowFlags_NoBackground
);
ImGui::Spacing();
for (const auto& a : achievements) {
if (a.category != catInfo.cat) {
continue;
}
ImGui::PushID(a.key);
ImGui::BeginGroup();
ImGui::PushStyleColor(
ImGuiCol_Text,
a.unlocked ?
ImVec4(1.0f, 0.65f, 0.15f, 1.0f) :
ImGui::GetStyleColorVec4(ImGuiCol_Text)
);
ImGui::TextUnformatted(a.name);
ImGui::PopStyleColor();
const char* statusLabel = a.unlocked ? "[Unlocked]" : "[Locked]";
ImGui::SameLine(
ImGui::GetContentRegionMax().x -
ImGui::CalcTextSize(statusLabel).x
);
if (a.unlocked) {
ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s", statusLabel);
} else {
ImGui::TextColored(ImVec4(0.8f, 0.2f, 0.2f, 1.0f), "%s", statusLabel);
}
ImGui::TextDisabled("%s", a.description);
if (a.isCounter) {
const float fraction = a.goal > 0 ? (float)(a.progress) / (float)(a.goal) : 1.0f;
const std::string overlay = fmt::format("{} / {}", a.progress, a.goal);
ImGui::PushStyleColor(
ImGuiCol_PlotHistogram,
a.unlocked ?
ImVec4(0.4f, 0.7f, 0.1f, 1.0f) :
ImVec4(0.2f, 0.45f, 0.8f, 1.0f)
);
ImGui::ProgressBar(fraction, ImVec2(-1.0f, 0.0f), overlay.c_str());
ImGui::PopStyleColor();
}
ImGui::EndGroup();
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
ImGui::OpenPopup("##ctx");
}
if (ImGui::BeginPopup("##ctx")) {
ImGui::TextDisabled("%s", a.name);
ImGui::Separator();
if (ImGui::MenuItem("Clear Achievement")) {
AchievementSystem::get().clearOne(a.key);
}
ImGui::EndPopup();
}
ImGui::Spacing();
ImGui::PopID();
}
ImGui::EndChild();
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
}
ImGui::Separator();
ImGui::Spacing();
if (ImGui::Button("Clear All Achievements")) {
ImGui::OpenPopup("##confirm_clear");
}
if (ImGui::BeginPopup("##confirm_clear")) {
ImGui::Text("Reset all achievement progress?");
ImGui::Spacing();
if (ImGui::Button("Yes, reset all")) {
AchievementSystem::get().clearAll();
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::End();
}
} // namespace dusk
-23
View File
@@ -1,23 +0,0 @@
#pragma once
#include <queue>
#include <string>
namespace dusk {
class ImGuiAchievements {
public:
void draw(bool& open);
void notify(std::string name);
private:
void showNotification();
std::string m_notifyName;
float m_notifyTimer = 0.f;
std::queue<std::string> m_notifyQueue;
static constexpr float NOTIFY_DURATION = 4.0f;
static constexpr float NOTIFY_FADE_TIME = 0.5f;
};
} // namespace dusk
+11 -3
View File
@@ -10,6 +10,8 @@
#include "fmt/format.h"
#include "ImGuiConsole.hpp"
#include "dusk/ui/preset.hpp"
#include "dusk/ui/ui.hpp"
#include "JSystem/JUtility/JUTGamePad.h"
#include "SDL3/SDL_mouse.h"
#include "dusk/achievements.h"
@@ -20,6 +22,8 @@
#include "dusk/livesplit.h"
#include "dusk/main.h"
#include "dusk/settings.h"
#include "f_pc/f_pc_manager.h"
#include "f_pc/f_pc_name.h"
#include "m_Do/m_Do_controller_pad.h"
#include "m_Do/m_Do_main.h"
#include "tracy/Tracy.hpp"
@@ -259,7 +263,8 @@ namespace dusk {
}
}
if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) &&
if (!fpcM_SearchByName(fpcNm_LOGO_SCENE_e) &&
(ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) &&
ImGui::IsKeyPressed(ImGuiKey_R))
{
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
@@ -300,7 +305,10 @@ namespace dusk {
ImGui::PopStyleColor();
if (!getSettings().backend.wasPresetChosen) {
m_firstRunPreset.draw();
if (!m_presetShown) {
m_presetShown = true;
dusk::ui::push_document(std::make_unique<dusk::ui::PresetWindow>());
}
return;
}
@@ -342,7 +350,7 @@ namespace dusk {
m_menuTools.ShowSaveEditor();
m_menuTools.ShowStateShare();
}
m_menuTools.ShowAchievements();
m_menuTools.showAchievementNotification();
DuskDebugPad(); // temporary, remove later
// Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds.
+1 -2
View File
@@ -7,7 +7,6 @@
#include <aurora/aurora.h>
#include "ImGuiFirstRunPreset.hpp"
#include "ImGuiMenuGame.hpp"
#include "ImGuiMenuTools.hpp"
#include "ImGuiPreLaunchWindow.hpp"
@@ -45,7 +44,7 @@ private:
ImVec2 m_dragScrollLastMousePos = {};
std::deque<Toast> m_toasts;
ImGuiFirstRunPreset m_firstRunPreset;
bool m_presetShown = false;
ImGuiMenuGame m_menuGame;
ImGuiPreLaunchWindow m_preLaunchWindow;
-141
View File
@@ -1,141 +0,0 @@
#include "ImGuiFirstRunPreset.hpp"
#include "imgui.h"
#include "ImGuiConsole.hpp"
#include "ImGuiEngine.hpp"
#include "dusk/settings.h"
#include "dusk/config.hpp"
#include <dusk/dusk.h>
namespace dusk {
static void ApplyPresetClassic() {
auto& s = getSettings();
s.video.lockAspectRatio.setValue(true);
s.game.bloomMode.setValue(BloomMode::Classic);
AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT);
}
static void ApplyPresetHD() {
auto& s = getSettings();
s.game.bloomMode.setValue(BloomMode::Classic);
s.game.hideTvSettingsScreen.setValue(true);
s.game.skipWarningScreen.setValue(true);
s.game.noReturnRupees.setValue(true);
s.game.disableRupeeCutscenes.setValue(true);
s.game.noSwordRecoil.setValue(true);
s.game.fastClimbing.setValue(true);
s.game.noMissClimbing.setValue(true);
s.game.fastTears.setValue(true);
s.game.biggerWallets.setValue(true);
s.game.invertCameraXAxis.setValue(true);
s.game.freeCamera.setValue(true);
s.game.no2ndFishForCat.setValue(true);
}
static void ApplyPresetDusk() {
ApplyPresetHD();
auto& s = getSettings();
s.game.enableAchievementNotifications.setValue(true);
s.game.enableQuickTransform.setValue(true);
s.game.instantSaves.setValue(true);
s.game.midnasLamentNonStop.setValue(true);
s.game.enableFrameInterpolation.setValue(true);
s.game.sunsSong.setValue(true);
s.game.bloomMode.setValue(BloomMode::Dusk);
s.game.autoSave.setValue(true);
}
// =========================================================================
void ImGuiFirstRunPreset::draw() {
const char* modalTitle = "Welcome to Dusk!";
if (m_done) return;
if (!m_opened) {
ImGui::OpenPopup(modalTitle);
m_opened = true;
}
const ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f));
ImGui::SetNextWindowSize(ImVec2(800.0f * ImGuiScale(), 0.0f), ImGuiCond_Always);
if (!ImGui::BeginPopupModal(modalTitle, nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) {
// force the user to actually pick one, and not just hit escape to skip the dialog
m_opened = false;
return;
}
ImGui::TextWrapped("Choose a preset to get started. You can change any setting later from the Enhancements menu.");
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
int chosen = -1;
if (ImGui::BeginTable("##presets", 5, ImGuiTableFlags_None)) {
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 16.0f * ImGuiScale());
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 16.0f * ImGuiScale());
ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch);
ImGui::TableNextRow();
ImGui::PushFont(ImGuiEngine::fontLarge);
ImGui::TableSetColumnIndex(0);
if (ImGui::Button("Classic##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) {
chosen = 0;
}
ImGui::TableSetColumnIndex(2);
if (ImGui::Button("HD##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) {
chosen = 1;
}
ImGui::TableSetColumnIndex(4);
if (ImGui::Button("Dusk##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale())))
{
chosen = 2;
}
ImGui::PopFont();
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Spacing();
ImGui::TextWrapped("All enhancements disabled to match the GameCube version. Good for speedrunning or simple nostalgia!");
ImGui::TableSetColumnIndex(2);
ImGui::Spacing();
ImGui::TextWrapped("Some enhancements enabled to match the HD version. A good starting point for most players!");
ImGui::TableSetColumnIndex(4);
ImGui::Spacing();
ImGui::TextWrapped("More enhancements enabled than the HD preset. Veteran players will appreciate the additional tweaks!");
ImGui::EndTable();
}
if (chosen >= 0) {
if (chosen == 0) ApplyPresetClassic();
if (chosen == 1) ApplyPresetHD();
if (chosen == 2) ApplyPresetDusk();
getSettings().backend.wasPresetChosen.setValue(true);
config::Save();
m_done = true;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
} // namespace dusk
-14
View File
@@ -1,14 +0,0 @@
#pragma once
namespace dusk {
class ImGuiFirstRunPreset {
public:
void draw();
private:
bool m_opened = false;
bool m_done = false;
};
} // namespace dusk
+58 -5
View File
@@ -66,7 +66,6 @@ namespace dusk {
ImGui::EndDisabled();
}
ImGui::MenuItem("Achievements", nullptr, &m_showAchievements);
#if DUSK_CAN_OPEN_DATA_FOLDER
ImGui::Separator();
@@ -269,11 +268,65 @@ namespace dusk {
ImGui::PopFont();
}
void ImGuiMenuTools::ShowAchievements() {
m_achievementsWindow.draw(m_showAchievements);
void ImGuiMenuTools::notifyAchievement(std::string name) {
if (m_notifyTimer <= 0.f) {
m_notifyName = std::move(name);
m_notifyTimer = NOTIFY_DURATION;
} else {
m_notifyQueue.push(std::move(name));
}
}
void ImGuiMenuTools::notifyAchievement(std::string name) {
m_achievementsWindow.notify(std::move(name));
void ImGuiMenuTools::showAchievementNotification() {
if (!getSettings().game.enableAchievementNotifications.getValue()) {
return;
}
if (m_notifyTimer <= 0.f) {
if (m_notifyQueue.empty()) {
return;
}
m_notifyName = std::move(m_notifyQueue.front());
m_notifyQueue.pop();
m_notifyTimer = NOTIFY_DURATION;
}
m_notifyTimer -= ImGui::GetIO().DeltaTime;
const float alpha = std::min({
m_notifyTimer / NOTIFY_FADE_TIME,
(NOTIFY_DURATION - m_notifyTimer) / NOTIFY_FADE_TIME,
1.0f
});
const ImGuiViewport* viewport = ImGui::GetMainViewport();
const float padding = 12.0f;
ImGui::SetNextWindowPos(
ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - padding, viewport->WorkPos.y + padding),
ImGuiCond_Always, ImVec2(1.0f, 0.0f)
);
ImGui::SetNextWindowBgAlpha(alpha * 0.92f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.06f, 0.01f, alpha * 0.92f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.1f, alpha));
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, alpha));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.0f, 10.0f));
constexpr ImGuiWindowFlags flags =
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs;
if (ImGui::Begin("##achievement_notify", nullptr, flags)) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.82f, 0.1f, alpha));
ImGui::TextUnformatted("Achievement Unlocked!");
ImGui::PopStyleColor();
ImGui::Spacing();
ImGui::TextUnformatted(m_notifyName.c_str());
}
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor(3);
}
}
+7 -4
View File
@@ -2,10 +2,10 @@
#define DUSK_IMGUI_MENUTOOLS_HPP
#include <aurora/aurora.h>
#include <queue>
#include <string>
#include "imgui.h"
#include "ImGuiAchievements.hpp"
#include "ImGuiSaveEditor.hpp"
#include "ImGuiStateShare.hpp"
@@ -27,8 +27,8 @@ namespace dusk {
void ShowAudioDebug();
void ShowSaveEditor();
void ShowStateShare();
void ShowAchievements();
void notifyAchievement(std::string name);
void showAchievementNotification();
private:
bool m_showDebugOverlay = false;
@@ -69,8 +69,11 @@ namespace dusk {
bool m_showStateShare = false;
ImGuiStateShare m_stateShare;
bool m_showAchievements = false;
ImGuiAchievements m_achievementsWindow;
std::string m_notifyName;
float m_notifyTimer = 0.f;
std::queue<std::string> m_notifyQueue;
static constexpr float NOTIFY_DURATION = 4.0f;
static constexpr float NOTIFY_FADE_TIME = 0.5f;
};
}
+2
View File
@@ -18,6 +18,7 @@ UserSettings g_userSettings = {
.fanfareVolume {"audio.fanfareVolume", 100},
.enableReverb {"audio.enableReverb", true},
.enableHrtf {"audio.enableHrtf", false},
.menuSounds {"audio.menuSounds", true},
},
.game = {
@@ -135,6 +136,7 @@ void registerSettings() {
Register(g_userSettings.audio.fanfareVolume);
Register(g_userSettings.audio.enableReverb);
Register(g_userSettings.audio.enableHrtf);
Register(g_userSettings.audio.menuSounds);
// Game
Register(g_userSettings.game.language);
+208
View File
@@ -0,0 +1,208 @@
#include "achievements.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "dusk/achievements.h"
#include "fmt/format.h"
#include "m_Do/m_Do_audio.h"
#include "nav_types.hpp"
#include "pane.hpp"
namespace dusk::ui {
namespace {
struct CategoryInfo {
AchievementCategory cat;
const char* label;
};
constexpr CategoryInfo kCategories[] = {
{AchievementCategory::Story, "Story"},
{AchievementCategory::Collection, "Collection"},
{AchievementCategory::Challenge, "Challenge"},
{AchievementCategory::Minigame, "Minigame"},
{AchievementCategory::Misc, "Misc"},
{AchievementCategory::Glitched, "Glitched"},
};
Rml::String build_achievement_info_rml(const Achievement& a) {
Rml::String s = fmt::format(
R"(<div class="achievement-header">)"
R"(<span class="achievement-name{}">{}</span>)"
R"(<span class="achievement-badge{}">{}</span>)"
R"(</div>)"
R"(<p class="achievement-desc">{}</p>)",
a.unlocked ? " unlocked" : "",
a.name,
a.unlocked ? " unlocked" : " locked",
a.unlocked ? "Unlocked" : "Locked",
a.description
);
if (a.isCounter) {
float fraction = a.goal > 0 ? float(a.progress) / float(a.goal) : 1.0f;
s += fmt::format(
R"(<progressbar value="{:.3f}" class="{}"/>)"
R"(<span class="achievement-progress">{} / {}</span>)",
fraction,
a.unlocked ? "progress-done" : "progress-ongoing",
a.progress,
a.goal
);
}
return s;
}
class AchievementRow : public FluentComponent<AchievementRow> {
public:
AchievementRow(Rml::Element* parent, const Achievement& a)
: FluentComponent(createRowRoot(parent))
{
auto& btn = add_child<Button>(Button::Props{"×"});
mClearButton = &btn;
btn.root()->SetClass("achievement-clear", true);
btn.on_nav_command([this, key = std::string(a.key)](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
if (mConfirming) {
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
AchievementSystem::get().clearOne(key.c_str());
resetConfirm();
} else {
mConfirming = true;
mClearButton->set_text("Clear?");
}
return true;
}
if (cmd == NavCommand::Cancel && mConfirming) {
resetConfirm();
return true;
}
return false;
});
Component::listen(btn.root(), Rml::EventId::Blur, [this](Rml::Event&) {
resetConfirm();
});
auto* infoDiv = append(mRoot, "div");
infoDiv->SetClass("achievement-info", true);
infoDiv->SetInnerRML(build_achievement_info_rml(a));
}
bool focus() override { return mClearButton->focus(); }
private:
static Rml::Element* createRowRoot(Rml::Element* parent) {
auto* doc = parent->GetOwnerDocument();
auto elem = doc->CreateElement("div");
elem->SetClass("achievement-row", true);
return parent->AppendChild(std::move(elem));
}
void resetConfirm() {
mConfirming = false;
mClearButton->set_text("×");
}
Button* mClearButton = nullptr;
bool mConfirming = false;
};
} // namespace
AchievementsWindow::AchievementsWindow() {
const auto all = AchievementSystem::get().getAchievements();
for (const auto& catInfo : kCategories) {
int catTotal = 0;
for (const auto& a : all) {
if (a.category == catInfo.cat) {
++catTotal;
}
}
if (catTotal == 0) {
continue;
}
add_tab(catInfo.label, [this, cat = catInfo.cat](Rml::Element* content) {
const auto achievements = AchievementSystem::get().getAchievements();
int total = 0, unlocked = 0;
for (const auto& a : achievements) {
if (a.category == cat) {
++total;
if (a.unlocked) {
++unlocked;
}
}
}
auto& pane = add_child<Pane>(content, Pane::Type::Controlled);
pane.add_section(fmt::format("{} / {} unlocked", unlocked, total));
for (const auto& a : achievements) {
if (a.category != cat) {
continue;
}
pane.add_child<AchievementRow>(a);
}
pane.add_section("Actions");
auto& clearAllBtn = pane.add_button("Clear All Achievements");
auto* clearAllPtr = &clearAllBtn;
auto confirmingAll = std::make_shared<bool>(false);
clearAllBtn.on_nav_command([clearAllPtr, confirmingAll](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
if (*confirmingAll) {
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
AchievementSystem::get().clearAll();
*confirmingAll = false;
clearAllPtr->set_text("Clear All Achievements");
} else {
*confirmingAll = true;
clearAllPtr->set_text("Are you sure?");
}
return true;
}
if (cmd == NavCommand::Cancel && *confirmingAll) {
*confirmingAll = false;
clearAllPtr->set_text("Clear All Achievements");
return true;
}
return false;
});
clearAllBtn.listen(Rml::EventId::Blur, [clearAllPtr, confirmingAll](Rml::Event&) {
*confirmingAll = false;
clearAllPtr->set_text("Clear All Achievements");
});
pane.finalize();
});
}
}
void AchievementsWindow::update() {
const auto current = AchievementSystem::get().getAchievements();
bool dirty = current.size() != mSnapshot.size();
if (!dirty) {
for (size_t i = 0; i < current.size(); ++i) {
if (current[i].progress != mSnapshot[i].progress ||
current[i].unlocked != mSnapshot[i].unlocked)
{
dirty = true;
break;
}
}
}
if (dirty) {
mSnapshot = current;
refresh_active_tab();
}
Window::update();
}
} // namespace dusk::ui
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include "dusk/achievements.h"
#include "window.hpp"
#include <vector>
namespace dusk::ui {
class AchievementsWindow : public Window {
public:
AchievementsWindow();
void update() override;
private:
std::vector<Achievement> mSnapshot;
};
} // namespace dusk::ui
+6 -1
View File
@@ -1,5 +1,8 @@
#include "bool_button.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
namespace dusk::ui {
BoolButton::BoolButton(Rml::Element* parent, Props props)
@@ -31,7 +34,9 @@ Rml::String BoolButton::format_value() {
bool BoolButton::handle_nav_command(NavCommand cmd) {
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left || cmd == NavCommand::Right) {
mSetValue(!mGetValue());
const bool newValue = !mGetValue();
mSetValue(newValue);
mDoAud_seStartMenu(newValue ? Z2SE_SY_CURSOR_OK : Z2SE_SY_CURSOR_CANCEL);
return true;
}
return false;
+4
View File
@@ -2,6 +2,9 @@
#include "ui.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
#include <utility>
namespace dusk::ui {
@@ -34,6 +37,7 @@ Button& Button::on_pressed(ButtonCallback callback) {
// TODO: convert this to a FluentComponent method?
on_nav_command([callback = std::move(callback)](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
callback();
return true;
}
+16 -2
View File
@@ -3,6 +3,9 @@
#include "aurora/rmlui.hpp"
#include "ui.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
namespace dusk::ui {
namespace {
@@ -17,7 +20,7 @@ Rml::ElementDocument* load_document(const Rml::String& source) {
} // namespace
Document::Document(const Rml::String& source) : mDocument(load_document(source)) {
// Block events while hidden (except for Menu command)
// Block events while hidden (except for Menu command); play nav sounds when visible
listen(
Rml::EventId::Keydown,
[this](Rml::Event& event) {
@@ -38,8 +41,18 @@ Document::Document(const Rml::String& source) : mDocument(load_document(source))
listen(Rml::EventId::Keydown, [this](Rml::Event& event) {
const auto cmd = map_nav_event(event);
if (cmd != NavCommand::None && handle_nav_command(event, cmd)) {
if (cmd == NavCommand::None) {
return;
}
auto* prevFocused = mDocument->GetFocusLeafNode();
if (handle_nav_command(event, cmd)) {
event.StopPropagation();
return;
}
if ((cmd == NavCommand::Up || cmd == NavCommand::Down) &&
mDocument->GetFocusLeafNode() != prevFocused)
{
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
}
});
}
@@ -100,6 +113,7 @@ bool Document::visible() const {
bool Document::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (cmd == NavCommand::Menu) {
mDoAud_seStartMenu(visible() ? Z2SE_SY_MENU_OUT : Z2SE_SY_MENU_IN);
toggle();
return true;
}
+3 -2
View File
@@ -635,7 +635,8 @@ void process_axis_direction(
}
set_pad_button_held(port, heldPadButton, true);
const bool chorded = heldPadButton == PAD_TRIGGER_R && is_menu_chord(port);
const bool chorded = heldPadButton == PAD_TRIGGER_R && is_menu_chord(port) &&
(port >= sMenuChordConsumed.size() || !sMenuChordConsumed[port]);
if (chorded) {
consume_menu_chord(port, context);
}
@@ -657,7 +658,7 @@ void process_axis_direction(
} // namespace
void sync_input_block() noexcept {
const bool shouldBlock = any_document_visible() || should_block_pad_for_menu_chord();
const bool shouldBlock = any_document_visible();
if (sPadInputBlocked == shouldBlock) {
return;
}
+10 -5
View File
@@ -1,5 +1,8 @@
#include "number_button.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
#include <charconv>
#include <fmt/format.h>
@@ -51,11 +54,13 @@ void NumberButton::set_value(Rml::String value) {
}
bool NumberButton::handle_nav_command(NavCommand cmd) {
if (cmd == NavCommand::Left) {
mSetValue(std::clamp(mGetValue() - mStep, mMin, mMax));
return true;
} else if (cmd == NavCommand::Right) {
mSetValue(std::clamp(mGetValue() + mStep, mMin, mMax));
if (cmd == NavCommand::Left || cmd == NavCommand::Right) {
const int newValue = std::clamp(
mGetValue() + (cmd == NavCommand::Right ? mStep : -mStep), mMin, mMax);
if (newValue != mGetValue()) {
mSetValue(newValue);
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
}
return true;
}
return BaseStringButton::handle_nav_command(cmd);
+5
View File
@@ -1,5 +1,8 @@
#include "overlay.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
#include <dolphin/gx/GXAurora.h>
#include <dolphin/vi.h>
#include <fmt/format.h>
@@ -146,6 +149,7 @@ void SteppedCarousel::apply(int value) {
if (nextValue == currentValue) {
return;
}
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
if (mProps.onChange) {
mProps.onChange(nextValue);
}
@@ -229,6 +233,7 @@ Overlay::Overlay(OverlayProps props)
}
void Overlay::show() {
mDoAud_seStartMenu(Z2SE_SY_CURSOR_OK);
Document::show();
mRoot->SetAttribute("open", "");
}
+3
View File
@@ -1,5 +1,7 @@
#include "pane.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
#include "ui.hpp"
namespace dusk::ui {
@@ -56,6 +58,7 @@ Pane::Pane(Rml::Element* parent, Type type) : FluentComponent(createRoot(parent)
int i = focusedChild + direction;
while (i >= 0 && i < mChildren.size()) {
if (mChildren[i]->focus()) {
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
event.StopPropagation();
break;
}
+24 -5
View File
@@ -2,8 +2,15 @@
#include <RmlUi/Core.h>
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
#include "aurora/rmlui.hpp"
#include "dusk/main.h"
#include "dusk/settings.h"
#include "f_pc/f_pc_manager.h"
#include "f_pc/f_pc_name.h"
#include "achievements.hpp"
#include "editor.hpp"
#include "imgui.h"
#include "settings.hpp"
@@ -40,19 +47,23 @@ Popup::Popup() : Document(kDocumentSource), mRoot(mDocument->GetElementById("pop
// // TODO
// });
mTabBar->add_tab("Editor", [this] { push(std::make_unique<EditorWindow>()); });
mTabBar->add_tab("Achievements", [this] { push(std::make_unique<AchievementsWindow>()); });
mTabBar->add_tab("Reset", [this] {
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
mTabBar->set_active_tab(-1);
if (fpcM_SearchByName(fpcNm_LOGO_SCENE_e)) {
return;
}
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
hide(false);
});
mTabBar->add_tab("Quit", [] { IsRunning = false; });
// Hide document after transition completion
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") &&
Document::visible())
Document::visible() && mPendingClose)
{
Document::hide(mPendingClose);
Document::hide(true);
}
});
@@ -64,9 +75,13 @@ void Popup::show() {
Document::show();
mRoot->SetAttribute("open", "");
mTabBar->set_active_tab(-1);
if (!mTabBar->focus_tab(mFocusedTabIndex)) {
mTabBar->focus();
}
}
void Popup::hide(bool close) {
mFocusedTabIndex = mTabBar->focused_tab_index();
mRoot->RemoveAttribute("open");
if (close) {
mPendingClose = true;
@@ -121,7 +136,11 @@ bool Popup::visible() const {
}
bool Popup::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (cmd == NavCommand::Cancel) {
if (!getSettings().backend.wasPresetChosen) {
return true;
}
if (cmd == NavCommand::Cancel && visible()) {
mDoAud_seStartMenu(Z2SE_SY_MENU_OUT);
hide(false);
return true;
}
+1
View File
@@ -32,6 +32,7 @@ private:
std::unique_ptr<Button> mCloseButton;
Insets mTabBarPadding;
float mTopMargin = 0.f;
int mFocusedTabIndex = -1;
};
} // namespace dusk::ui
+191
View File
@@ -0,0 +1,191 @@
#include "preset.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "button.hpp"
#include "dusk/config.hpp"
#include "dusk/settings.h"
#include "m_Do/m_Do_audio.h"
#include "ui.hpp"
#include <dolphin/gx/GXAurora.h>
namespace dusk::ui {
namespace {
void applyPresetClassic() {
auto& s = getSettings();
s.video.lockAspectRatio.setValue(true);
s.game.bloomMode.setValue(BloomMode::Classic);
AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT);
}
void applyPresetHD() {
auto& s = getSettings();
s.game.bloomMode.setValue(BloomMode::Classic);
s.game.hideTvSettingsScreen.setValue(true);
s.game.skipWarningScreen.setValue(true);
s.game.noReturnRupees.setValue(true);
s.game.disableRupeeCutscenes.setValue(true);
s.game.noSwordRecoil.setValue(true);
s.game.fastClimbing.setValue(true);
s.game.noMissClimbing.setValue(true);
s.game.fastTears.setValue(true);
s.game.biggerWallets.setValue(true);
s.game.invertCameraXAxis.setValue(true);
s.game.freeCamera.setValue(true);
s.game.no2ndFishForCat.setValue(true);
}
void applyPresetDusk() {
applyPresetHD();
auto& s = getSettings();
s.game.enableAchievementNotifications.setValue(true);
s.game.enableQuickTransform.setValue(true);
s.game.instantSaves.setValue(true);
s.game.midnasLamentNonStop.setValue(true);
s.game.enableFrameInterpolation.setValue(true);
s.game.sunsSong.setValue(true);
s.game.bloomMode.setValue(BloomMode::Dusk);
s.game.autoSave.setValue(true);
}
Rml::Element* createElement(Rml::Element* parent, const Rml::String& tag) {
auto* doc = parent->GetOwnerDocument();
auto elem = doc->CreateElement(tag);
return parent->AppendChild(std::move(elem));
}
const Rml::String kDocumentSource = R"RML(
<rml>
<head>
<link type="text/rcss" href="res/rml/window.rcss" />
</head>
<body>
<window id="window">
<div id="preset-dialog" class="preset-dialog"></div>
</window>
</body>
</rml>
)RML";
} // namespace
PresetWindow::PresetWindow()
: Document(kDocumentSource), mRoot(mDocument->GetElementById("window")) {
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mRoot && !mRoot->HasAttribute("open") &&
Document::visible()) {
Document::hide(mPendingClose);
}
});
auto* dialog = mDocument->GetElementById("preset-dialog");
auto* title = createElement(dialog, "div");
title->SetClass("preset-title", true);
title->SetInnerRML("Welcome to Dusk!");
auto* intro = createElement(dialog, "div");
intro->SetClass("preset-intro", true);
intro->SetInnerRML(
"Choose a preset to get started. "
"You can change any setting later from the Enhancements menu.");
auto* grid = createElement(dialog, "div");
grid->SetClass("preset-grid", true);
struct PresetInfo {
const char* name;
const char* desc;
void (*apply)();
};
static constexpr PresetInfo kPresets[] = {
{"Classic",
"All enhancements disabled to match the GameCube version. "
"Good for speedrunning or simple nostalgia!",
applyPresetClassic},
{"HD",
"Some enhancements enabled to match the HD version. "
"A good starting point for most players!",
applyPresetHD},
{"Dusk",
"More enhancements enabled than the HD preset. "
"Veteran players will appreciate the additional tweaks!",
applyPresetDusk},
};
for (const auto& preset : kPresets) {
auto* col = createElement(grid, "div");
col->SetClass("preset-col", true);
auto btn = std::make_unique<Button>(col, Rml::String(preset.name));
btn->root()->SetClass("preset-btn", true);
btn->on_nav_command([this, apply = preset.apply](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
apply();
getSettings().backend.wasPresetChosen.setValue(true);
config::Save();
hide(true);
return true;
}
return false;
});
mButtons.push_back(std::move(btn));
auto* desc = createElement(col, "div");
desc->SetClass("preset-desc", true);
desc->SetInnerRML(escape(preset.desc));
}
}
void PresetWindow::show() {
Document::show();
mRoot->SetAttribute("open", "");
}
void PresetWindow::hide(bool close) {
mRoot->RemoveAttribute("open");
mPendingClose = close;
}
bool PresetWindow::visible() const {
return mRoot->HasAttribute("open");
}
bool PresetWindow::focus() {
if (!mButtons.empty()) {
return mButtons.front()->focus();
}
return false;
}
bool PresetWindow::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (cmd == NavCommand::Cancel || cmd == NavCommand::Menu) {
return true;
}
int direction = 0;
if (cmd == NavCommand::Left) {
direction = -1;
} else if (cmd == NavCommand::Right) {
direction = 1;
} else {
return false;
}
auto* target = event.GetTargetElement();
for (int i = 0; i < static_cast<int>(mButtons.size()); ++i) {
if (mButtons[i]->contains(target)) {
const int next = i + direction;
if (next >= 0 && next < static_cast<int>(mButtons.size())) {
if (mButtons[next]->focus()) {
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
return true;
}
}
return false;
}
}
return false;
}
} // namespace dusk::ui
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include "component.hpp"
#include "document.hpp"
#include <memory>
#include <vector>
namespace dusk::ui {
class PresetWindow : public Document {
public:
PresetWindow();
void show() override;
void hide(bool close) override;
bool visible() const override;
bool focus() override;
protected:
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
private:
Rml::Element* mRoot = nullptr;
std::vector<std::unique_ptr<Component>> mButtons;
};
} // namespace dusk::ui
+5
View File
@@ -179,6 +179,11 @@ SettingsWindow::SettingsWindow() {
.helpText = "Emulate surround sound via HRTF. Recommended only for use with headphones!",
.onChange = [](bool value) { audio::EnableHrtf = value; },
});
config_bool_select(leftPane, rightPane, getSettings().audio.menuSounds,
{
.key = "Dusk Menu Sounds",
.helpText = "Play sound effects when navigating the Dusk menu.",
});
leftPane.add_section("Tweaks");
config_bool_select(leftPane, rightPane, getSettings().game.noLowHpSound,
+55 -11
View File
@@ -1,5 +1,8 @@
#include "tab_bar.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
namespace dusk::ui {
namespace {
@@ -41,7 +44,14 @@ TabBar::TabBar(Rml::Element* parent, Props props)
: FluentComponent(createRoot(parent)), mProps(std::move(props)) {
if (mProps.onClose) {
mRoot->SetAttribute("closable", "");
add_child<Button>(Button::Props{}, "close").on_pressed([this] { mProps.onClose(); });
add_child<Button>(Button::Props{}, "close").on_nav_command([this](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
mDoAud_seStartMenu(Z2SE_SY_CURSOR_CANCEL);
mProps.onClose();
return true;
}
return false;
});
mEndSpacer = append(mRoot, "tab-end-spacer");
}
@@ -85,12 +95,14 @@ bool TabBar::focus() {
if (mProps.selectedTabIndex >= 0 && mProps.selectedTabIndex < mTabs.size()) {
// Try to focus the currently selected tab
if (mTabs[mProps.selectedTabIndex].button.focus()) {
mLastFocusedTabIndex = mProps.selectedTabIndex;
return true;
}
}
// Otherwise, focus the first enabled tab
for (const auto& tab : mTabs) {
if (tab.button.focus()) {
for (int i = 0; i < static_cast<int>(mTabs.size()); ++i) {
if (mTabs[i].button.focus()) {
mLastFocusedTabIndex = i;
return true;
}
}
@@ -104,7 +116,14 @@ void TabBar::add_tab(const Rml::String& title, TabCallback callback) {
callback();
}
auto& button = add_child<Button>(Button::Props{title}, "tab");
button.on_pressed([this, index] { set_active_tab(index); });
button.on_nav_command([this, index](Rml::Event&, NavCommand cmd) {
if (cmd == NavCommand::Confirm) {
mDoAud_seStartMenu(mProps.autoSelect ? Z2SE_SY_NAME_CURSOR : Z2SE_SY_CURSOR_OK);
set_active_tab(index);
return true;
}
return false;
});
if (selected) {
button.set_selected(true);
}
@@ -134,6 +153,7 @@ bool TabBar::set_active_tab(int index) {
}
const auto& tab = mTabs[index];
if (tab.button.focus()) {
mLastFocusedTabIndex = index;
for (int i = 0; i < static_cast<int>(mTabs.size()); ++i) {
mTabs[i].button.set_selected(i == index);
}
@@ -146,11 +166,30 @@ bool TabBar::set_active_tab(int index) {
return false;
}
void TabBar::refresh_active_tab() {
if (mProps.selectedTabIndex >= 0 &&
mProps.selectedTabIndex < static_cast<int>(mTabs.size()))
{
const auto& tab = mTabs[mProps.selectedTabIndex];
if (tab.callback) {
tab.callback();
}
}
}
int TabBar::focused_tab_index() const {
return mLastFocusedTabIndex;
}
bool TabBar::focus_tab(int index) {
if (index < 0 || index >= mTabs.size() || index == mProps.selectedTabIndex) {
return false;
}
return mTabs[index].button.focus();
if (mTabs[index].button.focus()) {
mLastFocusedTabIndex = index;
return true;
}
return false;
}
int TabBar::tab_containing(Rml::Element* element) const {
@@ -172,18 +211,23 @@ bool TabBar::handle_nav_command(Rml::Event& event, NavCommand cmd) {
currentComponent = tab_containing(event.GetTargetElement());
}
int direction = isNext ? 1 : -1;
int i = currentComponent + direction;
if (currentComponent == -1) {
// If the container itself is focused and right is pressed, focus the first element
if (isNext) {
i = 0;
if (cmd == NavCommand::Left || cmd == NavCommand::Right) {
// If the container itself is focused and right is pressed, focus the first element
if (!isNext) {
return false;
}
currentComponent = -1;
} else {
// Otherwise, allow event to bubble
// Next/Previous require a currently selected tab to navigate from
return false;
}
}
int i = currentComponent + direction;
while (i >= 0 && i < mTabs.size()) {
if (mProps.autoSelect ? set_active_tab(i) : focus_tab(i)) {
const bool changed = mProps.autoSelect ? set_active_tab(i) : focus_tab(i);
if (changed) {
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
return true;
}
i += direction;
+3
View File
@@ -28,7 +28,9 @@ public:
void add_tab(const Rml::String& title, TabCallback callback);
bool set_active_tab(int index);
void refresh_active_tab();
bool focus_tab(int index);
int focused_tab_index() const;
bool handle_nav_command(Rml::Event& event, NavCommand cmd);
private:
@@ -38,6 +40,7 @@ private:
std::vector<Tab> mTabs;
Rml::Element* mEndSpacer = nullptr;
bool mRedirectingScroll = false;
int mLastFocusedTabIndex = -1;
};
} // namespace dusk::ui
+16 -3
View File
@@ -6,6 +6,9 @@
#include "pane.hpp"
#include "ui.hpp"
#include "Z2AudioLib/Z2SeMgr.h"
#include "m_Do/m_Do_audio.h"
#include <algorithm>
#include <cmath>
@@ -136,6 +139,10 @@ bool Window::set_active_tab(int index) {
return mTabBar->set_active_tab(index);
}
void Window::refresh_active_tab() {
mTabBar->refresh_active_tab();
}
void Window::add_tab(const Rml::String& title, TabBuilder builder) {
mTabBar->add_tab(title, [this, builder = std::move(builder)] {
clear_content();
@@ -168,11 +175,13 @@ bool Window::handle_nav_command(Rml::Event& event, NavCommand cmd) {
}
}
if (cmd == NavCommand::Confirm || cmd == NavCommand::Down) {
if (!mContentComponents.empty()) {
return mContentComponents.front()->focus();
if (!mContentComponents.empty() && mContentComponents.front()->focus()) {
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
return true;
}
}
if (cmd == NavCommand::Cancel) {
mDoAud_seStartMenu(Z2SE_SY_CURSOR_CANCEL);
pop();
return true;
}
@@ -184,7 +193,11 @@ bool Window::handle_nav_command(Rml::Event& event, NavCommand cmd) {
bool Window::handle_content_nav(Rml::Event& event, NavCommand cmd) noexcept {
if (cmd == NavCommand::Up) {
return focus();
if (focus()) {
mDoAud_seStartMenu(Z2SE_SY_NAME_CURSOR);
return true;
}
return false;
} else if (cmd == NavCommand::Cancel) {
int currentComponent = -1;
for (int i = 0; i < mContentComponents.size(); ++i) {
+1
View File
@@ -32,6 +32,7 @@ public:
protected:
void add_tab(const Rml::String& title, TabBuilder builder);
void refresh_active_tab();
void update_safe_area() noexcept;
void clear_content() noexcept;
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;