mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-06-10 04:15:39 -04:00
265 lines
7.9 KiB
C++
265 lines
7.9 KiB
C++
#include "prelaunch.hpp"
|
|
|
|
#include "dusk/config.hpp"
|
|
#include "dusk/file_select.hpp"
|
|
#include "dusk/iso_validate.hpp"
|
|
#include "dusk/main.h"
|
|
#include "dusk/ui/prelaunch_options.hpp"
|
|
#include "version.h"
|
|
|
|
#include <SDL3/SDL_dialog.h>
|
|
#include <SDL3/SDL_filesystem.h>
|
|
#include <aurora/lib/window.hpp>
|
|
|
|
namespace dusk::ui {
|
|
namespace {
|
|
|
|
const Rml::String kDocumentSource = R"RML(
|
|
<rml>
|
|
<head>
|
|
<link type="text/rcss" href="res/rml/prelaunch.rcss" />
|
|
</head>
|
|
<body>
|
|
<content id="root" open>
|
|
<menu>
|
|
<hero class="intro-item delay-0">
|
|
<div class="eyebrow"><span>Twilit Realm</span> presents</div>
|
|
<img src="res/logo-mascot.png" />
|
|
</hero>
|
|
<div id="menu-list" />
|
|
</menu>
|
|
<disc-info class="intro-item delay-4">
|
|
<div id="disc-status">
|
|
<icon />
|
|
<span id="disc-status-label" />
|
|
</div>
|
|
<span id="disc-version" class="detail" />
|
|
</disc-info>
|
|
<version-info class="intro-item delay-5">
|
|
<div class="version">Version <span id="version-text"></span></div>
|
|
<div class="update"><span>Update available!</span> Download</div>
|
|
</version-info>
|
|
</content>
|
|
</body>
|
|
</rml>
|
|
)RML";
|
|
|
|
constexpr std::array<SDL_DialogFileFilter, 2> kDiscFileFilters{{
|
|
{"Game Disc Images", "iso;gcm;ciso;gcz;nfs;rvz;wbfs;wia;tgc"},
|
|
{"All Files", "*"},
|
|
}};
|
|
|
|
void file_dialog_callback(void*, const char* path, const char* error) {
|
|
auto& state = prelaunch_state();
|
|
if (error != nullptr) {
|
|
return;
|
|
}
|
|
if (path == nullptr) {
|
|
return;
|
|
}
|
|
|
|
state.selectedIsoPath = path;
|
|
state.errorString.clear();
|
|
refresh_path_state();
|
|
getSettings().backend.isoPath.setValue(state.selectedIsoPath);
|
|
config::Save();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
PrelaunchState sPrelaunchState;
|
|
|
|
PrelaunchState& prelaunch_state() noexcept {
|
|
return sPrelaunchState;
|
|
}
|
|
|
|
void refresh_path_state() noexcept {
|
|
auto& state = prelaunch_state();
|
|
state.isPal = !state.selectedIsoPath.empty() && iso::isPal(state.selectedIsoPath.c_str());
|
|
}
|
|
|
|
void ensure_initialized() noexcept {
|
|
auto& state = prelaunch_state();
|
|
if (state.initialized) {
|
|
return;
|
|
}
|
|
|
|
state.selectedIsoPath = getSettings().backend.isoPath;
|
|
state.initialGraphicsBackend = getSettings().backend.graphicsBackend;
|
|
state.errorString.clear();
|
|
state.initialized = true;
|
|
refresh_path_state();
|
|
}
|
|
|
|
bool is_selected_path_valid() noexcept {
|
|
return !prelaunch_state().selectedIsoPath.empty() &&
|
|
SDL_GetPathInfo(prelaunch_state().selectedIsoPath.c_str(), nullptr);
|
|
}
|
|
|
|
void open_iso_picker() noexcept {
|
|
ensure_initialized();
|
|
ShowFileSelect(&file_dialog_callback, nullptr, aurora::window::get_sdl_window(),
|
|
kDiscFileFilters.data(), kDiscFileFilters.size(), nullptr, false);
|
|
}
|
|
|
|
void apply_intro_animation(Rml::Element* element, const char* delay_class) {
|
|
if (element == nullptr || delay_class == nullptr) {
|
|
return;
|
|
}
|
|
element->SetClass("intro-item", true);
|
|
element->SetClass(delay_class, true);
|
|
}
|
|
|
|
Prelaunch::Prelaunch() : Document(kDocumentSource), mRoot(mDocument->GetElementById("root")) {
|
|
ensure_initialized();
|
|
|
|
if (auto* menuList = mDocument->GetElementById("menu-list")) {
|
|
const bool hasValidPath = is_selected_path_valid();
|
|
mMenuButtons.push_back(
|
|
std::make_unique<Button>(menuList, hasValidPath ? "Start Game" : "Select Disc Image"));
|
|
mMenuButtons.back()->on_pressed([this] {
|
|
if (!is_selected_path_valid()) {
|
|
open_iso_picker();
|
|
return;
|
|
}
|
|
IsGameLaunched = true;
|
|
hide(true);
|
|
});
|
|
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>()); });
|
|
apply_intro_animation(mMenuButtons.back()->root(), "delay-2");
|
|
|
|
mMenuButtons.push_back(std::make_unique<Button>(menuList, "Quit To Desktop"));
|
|
mMenuButtons.back()->on_pressed([] { IsRunning = false; });
|
|
apply_intro_animation(mMenuButtons.back()->root(), "delay-3");
|
|
}
|
|
|
|
mDiscStatus = mDocument->GetElementById("disc-status");
|
|
mDiscDetail = mDocument->GetElementById("disc-version");
|
|
mVersion = mDocument->GetElementById("version-text");
|
|
|
|
listen(mDocument, Rml::EventId::Transitionend, [this](Rml::Event& event) {
|
|
auto* target = event.GetTargetElement();
|
|
if (target == nullptr) {
|
|
return;
|
|
}
|
|
if (target == mDocument && !mDocument->HasAttribute("open")) {
|
|
Document::hide(true);
|
|
} else if (target->GetTagName() == "button" && !target->IsClassSet("anim-done")) {
|
|
target->SetClass("anim-done", true);
|
|
}
|
|
});
|
|
}
|
|
|
|
void Prelaunch::show() {
|
|
Document::show();
|
|
mDocument->SetAttribute("open", "");
|
|
mRoot->SetAttribute("open", "");
|
|
}
|
|
|
|
void Prelaunch::hide(bool close) {
|
|
if (close) {
|
|
if (!mEntranceAnimationStarted) {
|
|
// Close document immediately
|
|
Document::hide(true);
|
|
}
|
|
mDocument->RemoveAttribute("open");
|
|
} else {
|
|
mRoot->RemoveAttribute("open");
|
|
}
|
|
}
|
|
|
|
void Prelaunch::update() {
|
|
ensure_initialized();
|
|
refresh_path_state();
|
|
|
|
auto& state = prelaunch_state();
|
|
const bool hasValidPath = is_selected_path_valid();
|
|
if (hasValidPath && getSettings().backend.skipPreLaunchUI) {
|
|
hide(true);
|
|
IsGameLaunched = true;
|
|
}
|
|
|
|
if (!mEntranceAnimationStarted && mDocument != nullptr) {
|
|
mDocument->SetClass("animate-in", true);
|
|
mEntranceAnimationStarted = true;
|
|
}
|
|
|
|
if (!mMenuButtons.empty()) {
|
|
mMenuButtons[0]->set_text(hasValidPath ? "Start Game" : "Select Disc Image");
|
|
}
|
|
|
|
const auto discStatusLabel = mDiscStatus->GetElementById("disc-status-label");
|
|
|
|
if (mDiscStatus != nullptr && discStatusLabel != nullptr) {
|
|
if (hasValidPath) {
|
|
mDiscStatus->SetAttribute("status", "good");
|
|
discStatusLabel->SetInnerRML("Disc ready.");
|
|
} else {
|
|
mDiscStatus->SetAttribute("status", "bad");
|
|
discStatusLabel->SetInnerRML("Disc not found.");
|
|
}
|
|
}
|
|
if (mDiscDetail != nullptr) {
|
|
if (hasValidPath) {
|
|
mDiscDetail->SetProperty(Rml::PropertyId::Display, Rml::Style::Display::Block);
|
|
mDiscDetail->SetInnerRML(state.isPal ? "GameCube • EUR" : "GameCube • USA");
|
|
} else {
|
|
mDiscDetail->SetProperty(Rml::PropertyId::Display, Rml::Style::Display::None);
|
|
}
|
|
}
|
|
if (mVersion != nullptr) {
|
|
std::string_view versionStr(DUSK_WC_DESCRIBE);
|
|
if (versionStr[0] == 'v') {
|
|
versionStr = versionStr.substr(1);
|
|
}
|
|
mVersion->SetInnerRML(escape(versionStr));
|
|
}
|
|
|
|
Document::update();
|
|
}
|
|
|
|
bool Prelaunch::focus() {
|
|
if (mMenuButtons.empty()) {
|
|
return false;
|
|
}
|
|
return mMenuButtons[0]->focus();
|
|
}
|
|
|
|
bool Prelaunch::visible() const {
|
|
return mDocument->HasAttribute("open") && mRoot->HasAttribute("open");
|
|
}
|
|
|
|
bool Prelaunch::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
|
int direction = 0;
|
|
if (cmd == NavCommand::Down) {
|
|
direction = 1;
|
|
} else if (cmd == NavCommand::Up) {
|
|
direction = -1;
|
|
} else {
|
|
return false;
|
|
}
|
|
auto* target = event.GetTargetElement();
|
|
int focusedButton = -1;
|
|
for (int i = 0; i < mMenuButtons.size(); ++i) {
|
|
if (mMenuButtons[i]->contains(target)) {
|
|
focusedButton = i;
|
|
break;
|
|
}
|
|
}
|
|
const auto n = static_cast<int>(mMenuButtons.size());
|
|
int i = ((focusedButton + direction) % n + n) % n;
|
|
while (i >= 0 && i < mMenuButtons.size()) {
|
|
if (mMenuButtons[i]->focus()) {
|
|
event.StopPropagation();
|
|
return true;
|
|
}
|
|
i += direction;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace dusk::ui
|