mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-05-28 16:00:40 -04:00
Add full Settings window to prelaunch
This commit is contained in:
@@ -1488,8 +1488,6 @@ set(DUSK_FILES
|
||||
src/dusk/ui/popup.hpp
|
||||
src/dusk/ui/prelaunch.cpp
|
||||
src/dusk/ui/prelaunch.hpp
|
||||
src/dusk/ui/prelaunch_options.cpp
|
||||
src/dusk/ui/prelaunch_options.hpp
|
||||
src/dusk/ui/preset.cpp
|
||||
src/dusk/ui/preset.hpp
|
||||
src/dusk/ui/select_button.cpp
|
||||
|
||||
@@ -52,10 +52,6 @@ window[open] {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
window[open].blurred {
|
||||
filter: blur(2dp);
|
||||
}
|
||||
|
||||
@media (max-height: 640dp) {
|
||||
body {
|
||||
padding: 16dp;
|
||||
|
||||
@@ -18,7 +18,6 @@ public:
|
||||
Rml::String bodyRml;
|
||||
std::vector<ModalAction> actions;
|
||||
std::function<void(Modal&)> onDismiss;
|
||||
bool doBlur = false;
|
||||
};
|
||||
|
||||
explicit Modal(Props props);
|
||||
|
||||
+55
-16
@@ -1,17 +1,17 @@
|
||||
#include "prelaunch.hpp"
|
||||
|
||||
#include "popup.hpp"
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/file_select.hpp"
|
||||
#include "dusk/iso_validate.hpp"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/ui/prelaunch_options.hpp"
|
||||
#include "dusk/ui/preset.hpp"
|
||||
#include "modal.hpp"
|
||||
#include "popup.hpp"
|
||||
#include "preset.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "version.h"
|
||||
|
||||
#include <SDL3/SDL_dialog.h>
|
||||
#include <SDL3/SDL_filesystem.h>
|
||||
#include <aurora/lib/window.hpp>
|
||||
|
||||
namespace dusk::ui {
|
||||
@@ -171,8 +171,8 @@ Prelaunch::Prelaunch() : Document(kDocumentSource), mRoot(mDocument->GetElementB
|
||||
|
||||
if (auto* menuList = mDocument->GetElementById("menu-list")) {
|
||||
auto& state = prelaunch_state();
|
||||
mMenuButtons.push_back(
|
||||
std::make_unique<Button>(menuList, state.selectedDiscIsValid ? "Play" : "Select Disc Image"));
|
||||
mMenuButtons.push_back(std::make_unique<Button>(
|
||||
menuList, state.selectedDiscIsValid ? "Play" : "Select Disc Image"));
|
||||
mMenuButtons.back()->on_pressed([this] {
|
||||
if (!prelaunch_state().selectedDiscIsValid) {
|
||||
open_iso_picker();
|
||||
@@ -196,7 +196,10 @@ Prelaunch::Prelaunch() : Document(kDocumentSource), mRoot(mDocument->GetElementB
|
||||
apply_intro_animation(mMenuButtons.back()->root(), "delay-1");
|
||||
|
||||
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Options"));
|
||||
mMenuButtons.back()->on_pressed([this] { push(std::make_unique<PrelaunchOptions>()); });
|
||||
mMenuButtons.back()->on_pressed([this] {
|
||||
mRestartSuppressed = false;
|
||||
push(std::make_unique<SettingsWindow>(true));
|
||||
});
|
||||
apply_intro_animation(mMenuButtons.back()->root(), "delay-2");
|
||||
|
||||
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Quit To Desktop"));
|
||||
@@ -227,6 +230,40 @@ void Prelaunch::show() {
|
||||
Document::show();
|
||||
mDocument->SetAttribute("open", "");
|
||||
mRoot->SetAttribute("open", "");
|
||||
|
||||
if (is_restart_pending() && !mRestartSuppressed) {
|
||||
const auto dismiss = [this](Modal& modal) {
|
||||
mRestartSuppressed = true;
|
||||
modal.pop();
|
||||
};
|
||||
std::vector<ModalAction> actions;
|
||||
if constexpr (dusk::SupportsProcessRestart) {
|
||||
actions.push_back(ModalAction{
|
||||
.label = "Restart later",
|
||||
.onPressed = dismiss,
|
||||
});
|
||||
actions.push_back(ModalAction{
|
||||
.label = "Restart now",
|
||||
.onPressed = [](Modal&) { dusk::RequestRestart(); },
|
||||
});
|
||||
} else {
|
||||
actions.push_back(ModalAction{
|
||||
.label = "OK",
|
||||
.onPressed = dismiss,
|
||||
});
|
||||
}
|
||||
push(std::make_unique<Modal>(Modal::Props{
|
||||
.title = "Apply Options",
|
||||
.bodyRml =
|
||||
dusk::SupportsProcessRestart ?
|
||||
"A restart is required to apply selected options.<br/><br/>Restart now to "
|
||||
"apply them immediately?" :
|
||||
"A restart is required to apply selected options.<br/><br/>Close and reopen "
|
||||
"Dusk to apply them.",
|
||||
.actions = std::move(actions),
|
||||
.onDismiss = dismiss,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
void Prelaunch::hide(bool close) {
|
||||
@@ -249,20 +286,21 @@ void Prelaunch::update() {
|
||||
|
||||
auto& state = prelaunch_state();
|
||||
if (!state.errorString.empty() && top_document() == this) {
|
||||
auto dismissInvalidDisc = [](Modal& modal) {
|
||||
auto dismiss = [](Modal& modal) {
|
||||
prelaunch_state().errorString.clear();
|
||||
modal.pop();
|
||||
};
|
||||
push_document(std::make_unique<Modal>(Modal::Props{
|
||||
push(std::make_unique<Modal>(Modal::Props{
|
||||
.title = "Invalid disc image",
|
||||
.bodyRml = state.errorString,
|
||||
.actions = {
|
||||
ModalAction{
|
||||
.label = "OK",
|
||||
.onPressed = dismissInvalidDisc,
|
||||
.actions =
|
||||
{
|
||||
ModalAction{
|
||||
.label = "OK",
|
||||
.onPressed = dismiss,
|
||||
},
|
||||
},
|
||||
},
|
||||
.onDismiss = dismissInvalidDisc,
|
||||
.onDismiss = dismiss,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -295,7 +333,8 @@ void Prelaunch::update() {
|
||||
if (mDiscDetail != nullptr) {
|
||||
if (hasValidPath) {
|
||||
mDiscDetail->SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Block);
|
||||
mDiscDetail->SetInnerRML(prelaunch_state().initialDiscIsPal ? "GameCube • EUR" : "GameCube • USA");
|
||||
mDiscDetail->SetInnerRML(
|
||||
prelaunch_state().initialDiscIsPal ? "GameCube • EUR" : "GameCube • USA");
|
||||
} else {
|
||||
mDiscDetail->SetProperty(Rml::PropertyId::Display, Rml::Style::Display::None);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ protected:
|
||||
|
||||
private:
|
||||
bool mEntranceAnimationStarted = false;
|
||||
bool mRestartSuppressed = false;
|
||||
std::vector<std::unique_ptr<Button>> mMenuButtons;
|
||||
Rml::Element* mRoot = nullptr;
|
||||
Rml::Element* mDiscStatus = nullptr;
|
||||
|
||||
@@ -1,420 +0,0 @@
|
||||
#include "prelaunch_options.hpp"
|
||||
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "pane.hpp"
|
||||
#include "prelaunch.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
static constexpr std::array<const char*, 5> kLanguageNames = {
|
||||
"English",
|
||||
"German",
|
||||
"French",
|
||||
"Spanish",
|
||||
"Italian",
|
||||
};
|
||||
|
||||
// TODO: Copied from ImGui prelaunch. Needs a refactor?
|
||||
bool try_parse_backend(std::string_view backend, AuroraBackend& outBackend) {
|
||||
if (backend == "auto") {
|
||||
outBackend = BACKEND_AUTO;
|
||||
return true;
|
||||
}
|
||||
if (backend == "d3d11") {
|
||||
outBackend = BACKEND_D3D11;
|
||||
return true;
|
||||
}
|
||||
if (backend == "d3d12") {
|
||||
outBackend = BACKEND_D3D12;
|
||||
return true;
|
||||
}
|
||||
if (backend == "metal") {
|
||||
outBackend = BACKEND_METAL;
|
||||
return true;
|
||||
}
|
||||
if (backend == "vulkan") {
|
||||
outBackend = BACKEND_VULKAN;
|
||||
return true;
|
||||
}
|
||||
if (backend == "opengl") {
|
||||
outBackend = BACKEND_OPENGL;
|
||||
return true;
|
||||
}
|
||||
if (backend == "opengles") {
|
||||
outBackend = BACKEND_OPENGLES;
|
||||
return true;
|
||||
}
|
||||
if (backend == "webgpu") {
|
||||
outBackend = BACKEND_WEBGPU;
|
||||
return true;
|
||||
}
|
||||
if (backend == "null") {
|
||||
outBackend = BACKEND_NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string_view backend_name(AuroraBackend backend) {
|
||||
switch (backend) {
|
||||
default:
|
||||
return "Auto";
|
||||
case BACKEND_D3D12:
|
||||
return "D3D12";
|
||||
case BACKEND_D3D11:
|
||||
return "D3D11";
|
||||
case BACKEND_METAL:
|
||||
return "Metal";
|
||||
case BACKEND_VULKAN:
|
||||
return "Vulkan";
|
||||
case BACKEND_OPENGL:
|
||||
return "OpenGL";
|
||||
case BACKEND_OPENGLES:
|
||||
return "OpenGL ES";
|
||||
case BACKEND_WEBGPU:
|
||||
return "WebGPU";
|
||||
case BACKEND_NULL:
|
||||
return "Null";
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view backend_id(AuroraBackend backend) {
|
||||
switch (backend) {
|
||||
default:
|
||||
return "auto";
|
||||
case BACKEND_D3D12:
|
||||
return "d3d12";
|
||||
case BACKEND_D3D11:
|
||||
return "d3d11";
|
||||
case BACKEND_METAL:
|
||||
return "metal";
|
||||
case BACKEND_VULKAN:
|
||||
return "vulkan";
|
||||
case BACKEND_OPENGL:
|
||||
return "opengl";
|
||||
case BACKEND_OPENGLES:
|
||||
return "opengles";
|
||||
case BACKEND_WEBGPU:
|
||||
return "webgpu";
|
||||
case BACKEND_NULL:
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<AuroraBackend> available_backends() {
|
||||
std::vector<AuroraBackend> backends;
|
||||
backends.emplace_back(BACKEND_AUTO);
|
||||
size_t backendCount = 0;
|
||||
const AuroraBackend* raw = aurora_get_available_backends(&backendCount);
|
||||
for (size_t i = 0; i < backendCount; ++i) {
|
||||
// Do not expose NULL or D3D11
|
||||
if (raw[i] != BACKEND_NULL && raw[i] != BACKEND_D3D11) {
|
||||
backends.emplace_back(raw[i]);
|
||||
}
|
||||
}
|
||||
return backends;
|
||||
}
|
||||
|
||||
class DiscSelect final : public SelectButton {
|
||||
public:
|
||||
explicit DiscSelect(Rml::Element* parent)
|
||||
: SelectButton(parent, Props{.key = "Change Disc Image"}) {}
|
||||
|
||||
void update() override {
|
||||
ensure_initialized();
|
||||
|
||||
const auto& path = prelaunch_state().selectedDiscPath;
|
||||
std::string display;
|
||||
if (path.empty()) {
|
||||
display = "(none)";
|
||||
} else {
|
||||
display = std::filesystem::path(path).filename().string();
|
||||
if (display.empty()) {
|
||||
display = path;
|
||||
}
|
||||
}
|
||||
const auto& initial = prelaunch_state().initialDiscPath;
|
||||
if (!initial.empty() && path != initial) {
|
||||
display += " (restart required)";
|
||||
}
|
||||
set_value_label(Rml::String(display));
|
||||
SelectButton::update();
|
||||
}
|
||||
|
||||
protected:
|
||||
bool handle_nav_command(NavCommand cmd) override {
|
||||
if (cmd != NavCommand::Confirm) {
|
||||
return false;
|
||||
}
|
||||
open_iso_picker();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class LanguageSelect final : public SelectButton {
|
||||
public:
|
||||
explicit LanguageSelect(Rml::Element* parent)
|
||||
: SelectButton(parent, Props{.key = "Language"}) {}
|
||||
|
||||
void update() override {
|
||||
ensure_initialized();
|
||||
|
||||
// LanguageInit already forces English for USA discs, so we can just change the button's
|
||||
// value instead of actually updating the config. This allows the old language setting to
|
||||
// be remembered when switching back to a PAL disc.
|
||||
const auto& state = prelaunch_state();
|
||||
std::string value;
|
||||
if (state.selectedDiscIsValid && !state.selectedDiscIsPal) {
|
||||
value = kLanguageNames[0];
|
||||
set_disabled(true);
|
||||
} else {
|
||||
const u8 idx = static_cast<u8>(getSettings().game.language.getValue());
|
||||
value = kLanguageNames[idx];
|
||||
set_disabled(false);
|
||||
}
|
||||
if (getSettings().game.language.getValue() != state.initialLanguage) {
|
||||
value += " (restart required)";
|
||||
}
|
||||
|
||||
set_value_label(Rml::String(value));
|
||||
SelectButton::update();
|
||||
}
|
||||
|
||||
protected:
|
||||
bool handle_nav_command(NavCommand cmd) override {
|
||||
if (disabled()) {
|
||||
return false;
|
||||
}
|
||||
if (cmd != NavCommand::Confirm && cmd != NavCommand::Left && cmd != NavCommand::Right) {
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr int n = static_cast<int>(kLanguageNames.size());
|
||||
int idx = static_cast<int>(getSettings().game.language.getValue());
|
||||
const int dir = (cmd == NavCommand::Left) ? -1 : 1;
|
||||
idx = ((idx + dir) % n + n) % n;
|
||||
getSettings().game.language.setValue(static_cast<GameLanguage>(idx));
|
||||
config::Save();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class BackendSelect final : public SelectButton {
|
||||
public:
|
||||
explicit BackendSelect(Rml::Element* parent)
|
||||
: SelectButton(parent, Props{.key = "Graphics Backend"}) {}
|
||||
|
||||
void update() override {
|
||||
AuroraBackend configuredBackend = BACKEND_AUTO;
|
||||
const auto configuredId = getSettings().backend.graphicsBackend.getValue();
|
||||
if (!try_parse_backend(configuredId, configuredBackend)) {
|
||||
configuredBackend = BACKEND_AUTO;
|
||||
}
|
||||
// Do not expose NULL or D3D11
|
||||
if (configuredBackend == BACKEND_NULL || configuredBackend == BACKEND_D3D11) {
|
||||
getSettings().backend.graphicsBackend.setValue("auto");
|
||||
config::Save();
|
||||
configuredBackend = BACKEND_AUTO;
|
||||
}
|
||||
|
||||
const auto backend = getSettings().backend.graphicsBackend.getValue();
|
||||
Rml::String value = backend_name(configuredBackend).data();
|
||||
if (backend != prelaunch_state().initialGraphicsBackend) {
|
||||
value += " (restart required)";
|
||||
}
|
||||
set_value_label(value);
|
||||
SelectButton::update();
|
||||
}
|
||||
|
||||
protected:
|
||||
bool handle_nav_command(NavCommand cmd) override {
|
||||
if (cmd != NavCommand::Confirm && cmd != NavCommand::Left && cmd != NavCommand::Right) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto backends = available_backends();
|
||||
const int n = static_cast<int>(backends.size());
|
||||
if (n <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AuroraBackend configuredBackend = BACKEND_AUTO;
|
||||
const auto configuredId = getSettings().backend.graphicsBackend.getValue();
|
||||
if (!try_parse_backend(configuredId, configuredBackend)) {
|
||||
configuredBackend = BACKEND_AUTO;
|
||||
}
|
||||
|
||||
int idx = 0;
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (backends[static_cast<size_t>(i)] == configuredBackend) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const int dir = (cmd == NavCommand::Left) ? -1 : 1;
|
||||
idx = ((idx + dir) % n + n) % n;
|
||||
getSettings().backend.graphicsBackend.setValue(
|
||||
std::string(backend_id(backends[static_cast<size_t>(idx)])));
|
||||
config::Save();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class SaveTypeSelect final : public SelectButton {
|
||||
public:
|
||||
explicit SaveTypeSelect(Rml::Element* parent)
|
||||
: SelectButton(parent, Props{.key = "Save File Type"}) {}
|
||||
|
||||
void update() override {
|
||||
ensure_initialized();
|
||||
|
||||
const CARDFileType cft =
|
||||
static_cast<CARDFileType>(getSettings().backend.cardFileType.getValue());
|
||||
std::string label = cft == CARD_GCIFOLDER ? "GCI Folder" : "Card Image";
|
||||
if (getSettings().backend.cardFileType.getValue() != prelaunch_state().initialCardFileType)
|
||||
{
|
||||
label += " (restart required)";
|
||||
}
|
||||
set_value_label(Rml::String(label));
|
||||
SelectButton::update();
|
||||
}
|
||||
|
||||
protected:
|
||||
bool handle_nav_command(NavCommand cmd) override {
|
||||
if (cmd != NavCommand::Confirm && cmd != NavCommand::Left && cmd != NavCommand::Right) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CARDFileType cft = static_cast<CARDFileType>(getSettings().backend.cardFileType.getValue());
|
||||
const CARDFileType newValue = cft == CARD_GCIFOLDER ? CARD_RAWIMAGE : CARD_GCIFOLDER;
|
||||
getSettings().backend.cardFileType.setValue(newValue);
|
||||
config::Save();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
PrelaunchOptions::PrelaunchOptions() {
|
||||
mSuppressNavFallback = true;
|
||||
add_tab("Options", [this](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
leftPane.add_child<DiscSelect>();
|
||||
leftPane.add_child<LanguageSelect>();
|
||||
leftPane.add_child<BackendSelect>();
|
||||
leftPane.add_child<SaveTypeSelect>();
|
||||
});
|
||||
}
|
||||
|
||||
void PrelaunchOptions::push_modal(Modal::Props props) {
|
||||
for (auto& action : props.actions) {
|
||||
auto originalOnPressed = std::move(action.onPressed);
|
||||
action.onPressed = [this, props, callback = std::move(originalOnPressed)](Modal& modal) {
|
||||
if (props.doBlur) {
|
||||
mRoot->SetClass("blurred", false);
|
||||
}
|
||||
if (callback) {
|
||||
callback(modal);
|
||||
}
|
||||
};
|
||||
}
|
||||
auto originalOnDismiss = std::move(props.onDismiss);
|
||||
props.onDismiss = [this, props, callback = std::move(originalOnDismiss)](Modal& modal) {
|
||||
if (props.doBlur) {
|
||||
mRoot->SetClass("blurred", false);
|
||||
}
|
||||
if (callback) {
|
||||
callback(modal);
|
||||
}
|
||||
};
|
||||
if (props.doBlur) {
|
||||
mRoot->SetClass("blurred", true);
|
||||
}
|
||||
push_document(std::make_unique<Modal>(std::move(props)));
|
||||
}
|
||||
|
||||
void PrelaunchOptions::hide(bool close) {
|
||||
mRoot->SetClass("blurred", false);
|
||||
Window::hide(close);
|
||||
}
|
||||
|
||||
void PrelaunchOptions::update() {
|
||||
Window::update();
|
||||
|
||||
auto& state = prelaunch_state();
|
||||
if (state.errorString.empty() || top_document() != this) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto dismissInvalidDisc = [](Modal& modal) {
|
||||
prelaunch_state().errorString.clear();
|
||||
modal.pop();
|
||||
};
|
||||
push_modal(Modal::Props{
|
||||
.title = "Invalid disc image",
|
||||
.bodyRml = state.errorString,
|
||||
.actions =
|
||||
{
|
||||
ModalAction{
|
||||
.label = "OK",
|
||||
.onPressed = dismissInvalidDisc,
|
||||
},
|
||||
},
|
||||
.onDismiss = dismissInvalidDisc,
|
||||
.doBlur = true,
|
||||
});
|
||||
}
|
||||
|
||||
bool PrelaunchOptions::consume_close_request() {
|
||||
if (!is_restart_pending()) {
|
||||
return false;
|
||||
}
|
||||
if (top_document() != this) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<ModalAction> actions;
|
||||
if constexpr (dusk::SupportsProcessRestart) {
|
||||
actions.push_back(ModalAction{
|
||||
.label = "Restart later",
|
||||
.onPressed =
|
||||
[this](Modal& modal) {
|
||||
modal.pop();
|
||||
pop();
|
||||
},
|
||||
});
|
||||
actions.push_back(ModalAction{
|
||||
.label = "Restart now",
|
||||
.onPressed = [](Modal&) { dusk::RequestRestart(); },
|
||||
});
|
||||
} else {
|
||||
actions.push_back(ModalAction{
|
||||
.label = "OK",
|
||||
.onPressed =
|
||||
[this](Modal& modal) {
|
||||
modal.pop();
|
||||
pop();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
push_modal(Modal::Props{
|
||||
.title = "Apply Options",
|
||||
.bodyRml = dusk::SupportsProcessRestart ?
|
||||
"A restart is required to apply selected options.<br/><br/>Restart now to "
|
||||
"apply them immediately?" :
|
||||
"A restart is required to apply selected options.<br/><br/>Close and reopen "
|
||||
"Dusk to apply them.",
|
||||
.actions = std::move(actions),
|
||||
.onDismiss = [](Modal& modal) { modal.pop(); },
|
||||
.doBlur = true,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "modal.hpp"
|
||||
#include "window.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
class PrelaunchOptions : public Window {
|
||||
public:
|
||||
PrelaunchOptions();
|
||||
void update() override;
|
||||
void hide(bool close) override;
|
||||
|
||||
protected:
|
||||
bool consume_close_request() override;
|
||||
|
||||
private:
|
||||
void push_modal(Modal::Props props);
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -59,6 +59,7 @@ SelectButton& SelectButton::on_pressed(SelectButtonCallback callback) {
|
||||
listen(Rml::EventId::Submit, [this, callback = std::move(callback)](Rml::Event& event) {
|
||||
if (!disabled() && event.GetTargetElement() == mRoot) {
|
||||
callback();
|
||||
event.StopPropagation();
|
||||
}
|
||||
});
|
||||
return *this;
|
||||
|
||||
+297
-18
@@ -12,13 +12,139 @@
|
||||
#include "number_button.hpp"
|
||||
#include "overlay.hpp"
|
||||
#include "pane.hpp"
|
||||
#include "prelaunch.hpp"
|
||||
#include "ui.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "modal.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
constexpr std::array kLanguageNames = {
|
||||
"English",
|
||||
"German",
|
||||
"French",
|
||||
"Spanish",
|
||||
"Italian",
|
||||
};
|
||||
|
||||
constexpr std::array kCardFileTypes = {
|
||||
"Card Image",
|
||||
"GCI Folder",
|
||||
};
|
||||
|
||||
bool try_parse_backend(std::string_view backend, AuroraBackend& outBackend) {
|
||||
if (backend == "auto") {
|
||||
outBackend = BACKEND_AUTO;
|
||||
return true;
|
||||
}
|
||||
if (backend == "d3d11") {
|
||||
outBackend = BACKEND_D3D11;
|
||||
return true;
|
||||
}
|
||||
if (backend == "d3d12") {
|
||||
outBackend = BACKEND_D3D12;
|
||||
return true;
|
||||
}
|
||||
if (backend == "metal") {
|
||||
outBackend = BACKEND_METAL;
|
||||
return true;
|
||||
}
|
||||
if (backend == "vulkan") {
|
||||
outBackend = BACKEND_VULKAN;
|
||||
return true;
|
||||
}
|
||||
if (backend == "opengl") {
|
||||
outBackend = BACKEND_OPENGL;
|
||||
return true;
|
||||
}
|
||||
if (backend == "opengles") {
|
||||
outBackend = BACKEND_OPENGLES;
|
||||
return true;
|
||||
}
|
||||
if (backend == "webgpu") {
|
||||
outBackend = BACKEND_WEBGPU;
|
||||
return true;
|
||||
}
|
||||
if (backend == "null") {
|
||||
outBackend = BACKEND_NULL;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string_view backend_name(AuroraBackend backend) {
|
||||
switch (backend) {
|
||||
default:
|
||||
return "Auto";
|
||||
case BACKEND_D3D12:
|
||||
return "D3D12";
|
||||
case BACKEND_D3D11:
|
||||
return "D3D11";
|
||||
case BACKEND_METAL:
|
||||
return "Metal";
|
||||
case BACKEND_VULKAN:
|
||||
return "Vulkan";
|
||||
case BACKEND_OPENGL:
|
||||
return "OpenGL";
|
||||
case BACKEND_OPENGLES:
|
||||
return "OpenGL ES";
|
||||
case BACKEND_WEBGPU:
|
||||
return "WebGPU";
|
||||
case BACKEND_NULL:
|
||||
return "Null";
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view backend_id(AuroraBackend backend) {
|
||||
switch (backend) {
|
||||
default:
|
||||
return "auto";
|
||||
case BACKEND_D3D12:
|
||||
return "d3d12";
|
||||
case BACKEND_D3D11:
|
||||
return "d3d11";
|
||||
case BACKEND_METAL:
|
||||
return "metal";
|
||||
case BACKEND_VULKAN:
|
||||
return "vulkan";
|
||||
case BACKEND_OPENGL:
|
||||
return "opengl";
|
||||
case BACKEND_OPENGLES:
|
||||
return "opengles";
|
||||
case BACKEND_WEBGPU:
|
||||
return "webgpu";
|
||||
case BACKEND_NULL:
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<AuroraBackend> available_backends() {
|
||||
std::vector<AuroraBackend> backends;
|
||||
backends.emplace_back(BACKEND_AUTO);
|
||||
size_t backendCount = 0;
|
||||
const AuroraBackend* raw = aurora_get_available_backends(&backendCount);
|
||||
for (size_t i = 0; i < backendCount; ++i) {
|
||||
// Do not expose NULL or D3D11
|
||||
if (raw[i] != BACKEND_NULL && raw[i] != BACKEND_D3D11) {
|
||||
backends.emplace_back(raw[i]);
|
||||
}
|
||||
}
|
||||
return backends;
|
||||
}
|
||||
|
||||
AuroraBackend configured_backend() {
|
||||
AuroraBackend configuredBackend = BACKEND_AUTO;
|
||||
const auto configuredId = getSettings().backend.graphicsBackend.getValue();
|
||||
if (!try_parse_backend(configuredId, configuredBackend)) {
|
||||
configuredBackend = BACKEND_AUTO;
|
||||
}
|
||||
return configuredBackend;
|
||||
}
|
||||
|
||||
void reset_for_speedrun_mode() {
|
||||
mDoMain::developmentMode = -1;
|
||||
|
||||
@@ -165,30 +291,156 @@ void overlay_control(
|
||||
|
||||
} // namespace
|
||||
|
||||
SettingsWindow::SettingsWindow() {
|
||||
SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
if (prelaunch) {
|
||||
mSuppressNavFallback = true;
|
||||
add_tab("Prelaunch", [this](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
|
||||
leftPane.register_control(
|
||||
leftPane
|
||||
.add_select_button({
|
||||
.key = "Disc Image",
|
||||
.getValue =
|
||||
[] {
|
||||
const auto& path = prelaunch_state().selectedDiscPath;
|
||||
std::string display;
|
||||
if (path.empty()) {
|
||||
display = "(none)";
|
||||
} else {
|
||||
display = std::filesystem::path(path).filename().string();
|
||||
if (display.empty()) {
|
||||
display = path;
|
||||
}
|
||||
}
|
||||
return display;
|
||||
},
|
||||
.isModified =
|
||||
[] {
|
||||
const auto& state = prelaunch_state();
|
||||
const auto& initial = state.initialDiscPath;
|
||||
return !initial.empty() && state.selectedDiscPath != initial;
|
||||
},
|
||||
})
|
||||
.on_pressed([] { open_iso_picker(); }),
|
||||
rightPane, [](Pane& pane) {
|
||||
pane.add_rml("Set the disc image that Dusk uses to launch the game.<br/><br/>"
|
||||
"Changes require a restart.");
|
||||
});
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Language",
|
||||
.getValue =
|
||||
[] {
|
||||
const auto& state = prelaunch_state();
|
||||
if (!state.selectedDiscIsValid || !state.selectedDiscIsPal) {
|
||||
return kLanguageNames[0];
|
||||
}
|
||||
const u8 idx = static_cast<u8>(getSettings().game.language.getValue());
|
||||
return kLanguageNames[idx];
|
||||
},
|
||||
.isDisabled =
|
||||
[] {
|
||||
const auto& state = prelaunch_state();
|
||||
return !state.selectedDiscIsValid || !state.selectedDiscIsPal;
|
||||
},
|
||||
.isModified =
|
||||
[] {
|
||||
return getSettings().game.language.getValue() !=
|
||||
prelaunch_state().initialLanguage;
|
||||
},
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
for (int i = 0; i < kLanguageNames.size(); i++) {
|
||||
pane.add_button({
|
||||
.text = kLanguageNames[i],
|
||||
.isSelected =
|
||||
[i] {
|
||||
return getSettings().game.language.getValue() ==
|
||||
static_cast<GameLanguage>(i);
|
||||
},
|
||||
})
|
||||
.on_pressed([i] {
|
||||
getSettings().game.language.setValue(static_cast<GameLanguage>(i));
|
||||
});
|
||||
}
|
||||
pane.add_rml("<br/>Changes require a restart.");
|
||||
});
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Graphics Backend",
|
||||
.getValue = [] { return Rml::String{backend_name(configured_backend())}; },
|
||||
.isModified =
|
||||
[] {
|
||||
return getSettings().backend.graphicsBackend.getValue() !=
|
||||
prelaunch_state().initialGraphicsBackend;
|
||||
},
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
const auto availableBackends = available_backends();
|
||||
for (const auto backend : availableBackends) {
|
||||
pane
|
||||
.add_button({
|
||||
.text = Rml::String{backend_name(backend)},
|
||||
.isSelected = [backend] { return configured_backend() == backend; },
|
||||
})
|
||||
.on_pressed([backend] {
|
||||
getSettings().backend.graphicsBackend.setValue(
|
||||
std::string{backend_id(backend)});
|
||||
});
|
||||
}
|
||||
pane.add_rml("<br/>Changes require a restart.");
|
||||
});
|
||||
leftPane.register_control(
|
||||
leftPane.add_select_button({
|
||||
.key = "Save File Type",
|
||||
.getValue =
|
||||
[] {
|
||||
return kCardFileTypes[getSettings().backend.cardFileType.getValue()];
|
||||
},
|
||||
.isModified =
|
||||
[] {
|
||||
return getSettings().backend.cardFileType.getValue() !=
|
||||
prelaunch_state().initialCardFileType;
|
||||
},
|
||||
}),
|
||||
rightPane, [](Pane& pane) {
|
||||
for (int i = 0; i < kCardFileTypes.size(); i++) {
|
||||
pane
|
||||
.add_button({
|
||||
.text = kCardFileTypes[i],
|
||||
.isSelected =
|
||||
[i] {
|
||||
return getSettings().backend.cardFileType.getValue() == i;
|
||||
},
|
||||
})
|
||||
.on_pressed([i] { getSettings().backend.cardFileType.setValue(i); });
|
||||
}
|
||||
pane.add_rml("<br/>Changes require a restart.");
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
add_tab("Graphics", [this](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
|
||||
leftPane.add_section("Display");
|
||||
|
||||
leftPane.register_control(
|
||||
leftPane.add_button("Toggle Fullscreen").on_pressed([] {
|
||||
getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen);
|
||||
VISetWindowFullscreen(getSettings().video.enableFullscreen);
|
||||
config::Save();
|
||||
}),
|
||||
rightPane, [](Pane& pane) { pane.clear(); }
|
||||
);
|
||||
leftPane.register_control(
|
||||
leftPane.add_button("Restore Default Window Size").on_pressed([] {
|
||||
getSettings().video.enableFullscreen.setValue(false);
|
||||
VISetWindowFullscreen(false);
|
||||
VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2);
|
||||
VICenterWindow();
|
||||
}),
|
||||
rightPane, [](Pane& pane) { pane.clear(); }
|
||||
);
|
||||
leftPane.register_control(leftPane.add_button("Toggle Fullscreen").on_pressed([] {
|
||||
getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen);
|
||||
VISetWindowFullscreen(getSettings().video.enableFullscreen);
|
||||
config::Save();
|
||||
}),
|
||||
rightPane, [](Pane& pane) { pane.clear(); });
|
||||
leftPane.register_control(leftPane.add_button("Restore Default Window Size").on_pressed([] {
|
||||
getSettings().video.enableFullscreen.setValue(false);
|
||||
VISetWindowFullscreen(false);
|
||||
VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2);
|
||||
VICenterWindow();
|
||||
}),
|
||||
rightPane, [](Pane& pane) { pane.clear(); });
|
||||
config_bool_select(leftPane, rightPane, getSettings().video.enableVsync,
|
||||
{
|
||||
.key = "Enable VSync",
|
||||
@@ -610,4 +862,31 @@ SettingsWindow::SettingsWindow() {
|
||||
});
|
||||
}
|
||||
|
||||
void SettingsWindow::update() {
|
||||
// Show disc validation error message if present
|
||||
if (mPrelaunch && top_document() == this) {
|
||||
auto& state = prelaunch_state();
|
||||
if (!state.errorString.empty()) {
|
||||
auto dismissInvalidDisc = [](Modal& modal) {
|
||||
prelaunch_state().errorString.clear();
|
||||
modal.pop();
|
||||
};
|
||||
push_document(std::make_unique<Modal>(Modal::Props{
|
||||
.title = "Invalid disc image",
|
||||
.bodyRml = state.errorString,
|
||||
.actions =
|
||||
{
|
||||
ModalAction{
|
||||
.label = "OK",
|
||||
.onPressed = dismissInvalidDisc,
|
||||
},
|
||||
},
|
||||
.onDismiss = dismissInvalidDisc,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Window::update();
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
|
||||
@@ -5,7 +5,12 @@ namespace dusk::ui {
|
||||
|
||||
class SettingsWindow : public Window {
|
||||
public:
|
||||
SettingsWindow();
|
||||
SettingsWindow(bool prelaunch = false);
|
||||
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
bool mPrelaunch;
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
Reference in New Issue
Block a user