mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-06-01 09:17:15 -04:00
Start UI over from scratch and add demo window
This commit is contained in:
Vendored
+1
-1
Submodule extern/aurora updated: 8a2b80ecb1...1fed316829
+2
-22
@@ -1462,28 +1462,8 @@ set(DUSK_FILES
|
||||
src/dusk/imgui/ImGuiStateShare.cpp
|
||||
src/dusk/imgui/ImGuiAchievements.hpp
|
||||
src/dusk/imgui/ImGuiAchievements.cpp
|
||||
src/dusk/ui/button.hpp
|
||||
src/dusk/ui/button.cpp
|
||||
src/dusk/ui/control_surface.hpp
|
||||
src/dusk/ui/control_surface.cpp
|
||||
src/dusk/ui/disc_state.hpp
|
||||
src/dusk/ui/disc_state.cpp
|
||||
src/dusk/ui/element.hpp
|
||||
src/dusk/ui/element.cpp
|
||||
src/dusk/ui/focus_border.hpp
|
||||
src/dusk/ui/focus_border.cpp
|
||||
src/dusk/ui/game_menu.hpp
|
||||
src/dusk/ui/game_menu.cpp
|
||||
src/dusk/ui/game_option.hpp
|
||||
src/dusk/ui/game_option.cpp
|
||||
src/dusk/ui/label.hpp
|
||||
src/dusk/ui/label.cpp
|
||||
src/dusk/ui/prelaunch_layout.hpp
|
||||
src/dusk/ui/prelaunch_layout.cpp
|
||||
src/dusk/ui/prelaunch_screen.hpp
|
||||
src/dusk/ui/prelaunch_screen.cpp
|
||||
src/dusk/ui/theme.hpp
|
||||
src/dusk/ui/theme.cpp
|
||||
src/dusk/ui/editor.cpp
|
||||
src/dusk/ui/editor.hpp
|
||||
src/dusk/ui/ui.hpp
|
||||
src/dusk/ui/ui.cpp
|
||||
src/dusk/ui/window.hpp
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -22,8 +22,6 @@
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/ui/game_menu.hpp"
|
||||
#include "dusk/ui/prelaunch_screen.hpp"
|
||||
#include "m_Do/m_Do_controller_pad.h"
|
||||
#include "m_Do/m_Do_main.h"
|
||||
#include "tracy/Tracy.hpp"
|
||||
@@ -341,14 +339,14 @@ namespace dusk {
|
||||
ImGuiMenuGame::ToggleFullscreen();
|
||||
}
|
||||
|
||||
if (!dusk::IsGameLaunched && !dusk::ui::prelaunch::is_active()) {
|
||||
if (!dusk::IsGameLaunched) {
|
||||
m_preLaunchWindow.draw();
|
||||
}
|
||||
|
||||
m_isHidden = !getSettings().backend.duskMenuOpen;
|
||||
if (dusk::IsGameLaunched) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_F1)) {
|
||||
dusk::ui::game_menu::toggle();
|
||||
m_isHidden = !m_isHidden;
|
||||
}
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_GamepadBack)) {
|
||||
m_isHidden = !m_isHidden;
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
#include "button.hpp"
|
||||
|
||||
#include "control_surface.hpp"
|
||||
#include "element.hpp"
|
||||
#include "focus_border.hpp"
|
||||
#include "label.hpp"
|
||||
#include "theme.hpp"
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
ControlSurfaceTone control_surface_tone(ButtonVariant variant) {
|
||||
switch (variant) {
|
||||
case ButtonVariant::Primary:
|
||||
return ControlSurfaceTone::Primary;
|
||||
case ButtonVariant::Secondary:
|
||||
return ControlSurfaceTone::Secondary;
|
||||
case ButtonVariant::Quiet:
|
||||
default:
|
||||
return ControlSurfaceTone::Quiet;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Button::Button(Rml::Element* parent, std::string_view id, std::string_view text,
|
||||
ButtonVariant variant, std::function<void()> pressedCallback)
|
||||
: m_variant(variant), m_pressedCallback(std::move(pressedCallback)) {
|
||||
using namespace theme;
|
||||
|
||||
m_element = append(parent, "button", id,
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::Position, Rml::Style::Position::Relative},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Row},
|
||||
{Rml::PropertyId::AlignItems, Rml::Style::AlignItems::Center},
|
||||
{Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::Center},
|
||||
{Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox},
|
||||
{Rml::PropertyId::Width, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::Height, rml_dp(68.0f)},
|
||||
{Rml::PropertyId::MinHeight, rml_dp(68.0f)},
|
||||
{Rml::PropertyId::MaxHeight, rml_dp(68.0f)},
|
||||
{Rml::PropertyId::PaddingLeft, rml_dp(22.0f)},
|
||||
{Rml::PropertyId::PaddingRight, rml_dp(22.0f)},
|
||||
{Rml::PropertyId::BorderTopWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderRightWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderBottomWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderLeftWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderTopLeftRadius, rml_dp(BorderRadiusMedium)},
|
||||
{Rml::PropertyId::BorderTopRightRadius, rml_dp(BorderRadiusMedium)},
|
||||
{Rml::PropertyId::BorderBottomRightRadius, rml_dp(BorderRadiusMedium)},
|
||||
{Rml::PropertyId::BorderBottomLeftRadius, rml_dp(BorderRadiusMedium)},
|
||||
{Rml::PropertyId::Cursor, rml_string("pointer")},
|
||||
{Rml::PropertyId::TabIndex, Rml::Style::TabIndex::Auto},
|
||||
{Rml::PropertyId::NavUp, Rml::Style::Nav::Auto},
|
||||
{Rml::PropertyId::NavDown, Rml::Style::Nav::Auto},
|
||||
{Rml::PropertyId::NavLeft, Rml::Style::Nav::Auto},
|
||||
{Rml::PropertyId::NavRight, Rml::Style::Nav::Auto},
|
||||
{Rml::PropertyId::Opacity, rml_number(1.0f)},
|
||||
{Rml::PropertyId::FontFamily, rml_string("Inter")},
|
||||
{Rml::PropertyId::Color, rml_color(Text)},
|
||||
});
|
||||
|
||||
add_focus_border(m_element, BorderRadiusMedium);
|
||||
m_label = append_text(m_element, "span", text);
|
||||
apply_label_style(m_label, LabelStyle::Medium);
|
||||
set_props(m_label, {
|
||||
{Rml::PropertyId::PointerEvents, Rml::Style::PointerEvents::None},
|
||||
{Rml::PropertyId::TextAlign, Rml::Style::TextAlign::Center},
|
||||
});
|
||||
|
||||
m_element->AddEventListener(Rml::EventId::Click, this);
|
||||
m_element->AddEventListener(Rml::EventId::Focus, this);
|
||||
m_element->AddEventListener(Rml::EventId::Blur, this);
|
||||
m_element->AddEventListener(Rml::EventId::Mouseover, this);
|
||||
m_element->AddEventListener(Rml::EventId::Mouseout, this);
|
||||
apply_style();
|
||||
}
|
||||
|
||||
Button::~Button() {
|
||||
if (m_element == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_element->RemoveEventListener(Rml::EventId::Click, this);
|
||||
m_element->RemoveEventListener(Rml::EventId::Focus, this);
|
||||
m_element->RemoveEventListener(Rml::EventId::Blur, this);
|
||||
m_element->RemoveEventListener(Rml::EventId::Mouseover, this);
|
||||
m_element->RemoveEventListener(Rml::EventId::Mouseout, this);
|
||||
m_element = nullptr;
|
||||
}
|
||||
|
||||
void Button::ProcessEvent(Rml::Event& event) {
|
||||
switch (event.GetId()) {
|
||||
case Rml::EventId::Click:
|
||||
if (m_pressedCallback) {
|
||||
m_pressedCallback();
|
||||
}
|
||||
break;
|
||||
case Rml::EventId::Focus:
|
||||
m_focused = true;
|
||||
apply_style();
|
||||
break;
|
||||
case Rml::EventId::Blur:
|
||||
m_focused = false;
|
||||
apply_style();
|
||||
break;
|
||||
case Rml::EventId::Mouseover:
|
||||
m_hovered = true;
|
||||
apply_style();
|
||||
break;
|
||||
case Rml::EventId::Mouseout:
|
||||
m_hovered = false;
|
||||
apply_style();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Rml::Element* Button::element() const {
|
||||
return m_element;
|
||||
}
|
||||
|
||||
std::string Button::id() const {
|
||||
return m_element == nullptr ? std::string{} : m_element->GetId();
|
||||
}
|
||||
|
||||
void Button::set_text(std::string_view text) {
|
||||
ui::set_text(m_label, text);
|
||||
}
|
||||
|
||||
void Button::apply_style() {
|
||||
using namespace theme;
|
||||
|
||||
if (m_element == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool active = m_hovered || m_focused;
|
||||
apply_control_surface_style(
|
||||
m_element, control_surface_style(control_surface_tone(m_variant)), active);
|
||||
m_element->SetProperty(Rml::PropertyId::Color, rml_color(active ? TextActive : Text));
|
||||
m_label->SetProperty(Rml::PropertyId::Color, rml_color(active ? TextActive : Text));
|
||||
set_focus_border_visible(m_element, m_focused);
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -1,47 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <RmlUi/Core/EventListener.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace Rml {
|
||||
class Element;
|
||||
}
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
enum class ButtonVariant {
|
||||
Primary,
|
||||
Secondary,
|
||||
Quiet,
|
||||
};
|
||||
|
||||
class Button : public Rml::EventListener {
|
||||
public:
|
||||
Button(Rml::Element* parent, std::string_view id, std::string_view text, ButtonVariant variant,
|
||||
std::function<void()> pressedCallback);
|
||||
~Button() override;
|
||||
|
||||
Button(const Button&) = delete;
|
||||
Button& operator=(const Button&) = delete;
|
||||
|
||||
void ProcessEvent(Rml::Event& event) override;
|
||||
|
||||
Rml::Element* element() const;
|
||||
std::string id() const;
|
||||
void set_text(std::string_view text);
|
||||
|
||||
private:
|
||||
Rml::Element* m_element = nullptr;
|
||||
Rml::Element* m_label = nullptr;
|
||||
ButtonVariant m_variant = ButtonVariant::Secondary;
|
||||
std::function<void()> m_pressedCallback;
|
||||
bool m_hovered = false;
|
||||
bool m_focused = false;
|
||||
|
||||
void apply_style();
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -1,60 +0,0 @@
|
||||
#include "control_surface.hpp"
|
||||
|
||||
#include <RmlUi/Core/Element.h>
|
||||
|
||||
namespace dusk::ui {
|
||||
ControlSurfaceStyle control_surface_style(ControlSurfaceTone tone) {
|
||||
switch (tone) {
|
||||
case ControlSurfaceTone::Primary:
|
||||
return {
|
||||
.accent = theme::Primary,
|
||||
.inactiveBorderOpacity = 112,
|
||||
.inactiveBackgroundOpacity = 28,
|
||||
.activeBorderOpacity = 255,
|
||||
.activeBackgroundOpacity = 116,
|
||||
};
|
||||
case ControlSurfaceTone::Secondary:
|
||||
return {
|
||||
.accent = theme::Secondary,
|
||||
.inactiveBorderOpacity = 112,
|
||||
.inactiveBackgroundOpacity = 28,
|
||||
.activeBorderOpacity = 255,
|
||||
.activeBackgroundOpacity = 116,
|
||||
};
|
||||
case ControlSurfaceTone::Window:
|
||||
return {
|
||||
.accent = theme::WindowAccent,
|
||||
.inactiveBorderOpacity = 0,
|
||||
.inactiveBackgroundOpacity = 26,
|
||||
.activeBorderOpacity = 200,
|
||||
.activeBackgroundOpacity = 76,
|
||||
};
|
||||
case ControlSurfaceTone::Quiet:
|
||||
default:
|
||||
return {
|
||||
.accent = theme::Elevated,
|
||||
.inactiveBorderOpacity = 86,
|
||||
.inactiveBackgroundOpacity = 0,
|
||||
.activeBorderOpacity = 150,
|
||||
.activeBackgroundOpacity = 68,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void apply_control_surface_style(
|
||||
Rml::Element* element, const ControlSurfaceStyle& style, bool active) {
|
||||
if (element == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto borderColor = active ? rml_color(style.accent, style.activeBorderOpacity) :
|
||||
rml_color(theme::ElevatedBorder, style.inactiveBorderOpacity);
|
||||
element->SetProperty(Rml::PropertyId::BorderLeftColor, borderColor);
|
||||
element->SetProperty(Rml::PropertyId::BorderRightColor, borderColor);
|
||||
element->SetProperty(Rml::PropertyId::BorderTopColor, borderColor);
|
||||
element->SetProperty(Rml::PropertyId::BorderBottomColor, borderColor);
|
||||
element->SetProperty(Rml::PropertyId::BackgroundColor,
|
||||
rml_color(style.accent,
|
||||
active ? style.activeBackgroundOpacity : style.inactiveBackgroundOpacity));
|
||||
}
|
||||
} // namespace dusk::ui
|
||||
@@ -1,28 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "theme.hpp"
|
||||
|
||||
namespace Rml {
|
||||
class Element;
|
||||
}
|
||||
|
||||
namespace dusk::ui {
|
||||
enum class ControlSurfaceTone {
|
||||
Primary,
|
||||
Secondary,
|
||||
Quiet,
|
||||
Window,
|
||||
};
|
||||
|
||||
struct ControlSurfaceStyle {
|
||||
theme::Color accent = theme::Primary;
|
||||
int inactiveBorderOpacity = 86;
|
||||
int inactiveBackgroundOpacity = 0;
|
||||
int activeBorderOpacity = 150;
|
||||
int activeBackgroundOpacity = 68;
|
||||
};
|
||||
|
||||
ControlSurfaceStyle control_surface_style(ControlSurfaceTone tone);
|
||||
void apply_control_surface_style(
|
||||
Rml::Element* element, const ControlSurfaceStyle& style, bool active);
|
||||
} // namespace dusk::ui
|
||||
@@ -1,153 +0,0 @@
|
||||
#include "disc_state.hpp"
|
||||
|
||||
#include "element.hpp"
|
||||
#include "focus_border.hpp"
|
||||
#include "label.hpp"
|
||||
#include "theme.hpp"
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
DiscState::DiscState(Rml::Element* parent, std::string_view id, std::string_view text,
|
||||
std::string_view statusText, bool statusIsError, std::function<void()> pressedCallback)
|
||||
: m_pressedCallback(std::move(pressedCallback)), m_statusIsError(statusIsError) {
|
||||
using namespace theme;
|
||||
|
||||
m_element = append(parent, "button", id,
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::Position, Rml::Style::Position::Relative},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column},
|
||||
{Rml::PropertyId::AlignItems, Rml::Style::AlignItems::Stretch},
|
||||
{Rml::PropertyId::RowGap, rml_dp(6.0f)},
|
||||
{Rml::PropertyId::ColumnGap, rml_dp(6.0f)},
|
||||
{Rml::PropertyId::Width, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox},
|
||||
{Rml::PropertyId::PaddingTop, rml_dp(14.0f)},
|
||||
{Rml::PropertyId::PaddingRight, rml_dp(16.0f)},
|
||||
{Rml::PropertyId::PaddingBottom, rml_dp(14.0f)},
|
||||
{Rml::PropertyId::PaddingLeft, rml_dp(16.0f)},
|
||||
{Rml::PropertyId::BorderTopWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderRightWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderBottomWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderLeftWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderTopLeftRadius, rml_dp(BorderRadiusSmall)},
|
||||
{Rml::PropertyId::BorderTopRightRadius, rml_dp(BorderRadiusSmall)},
|
||||
{Rml::PropertyId::BorderBottomRightRadius, rml_dp(BorderRadiusSmall)},
|
||||
{Rml::PropertyId::BorderBottomLeftRadius, rml_dp(BorderRadiusSmall)},
|
||||
{Rml::PropertyId::Cursor, rml_string("pointer")},
|
||||
{Rml::PropertyId::TabIndex, Rml::Style::TabIndex::Auto},
|
||||
{Rml::PropertyId::NavUp, Rml::Style::Nav::Auto},
|
||||
{Rml::PropertyId::NavDown, Rml::Style::Nav::Auto},
|
||||
{Rml::PropertyId::NavLeft, Rml::Style::Nav::Auto},
|
||||
{Rml::PropertyId::NavRight, Rml::Style::Nav::Auto},
|
||||
{Rml::PropertyId::FontFamily, rml_string("Inter")},
|
||||
});
|
||||
|
||||
add_focus_border(m_element, BorderRadiusSmall);
|
||||
|
||||
m_value = add_label(m_element, text, LabelStyle::Body);
|
||||
set_props(m_value, {
|
||||
{Rml::PropertyId::OverflowX, Rml::Style::Overflow::Hidden},
|
||||
{Rml::PropertyId::OverflowY, Rml::Style::Overflow::Hidden},
|
||||
{Rml::PropertyId::TextOverflow, Rml::Style::TextOverflow::Ellipsis},
|
||||
{Rml::PropertyId::WhiteSpace, Rml::Style::WhiteSpace::Nowrap},
|
||||
{Rml::PropertyId::PointerEvents, Rml::Style::PointerEvents::None},
|
||||
});
|
||||
|
||||
if (!statusText.empty()) {
|
||||
m_status = add_label(m_element, statusText, LabelStyle::Annotation);
|
||||
set_props(m_status, {
|
||||
{Rml::PropertyId::PointerEvents, Rml::Style::PointerEvents::None},
|
||||
{Rml::PropertyId::WhiteSpace, Rml::Style::WhiteSpace::Normal},
|
||||
});
|
||||
}
|
||||
|
||||
m_element->AddEventListener(Rml::EventId::Click, this);
|
||||
m_element->AddEventListener(Rml::EventId::Focus, this);
|
||||
m_element->AddEventListener(Rml::EventId::Blur, this);
|
||||
m_element->AddEventListener(Rml::EventId::Mouseover, this);
|
||||
m_element->AddEventListener(Rml::EventId::Mouseout, this);
|
||||
apply_style();
|
||||
}
|
||||
|
||||
DiscState::~DiscState() {
|
||||
if (m_element == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_element->RemoveEventListener(Rml::EventId::Click, this);
|
||||
m_element->RemoveEventListener(Rml::EventId::Focus, this);
|
||||
m_element->RemoveEventListener(Rml::EventId::Blur, this);
|
||||
m_element->RemoveEventListener(Rml::EventId::Mouseover, this);
|
||||
m_element->RemoveEventListener(Rml::EventId::Mouseout, this);
|
||||
m_element = nullptr;
|
||||
}
|
||||
|
||||
void DiscState::ProcessEvent(Rml::Event& event) {
|
||||
switch (event.GetId()) {
|
||||
case Rml::EventId::Click:
|
||||
if (m_pressedCallback) {
|
||||
m_pressedCallback();
|
||||
}
|
||||
break;
|
||||
case Rml::EventId::Focus:
|
||||
m_focused = true;
|
||||
apply_style();
|
||||
break;
|
||||
case Rml::EventId::Blur:
|
||||
m_focused = false;
|
||||
apply_style();
|
||||
break;
|
||||
case Rml::EventId::Mouseover:
|
||||
m_hovered = true;
|
||||
apply_style();
|
||||
break;
|
||||
case Rml::EventId::Mouseout:
|
||||
m_hovered = false;
|
||||
apply_style();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Rml::Element* DiscState::element() const {
|
||||
return m_element;
|
||||
}
|
||||
|
||||
std::string DiscState::id() const {
|
||||
return m_element == nullptr ? std::string{} : m_element->GetId();
|
||||
}
|
||||
|
||||
void DiscState::apply_style() {
|
||||
using namespace theme;
|
||||
|
||||
if (m_element == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool active = m_hovered || m_focused;
|
||||
const Color accent = m_statusIsError ? Danger : Primary;
|
||||
|
||||
m_element->SetProperty(Rml::PropertyId::BackgroundColor,
|
||||
rml_color(accent, active ? 52 : (m_statusIsError ? 32 : 20)));
|
||||
const auto borderColor = rml_color(accent, active ? 220 : (m_statusIsError ? 190 : 120));
|
||||
m_element->SetProperty(Rml::PropertyId::BorderTopColor, borderColor);
|
||||
m_element->SetProperty(Rml::PropertyId::BorderRightColor, borderColor);
|
||||
m_element->SetProperty(Rml::PropertyId::BorderBottomColor, borderColor);
|
||||
m_element->SetProperty(Rml::PropertyId::BorderLeftColor, borderColor);
|
||||
m_element->SetProperty(Rml::PropertyId::Color, rml_color(active ? TextActive : Text));
|
||||
|
||||
m_value->SetProperty(Rml::PropertyId::Color, rml_color(active ? TextActive : Text));
|
||||
if (m_status != nullptr) {
|
||||
m_status->SetProperty(
|
||||
Rml::PropertyId::Color, rml_color(m_statusIsError ? Danger : TextDim));
|
||||
}
|
||||
set_focus_border_visible(m_element, m_focused);
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -1,41 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <RmlUi/Core/EventListener.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace Rml {
|
||||
class Element;
|
||||
}
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
class DiscState : public Rml::EventListener {
|
||||
public:
|
||||
DiscState(Rml::Element* parent, std::string_view id, std::string_view text,
|
||||
std::string_view statusText, bool statusIsError, std::function<void()> pressedCallback);
|
||||
~DiscState() override;
|
||||
|
||||
DiscState(const DiscState&) = delete;
|
||||
DiscState& operator=(const DiscState&) = delete;
|
||||
|
||||
void ProcessEvent(Rml::Event& event) override;
|
||||
|
||||
Rml::Element* element() const;
|
||||
std::string id() const;
|
||||
|
||||
private:
|
||||
Rml::Element* m_element = nullptr;
|
||||
Rml::Element* m_value = nullptr;
|
||||
Rml::Element* m_status = nullptr;
|
||||
std::function<void()> m_pressedCallback;
|
||||
bool m_statusIsError = false;
|
||||
bool m_hovered = false;
|
||||
bool m_focused = false;
|
||||
|
||||
void apply_style();
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -0,0 +1,112 @@
|
||||
#include "editor.hpp"
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
const Rml::String kPlayerStatusContent = R"RML(
|
||||
<div class="pane">
|
||||
<div class="section-heading">Player</div>
|
||||
<button class="select-button">
|
||||
<div class="key">Player Name</div>
|
||||
<div class="value">Link</div>
|
||||
</button>
|
||||
<button class="select-button">
|
||||
<div class="key">Horse Name</div>
|
||||
<div class="value">Epona</div>
|
||||
</button>
|
||||
<button class="select-button">
|
||||
<div class="key">Max Health</div>
|
||||
<div class="value">15</div>
|
||||
</button>
|
||||
<button class="select-button">
|
||||
<div class="key">Health</div>
|
||||
<div class="value">12</div>
|
||||
</button>
|
||||
<button class="select-button">
|
||||
<div class="key">Max Oil</div>
|
||||
<div class="value">0</div>
|
||||
</button>
|
||||
<button class="select-button">
|
||||
<div class="key">Oil</div>
|
||||
<div class="value">0</div>
|
||||
</button>
|
||||
<div class="section-heading">Equipment</div>
|
||||
<button class="select-button selected">
|
||||
<div class="key">Equip X</div>
|
||||
<div class="value">Spinner</div>
|
||||
</button>
|
||||
<button class="select-button">
|
||||
<div class="key">Equip Y</div>
|
||||
<div class="value">None</div>
|
||||
</button>
|
||||
<button class="select-button">
|
||||
<div class="key">Combo Equip X</div>
|
||||
<div class="value">None</div>
|
||||
</button>
|
||||
<button class="select-button">
|
||||
<div class="key">Combo Equip Y</div>
|
||||
<div class="value">None</div>
|
||||
</button>
|
||||
<button class="select-button">
|
||||
<div class="key">Clothes</div>
|
||||
<div class="value">Hero's Clothes</div>
|
||||
</button>
|
||||
</div>
|
||||
<!-- TODO: right pane is going to be dynamic based on the highlighted left pane value -->
|
||||
<div class="pane">
|
||||
<button class="button">Slot 0 (Gale Boomerang)</button>
|
||||
<button class="button">Slot 1 (Lantern)</button>
|
||||
<button class="button">Slot 2 (Spinner)</button>
|
||||
<button class="button">Slot 3 (Iron Boots)</button>
|
||||
<button class="button">Slot 4 (Hero's Bow)</button>
|
||||
<button class="button">Slot 5 (Hawkeye)</button>
|
||||
<button class="button">Slot 6 (Ball and Chain)</button>
|
||||
<button class="button">Slot 7 (None)</button>
|
||||
<button class="button">Slot 8 (Dominion Rod)</button>
|
||||
</div>
|
||||
)RML";
|
||||
|
||||
const Rml::String kLocationContent = R"RML(
|
||||
<div class="pane">
|
||||
<div class="section-heading">Save Location</div>
|
||||
<button class="select-button">
|
||||
<div class="key">Stage</div>
|
||||
<div class="value">F_SP108</div>
|
||||
</button>
|
||||
<button class="select-button">
|
||||
<div class="key">Room</div>
|
||||
<div class="value">1</div>
|
||||
</button>
|
||||
<button class="select-button">
|
||||
<div class="key">Spawn ID</div>
|
||||
<div class="value">0</div>
|
||||
</button>
|
||||
<div class="section-heading">Horse Location</div>
|
||||
<button class="select-button">
|
||||
<div class="key">Position</div>
|
||||
<div class="value">34814, -260, -41181</div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pane"></div>
|
||||
)RML";
|
||||
|
||||
} // namespace
|
||||
|
||||
EditorWindow::EditorWindow()
|
||||
: Window({.tabs = {
|
||||
{"Player Status",
|
||||
[](Rml::Element* content) {
|
||||
// TODO: actually bind values and events. wonder if we should have
|
||||
// a SettingsPane element or something for sharing?
|
||||
Rml::Factory::InstanceElementText(content, kPlayerStatusContent);
|
||||
}},
|
||||
{"Location",
|
||||
[](Rml::Element* content) {
|
||||
Rml::Factory::InstanceElementText(content, kLocationContent);
|
||||
}},
|
||||
{"Inventory"},
|
||||
}}) {}
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include "window.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
class EditorWindow : public Window {
|
||||
public:
|
||||
EditorWindow();
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -1,94 +0,0 @@
|
||||
#include "element.hpp"
|
||||
|
||||
#include <RmlUi/Core/ElementDocument.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
std::string escape(std::string_view text) {
|
||||
std::string result;
|
||||
result.reserve(text.size());
|
||||
for (char c : text) {
|
||||
switch (c) {
|
||||
case '&':
|
||||
result += "&";
|
||||
break;
|
||||
case '<':
|
||||
result += "<";
|
||||
break;
|
||||
case '>':
|
||||
result += ">";
|
||||
break;
|
||||
case '"':
|
||||
result += """;
|
||||
break;
|
||||
case '\'':
|
||||
result += "'";
|
||||
break;
|
||||
default:
|
||||
result += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Rml::Element* append(Rml::Element* parent, std::string_view tag, std::string_view id,
|
||||
std::initializer_list<std::pair<Rml::PropertyId, Rml::Property> > properties) {
|
||||
if (parent == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Rml::ElementDocument* document = parent->GetOwnerDocument();
|
||||
if (document == nullptr) {
|
||||
document = dynamic_cast<Rml::ElementDocument*>(parent);
|
||||
}
|
||||
if (document == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Rml::ElementPtr child = document->CreateElement(std::string(tag));
|
||||
Rml::Element* rawChild = child.get();
|
||||
if (!id.empty()) {
|
||||
rawChild->SetId(std::string(id));
|
||||
}
|
||||
Rml::Element* appended = parent->AppendChild(std::move(child));
|
||||
set_props(appended, properties);
|
||||
return appended;
|
||||
}
|
||||
|
||||
Rml::Element* append_text(
|
||||
Rml::Element* parent, std::string_view tag, std::string_view text, std::string_view id) {
|
||||
Rml::Element* element = append(parent, tag, id);
|
||||
set_text(element, text);
|
||||
return element;
|
||||
}
|
||||
|
||||
void set_text(Rml::Element* element, std::string_view text) {
|
||||
if (element != nullptr) {
|
||||
element->SetInnerRML(escape(text));
|
||||
}
|
||||
}
|
||||
|
||||
void set_props(Rml::Element* element,
|
||||
std::initializer_list<std::pair<std::string_view, std::string_view> > properties) {
|
||||
if (element == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (const auto& [name, value] : properties) {
|
||||
element->SetProperty(std::string(name), std::string(value));
|
||||
}
|
||||
}
|
||||
|
||||
void set_props(Rml::Element* element,
|
||||
std::initializer_list<std::pair<Rml::PropertyId, Rml::Property> > properties) {
|
||||
if (element == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (const auto& [name, value] : properties) {
|
||||
element->SetProperty(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -1,21 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <RmlUi/Core/Element.h>
|
||||
|
||||
#include <initializer_list>
|
||||
#include <string_view>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
std::string escape(std::string_view text);
|
||||
Rml::Element* append(Rml::Element* parent, std::string_view tag, std::string_view id = {},
|
||||
std::initializer_list<std::pair<Rml::PropertyId, Rml::Property> > properties = {});
|
||||
Rml::Element* append_text(
|
||||
Rml::Element* parent, std::string_view tag, std::string_view text, std::string_view id = {});
|
||||
void set_text(Rml::Element* element, std::string_view text);
|
||||
void set_props(Rml::Element* element,
|
||||
std::initializer_list<std::pair<std::string_view, std::string_view> > properties);
|
||||
void set_props(Rml::Element* element,
|
||||
std::initializer_list<std::pair<Rml::PropertyId, Rml::Property> > properties);
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -1,51 +0,0 @@
|
||||
#include "focus_border.hpp"
|
||||
|
||||
#include "element.hpp"
|
||||
#include "theme.hpp"
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
Rml::Element* add_focus_border(Rml::Element* parent, float radius) {
|
||||
using namespace theme;
|
||||
|
||||
const auto borderColor = rml_color(PrimaryLight, 0);
|
||||
return append(parent, "div", {},
|
||||
{
|
||||
{Rml::PropertyId::Position, Rml::Style::Position::Absolute},
|
||||
{Rml::PropertyId::PointerEvents, Rml::Style::PointerEvents::None},
|
||||
{Rml::PropertyId::Left, rml_dp(-(BorderWidth * 3.0f))},
|
||||
{Rml::PropertyId::Top, rml_dp(-(BorderWidth * 3.0f))},
|
||||
{Rml::PropertyId::Right, rml_dp(-(BorderWidth * 3.0f))},
|
||||
{Rml::PropertyId::Bottom, rml_dp(-(BorderWidth * 3.0f))},
|
||||
{Rml::PropertyId::BorderTopWidth, rml_dp(BorderWidth * 2.0f)},
|
||||
{Rml::PropertyId::BorderRightWidth, rml_dp(BorderWidth * 2.0f)},
|
||||
{Rml::PropertyId::BorderBottomWidth, rml_dp(BorderWidth * 2.0f)},
|
||||
{Rml::PropertyId::BorderLeftWidth, rml_dp(BorderWidth * 2.0f)},
|
||||
{Rml::PropertyId::BorderTopLeftRadius, rml_dp(radius + BorderWidth * 4.0f)},
|
||||
{Rml::PropertyId::BorderTopRightRadius, rml_dp(radius + BorderWidth * 4.0f)},
|
||||
{Rml::PropertyId::BorderBottomRightRadius, rml_dp(radius + BorderWidth * 4.0f)},
|
||||
{Rml::PropertyId::BorderBottomLeftRadius, rml_dp(radius + BorderWidth * 4.0f)},
|
||||
{Rml::PropertyId::BorderTopColor, borderColor},
|
||||
{Rml::PropertyId::BorderRightColor, borderColor},
|
||||
{Rml::PropertyId::BorderBottomColor, borderColor},
|
||||
{Rml::PropertyId::BorderLeftColor, borderColor},
|
||||
});
|
||||
}
|
||||
|
||||
void set_focus_border_visible(Rml::Element* parent, bool visible) {
|
||||
if (parent == nullptr || parent->GetNumChildren() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto borderColor = rml_color(theme::PrimaryLight, visible ? 255 : 0);
|
||||
set_props(parent->GetChild(0), {
|
||||
{Rml::PropertyId::BorderTopColor, borderColor},
|
||||
{Rml::PropertyId::BorderRightColor, borderColor},
|
||||
{Rml::PropertyId::BorderBottomColor, borderColor},
|
||||
{Rml::PropertyId::BorderLeftColor, borderColor},
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -1,10 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <RmlUi/Core/Element.h>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
Rml::Element* add_focus_border(Rml::Element* parent, float radius);
|
||||
void set_focus_border_visible(Rml::Element* parent, bool visible);
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -1,808 +0,0 @@
|
||||
#include "game_menu.hpp"
|
||||
|
||||
#include "element.hpp"
|
||||
#include "game_option.hpp"
|
||||
#include "label.hpp"
|
||||
#include "theme.hpp"
|
||||
#include "ui.hpp"
|
||||
#include "window.hpp"
|
||||
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/imgui/ImGuiEngine.hpp"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "m_Do/m_Do_graphic.h"
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
#include <RmlUi/Core/ElementDocument.h>
|
||||
#include <aurora/aurora.h>
|
||||
#include <aurora/gfx.h>
|
||||
#include <aurora/rmlui.hpp>
|
||||
#include <dolphin/vi.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace aurora::gx {
|
||||
extern bool enableLodBias;
|
||||
}
|
||||
|
||||
namespace dusk::ui::game_menu {
|
||||
namespace {
|
||||
|
||||
enum class Tab : int {
|
||||
Audio = 0,
|
||||
Cheats,
|
||||
Gameplay,
|
||||
Graphics,
|
||||
Input,
|
||||
Interface,
|
||||
Count,
|
||||
};
|
||||
|
||||
struct TabDef {
|
||||
const char* id;
|
||||
const char* label;
|
||||
};
|
||||
|
||||
constexpr std::array<TabDef, static_cast<size_t>(Tab::Count)> kTabs{{
|
||||
{"audio", "Audio"},
|
||||
{"cheats", "Cheats"},
|
||||
{"gameplay", "Gameplay"},
|
||||
{"graphics", "Graphics"},
|
||||
{"input", "Input"},
|
||||
{"interface", "Interface"},
|
||||
}};
|
||||
|
||||
constexpr int kInternalResolutionScaleMax = 12;
|
||||
constexpr int kShadowResolutionMax = 8;
|
||||
|
||||
constexpr std::array<float, 5> kBloomMultiplierStops{0.0f, 0.25f, 0.50f, 0.75f, 1.00f};
|
||||
constexpr std::array<const char*, 3> kBloomModeNames{"Off", "Classic", "Dusk"};
|
||||
|
||||
// TODO: Needs more spacing for newlines
|
||||
static const char* get_description_for_item(std::string_view id) {
|
||||
if (id == "internal-resolution") {
|
||||
return "Auto renders at the native window resolution.\nHigher values scale the internal "
|
||||
"framebuffer.";
|
||||
}
|
||||
if (id == "shadow-resolution") {
|
||||
return "Improves the shadow resolution, making them higher quality.";
|
||||
}
|
||||
if (id == "frame-interp") {
|
||||
return "Uses inter-frame interpolation to enable higher frame rates.\nVisual artifacts, "
|
||||
"animation glitches, or instability may occur.";
|
||||
}
|
||||
|
||||
return "No description found.";
|
||||
}
|
||||
|
||||
struct Row {
|
||||
std::string id;
|
||||
std::function<void()> activate;
|
||||
std::function<void(int direction)> cycle;
|
||||
};
|
||||
|
||||
class Screen : public Rml::EventListener {
|
||||
public:
|
||||
bool initialize() {
|
||||
if (m_initialized) {
|
||||
return true;
|
||||
}
|
||||
if (!ui::initialize()) {
|
||||
return false;
|
||||
}
|
||||
m_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
close_document();
|
||||
if (ui::is_active()) {
|
||||
ui::set_active(false);
|
||||
}
|
||||
m_initialized = false;
|
||||
m_focusIds.clear();
|
||||
m_rows.clear();
|
||||
}
|
||||
|
||||
bool is_open() const { return m_open; }
|
||||
|
||||
void set_open(bool open) {
|
||||
if (open == m_open) {
|
||||
return;
|
||||
}
|
||||
if (open && !initialize()) {
|
||||
return;
|
||||
}
|
||||
m_open = open;
|
||||
if (open) {
|
||||
ui::set_active(true);
|
||||
rebuild();
|
||||
} else {
|
||||
close_document();
|
||||
ui::set_active(false);
|
||||
}
|
||||
}
|
||||
|
||||
void toggle() { set_open(!m_open); }
|
||||
|
||||
void handle_event(const SDL_Event& event) {
|
||||
if (!m_open) {
|
||||
return;
|
||||
}
|
||||
ui::handle_event(event);
|
||||
}
|
||||
|
||||
void update() {
|
||||
if (!m_open) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_requestClose) {
|
||||
m_requestClose = false;
|
||||
set_open(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_pendingTabId.empty()) {
|
||||
const std::string tabId = std::move(m_pendingTabId);
|
||||
m_pendingTabId.clear();
|
||||
apply_tab_selection(tabId);
|
||||
if (!m_open) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_requestedActivation.empty()) {
|
||||
const std::string id = std::move(m_requestedActivation);
|
||||
m_requestedActivation.clear();
|
||||
invoke_activate(id);
|
||||
if (!m_open) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_requestedCycleDirection != 0) {
|
||||
const std::string id = std::move(m_requestedCycleId);
|
||||
const int direction = m_requestedCycleDirection;
|
||||
m_requestedCycleId.clear();
|
||||
m_requestedCycleDirection = 0;
|
||||
invoke_cycle(id, direction);
|
||||
if (!m_open) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_needsRebuild) {
|
||||
m_needsRebuild = false;
|
||||
rebuild();
|
||||
}
|
||||
|
||||
ui::update();
|
||||
sync_description_pane();
|
||||
}
|
||||
|
||||
void ProcessEvent(Rml::Event& event) override {
|
||||
if (event.GetId() != Rml::EventId::Keydown) {
|
||||
return;
|
||||
}
|
||||
const auto key = static_cast<Rml::Input::KeyIdentifier>(
|
||||
event.GetParameter<int>("key_identifier", Rml::Input::KI_UNKNOWN));
|
||||
if (handle_key(key)) {
|
||||
event.StopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_initialized = false;
|
||||
bool m_open = false;
|
||||
Tab m_tab = Tab::Graphics;
|
||||
Rml::ElementDocument* m_document = nullptr;
|
||||
std::unique_ptr<Window> m_window;
|
||||
std::vector<std::unique_ptr<GameOption> > m_options;
|
||||
std::vector<Row> m_rows;
|
||||
std::vector<std::string> m_focusIds;
|
||||
std::string m_pendingFocusId;
|
||||
std::string m_requestedActivation;
|
||||
std::string m_requestedCycleId;
|
||||
int m_requestedCycleDirection = 0;
|
||||
bool m_requestClose = false;
|
||||
bool m_needsRebuild = false;
|
||||
std::string m_pendingTabId;
|
||||
Rml::Element* m_descriptionElement = nullptr;
|
||||
Rml::Element* m_lastDescriptionSyncFocus = nullptr;
|
||||
|
||||
Row* find_row(std::string_view id) {
|
||||
for (auto& row : m_rows) {
|
||||
if (row.id == id) {
|
||||
return &row;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void invoke_activate(const std::string& id) {
|
||||
if (Row* row = find_row(id); row && row->activate) {
|
||||
row->activate();
|
||||
}
|
||||
}
|
||||
|
||||
void invoke_cycle(const std::string& id, int direction) {
|
||||
if (Row* row = find_row(id); row && row->cycle) {
|
||||
row->cycle(direction);
|
||||
}
|
||||
}
|
||||
|
||||
void close_document() {
|
||||
if (m_document == nullptr) {
|
||||
return;
|
||||
}
|
||||
m_document->RemoveEventListener(Rml::EventId::Keydown, this);
|
||||
m_options.clear();
|
||||
m_rows.clear();
|
||||
m_focusIds.clear();
|
||||
m_descriptionElement = nullptr;
|
||||
m_lastDescriptionSyncFocus = nullptr;
|
||||
m_window.reset();
|
||||
m_document->Close();
|
||||
m_document = nullptr;
|
||||
}
|
||||
|
||||
void style_document(Rml::ElementDocument* document) {
|
||||
using namespace theme;
|
||||
set_props(document, {
|
||||
{Rml::PropertyId::Width, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::Height, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::MarginTop, rml_px(0.0f)},
|
||||
{Rml::PropertyId::MarginRight, rml_px(0.0f)},
|
||||
{Rml::PropertyId::MarginBottom, rml_px(0.0f)},
|
||||
{Rml::PropertyId::MarginLeft, rml_px(0.0f)},
|
||||
{Rml::PropertyId::PaddingTop, rml_px(0.0f)},
|
||||
{Rml::PropertyId::PaddingRight, rml_px(0.0f)},
|
||||
{Rml::PropertyId::PaddingBottom, rml_px(0.0f)},
|
||||
{Rml::PropertyId::PaddingLeft, rml_px(0.0f)},
|
||||
{Rml::PropertyId::FontFamily, rml_string("Inter")},
|
||||
{Rml::PropertyId::Color, rml_color(Text)},
|
||||
});
|
||||
}
|
||||
|
||||
Rml::Element* add_screen() {
|
||||
using namespace theme;
|
||||
return append(m_document, "div", "game-menu-screen",
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::Position, Rml::Style::Position::Absolute},
|
||||
{Rml::PropertyId::Left, rml_px(0.0f)},
|
||||
{Rml::PropertyId::Top, rml_px(0.0f)},
|
||||
{Rml::PropertyId::Right, rml_px(0.0f)},
|
||||
{Rml::PropertyId::Bottom, rml_px(0.0f)},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column},
|
||||
{Rml::PropertyId::AlignItems, Rml::Style::AlignItems::Center},
|
||||
{Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::Center},
|
||||
{Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox},
|
||||
{Rml::PropertyId::PaddingTop, rml_dp(32.0f)},
|
||||
{Rml::PropertyId::PaddingRight, rml_dp(32.0f)},
|
||||
{Rml::PropertyId::PaddingBottom, rml_dp(32.0f)},
|
||||
{Rml::PropertyId::PaddingLeft, rml_dp(32.0f)},
|
||||
});
|
||||
}
|
||||
|
||||
Rml::Element* add_section_header(Rml::Element* parent, std::string_view title) {
|
||||
auto* row = append(parent, "div", {},
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Row},
|
||||
{Rml::PropertyId::AlignItems, Rml::Style::AlignItems::Center},
|
||||
{Rml::PropertyId::Width, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::PaddingTop, rml_dp(8.0f)},
|
||||
{Rml::PropertyId::PaddingBottom, rml_dp(4.0f)},
|
||||
});
|
||||
auto* label = add_label(row, title, LabelStyle::Annotation);
|
||||
set_props(label, {
|
||||
{Rml::PropertyId::FontSize, rml_dp(14.0f)},
|
||||
{Rml::PropertyId::LetterSpacing, rml_dp(3.0f)},
|
||||
{Rml::PropertyId::Color, rml_color(theme::WindowAccentSoft)},
|
||||
{Rml::PropertyId::FlexShrink, rml_number(0.0f)},
|
||||
});
|
||||
return row;
|
||||
}
|
||||
|
||||
Rml::Element* add_scroll_body(Rml::Element* parent) {
|
||||
return append(parent, "div", {},
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column},
|
||||
{Rml::PropertyId::Width, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::FlexGrow, rml_number(1.0f)},
|
||||
{Rml::PropertyId::MinHeight, rml_px(0.0f)},
|
||||
{Rml::PropertyId::RowGap, rml_dp(8.0f)},
|
||||
{Rml::PropertyId::ColumnGap, rml_dp(8.0f)},
|
||||
{Rml::PropertyId::OverflowY, Rml::Style::Overflow::Auto},
|
||||
});
|
||||
}
|
||||
|
||||
std::function<void()> queue_activate(std::string id) {
|
||||
return [this, id = std::move(id)] { m_requestedActivation = id; };
|
||||
}
|
||||
|
||||
void register_row(Row row, std::unique_ptr<GameOption> option) {
|
||||
m_focusIds.push_back(row.id);
|
||||
m_rows.push_back(std::move(row));
|
||||
m_options.push_back(std::move(option));
|
||||
}
|
||||
|
||||
void add_toggle(Rml::Element* parent, std::string id, std::string_view title,
|
||||
config::ConfigVar<bool>& var, std::function<void(bool)> sideEffect = {},
|
||||
std::string_view detail = {}) {
|
||||
auto mutate = [this, &var, sideEffect = std::move(sideEffect)] {
|
||||
const bool next = !var.getValue();
|
||||
var.setValue(next);
|
||||
Save();
|
||||
if (sideEffect) {
|
||||
sideEffect(next);
|
||||
}
|
||||
m_needsRebuild = true;
|
||||
};
|
||||
const std::string_view valueText = var.getValue() ? "On" : "Off";
|
||||
auto option =
|
||||
std::make_unique<GameOption>(parent, id, title, valueText, detail, queue_activate(id));
|
||||
register_row(Row{id, mutate, [mutate](int) { mutate(); }}, std::move(option));
|
||||
}
|
||||
|
||||
void add_action(Rml::Element* parent, std::string id, std::string_view title,
|
||||
std::function<void()> action, std::string_view valueText = ">",
|
||||
std::string_view detail = {}) {
|
||||
auto mutate = [this, action = std::move(action)] {
|
||||
action();
|
||||
m_needsRebuild = true;
|
||||
};
|
||||
auto option =
|
||||
std::make_unique<GameOption>(parent, id, title, valueText, detail, queue_activate(id));
|
||||
register_row(Row{id, mutate, {}}, std::move(option));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void add_cycle_row(Rml::Element* parent, std::string id, std::string_view title,
|
||||
std::string_view valueText, std::string_view detail, std::function<void(int)> cycle) {
|
||||
auto mutate = [this, cycle] {
|
||||
cycle(1);
|
||||
m_needsRebuild = true;
|
||||
};
|
||||
auto cycleWithRebuild = [this, cycle](int direction) {
|
||||
cycle(direction);
|
||||
m_needsRebuild = true;
|
||||
};
|
||||
auto option =
|
||||
std::make_unique<GameOption>(parent, id, title, valueText, detail, queue_activate(id));
|
||||
register_row(Row{id, mutate, cycleWithRebuild}, std::move(option));
|
||||
}
|
||||
|
||||
void add_int_cycle(Rml::Element* parent, std::string id, std::string_view title,
|
||||
config::ConfigVar<int>& var, int minValue, int maxValue,
|
||||
std::function<std::string(int)> formatter, std::function<void(int)> sideEffect = {}) {
|
||||
const int current = std::clamp(var.getValue(), minValue, maxValue);
|
||||
const std::string valueText = formatter(current);
|
||||
auto cycle = [&var, minValue, maxValue, sideEffect = std::move(sideEffect)](int dir) {
|
||||
int next = std::clamp(var.getValue(), minValue, maxValue) + dir;
|
||||
if (next < minValue) {
|
||||
next = maxValue;
|
||||
} else if (next > maxValue) {
|
||||
next = minValue;
|
||||
}
|
||||
var.setValue(next);
|
||||
Save();
|
||||
if (sideEffect) {
|
||||
sideEffect(next);
|
||||
}
|
||||
};
|
||||
add_cycle_row<int>(parent, std::move(id), title, valueText, {}, std::move(cycle));
|
||||
}
|
||||
|
||||
void add_bloom_mode_row(Rml::Element* parent) {
|
||||
auto& var = getSettings().game.bloomMode;
|
||||
const int current = std::clamp(
|
||||
static_cast<int>(var.getValue()), 0, static_cast<int>(kBloomModeNames.size() - 1));
|
||||
const std::string_view valueText = kBloomModeNames[static_cast<size_t>(current)];
|
||||
auto cycle = [&var](int dir) {
|
||||
const int count = kBloomModeNames.size();
|
||||
int next = static_cast<int>(var.getValue()) + dir;
|
||||
next = (next % count + count) % count;
|
||||
var.setValue(static_cast<BloomMode>(next));
|
||||
Save();
|
||||
};
|
||||
add_cycle_row<int>(parent, "bloom-mode", "Bloom", valueText, {}, std::move(cycle));
|
||||
}
|
||||
|
||||
void add_bloom_brightness_row(Rml::Element* parent) {
|
||||
auto& var = getSettings().game.bloomMultiplier;
|
||||
const std::string valueText = fmt::format("{:.2f}", var.getValue());
|
||||
auto cycle = [&var](int dir) {
|
||||
const float currentValue = var.getValue();
|
||||
int closest = 0;
|
||||
float bestDelta = std::abs(currentValue - kBloomMultiplierStops[0]);
|
||||
for (int i = 1; i < static_cast<int>(kBloomMultiplierStops.size()); ++i) {
|
||||
const float delta = std::abs(currentValue - kBloomMultiplierStops[i]);
|
||||
if (delta < bestDelta) {
|
||||
bestDelta = delta;
|
||||
closest = i;
|
||||
}
|
||||
}
|
||||
const int count = kBloomMultiplierStops.size();
|
||||
const int next = (closest + dir + count) % count;
|
||||
var.setValue(kBloomMultiplierStops[next]);
|
||||
Save();
|
||||
};
|
||||
const std::string_view detail =
|
||||
getSettings().game.bloomMode.getValue() == BloomMode::Off ? "Bloom is disabled" : "";
|
||||
add_cycle_row<int>(
|
||||
parent, "bloom-brightness", "Bloom Brightness", valueText, detail, std::move(cycle));
|
||||
}
|
||||
|
||||
void build_description_pane() {
|
||||
m_descriptionElement = nullptr;
|
||||
m_lastDescriptionSyncFocus = nullptr;
|
||||
Rml::Element* right = m_window->right_pane();
|
||||
if (right == nullptr) {
|
||||
return;
|
||||
}
|
||||
m_descriptionElement = append_text(right, "p", " ", "option-description");
|
||||
set_props(m_descriptionElement,
|
||||
{
|
||||
{Rml::PropertyId::Color, rml_color(theme::TextActive)},
|
||||
{Rml::PropertyId::FontSize, rml_dp(20.0f)},
|
||||
{Rml::PropertyId::LineHeight, Rml::Property(1.45f, Rml::Unit::EM)},
|
||||
{Rml::PropertyId::TextAlign, Rml::Style::TextAlign::Left},
|
||||
{Rml::PropertyId::AlignSelf, Rml::Style::AlignSelf::Stretch},
|
||||
{Rml::PropertyId::Width, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox},
|
||||
{Rml::PropertyId::WhiteSpace, Rml::Style::WhiteSpace::Preline},
|
||||
});
|
||||
}
|
||||
|
||||
void sync_description_pane() {
|
||||
if (m_descriptionElement == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (m_tab != Tab::Graphics) {
|
||||
return;
|
||||
}
|
||||
Rml::Context* context = aurora::rmlui::get_context();
|
||||
if (context == nullptr) {
|
||||
return;
|
||||
}
|
||||
Rml::Element* const focused = context->GetFocusElement();
|
||||
if (focused == m_lastDescriptionSyncFocus) {
|
||||
return;
|
||||
}
|
||||
m_lastDescriptionSyncFocus = focused;
|
||||
if (focused == nullptr) {
|
||||
set_text(m_descriptionElement, get_description_for_item({}));
|
||||
} else {
|
||||
set_text(m_descriptionElement, get_description_for_item(focused->GetId()));
|
||||
}
|
||||
}
|
||||
|
||||
void build_graphics_tab(Rml::Element* body) {
|
||||
auto* scroll = add_scroll_body(body);
|
||||
|
||||
add_section_header(scroll, "Display");
|
||||
|
||||
// TODO: Replace this with a Display Mode toggle.
|
||||
add_toggle(scroll, "fullscreen", "Toggle Fullscreen", getSettings().video.enableFullscreen,
|
||||
[](bool enabled) { VISetWindowFullscreen(enabled); });
|
||||
|
||||
u32 internalWidth = 0;
|
||||
u32 internalHeight = 0;
|
||||
AuroraGetRenderSize(&internalWidth, &internalHeight);
|
||||
const std::string detail = fmt::format("Current: {}x{}", internalWidth, internalHeight);
|
||||
|
||||
const int currentScale = std::clamp(
|
||||
getSettings().game.internalResolutionScale.getValue(), 0, kInternalResolutionScaleMax);
|
||||
const std::string scaleValue =
|
||||
currentScale == 0 ? std::string("Auto") : fmt::format("{}x", currentScale);
|
||||
|
||||
auto scaleCycle = [](int dir) {
|
||||
int next = std::clamp(getSettings().game.internalResolutionScale.getValue(), 0,
|
||||
kInternalResolutionScaleMax) +
|
||||
dir;
|
||||
if (next < 0) {
|
||||
next = kInternalResolutionScaleMax;
|
||||
} else if (next > kInternalResolutionScaleMax) {
|
||||
next = 0;
|
||||
}
|
||||
getSettings().game.internalResolutionScale.setValue(next);
|
||||
VISetFrameBufferScale(static_cast<float>(next));
|
||||
Save();
|
||||
};
|
||||
|
||||
add_cycle_row<int>(scroll, "internal-resolution", "Internal Resolution", scaleValue, detail,
|
||||
std::move(scaleCycle));
|
||||
|
||||
add_int_cycle(scroll, "shadow-resolution", "Shadow Resolution",
|
||||
getSettings().game.shadowResolutionMultiplier, 1, kShadowResolutionMax,
|
||||
[](int v) { return fmt::format("x{}", v); });
|
||||
|
||||
add_toggle(scroll, "lock-aspect", "Force 4:3 Aspect Ratio",
|
||||
getSettings().video.lockAspectRatio, [](bool enabled) {
|
||||
AuroraSetViewportPolicy(enabled ? AURORA_VIEWPORT_FIT : AURORA_VIEWPORT_STRETCH);
|
||||
});
|
||||
|
||||
add_toggle(scroll, "vsync", "VSync", getSettings().video.enableVsync,
|
||||
[](bool enabled) { aurora_enable_vsync(enabled); });
|
||||
|
||||
add_toggle(scroll, "frame-interp", "Unlock Framerate",
|
||||
getSettings().game.enableFrameInterpolation, {}, "Experimental");
|
||||
|
||||
add_section_header(scroll, "Post-Processing");
|
||||
|
||||
add_bloom_mode_row(scroll);
|
||||
if (getSettings().game.bloomMode.getValue() != BloomMode::Off) {
|
||||
add_bloom_brightness_row(scroll);
|
||||
}
|
||||
|
||||
add_toggle(
|
||||
scroll, "depth-of-field", "Depth of Field", getSettings().game.enableDepthOfField);
|
||||
|
||||
add_section_header(scroll, "Developer Options");
|
||||
|
||||
const std::string lodValue = aurora::gx::enableLodBias ? "On" : "Off";
|
||||
auto lodMutate = [this] {
|
||||
aurora::gx::enableLodBias = !aurora::gx::enableLodBias;
|
||||
m_needsRebuild = true;
|
||||
};
|
||||
auto lodOption = std::make_unique<GameOption>(scroll, "lod-bias", "LOD Bias", lodValue,
|
||||
std::string_view{}, queue_activate("lod-bias"));
|
||||
register_row(
|
||||
Row{"lod-bias", lodMutate, [lodMutate](int) { lodMutate(); }}, std::move(lodOption));
|
||||
|
||||
add_toggle(
|
||||
scroll, "minimap-shadows", "Mini-Map Shadows", getSettings().game.enableMapBackground);
|
||||
}
|
||||
|
||||
void build_placeholder_tab(Rml::Element* body, std::string_view tabLabel) {
|
||||
auto* wrap = append(body, "div", {},
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column},
|
||||
{Rml::PropertyId::AlignItems, Rml::Style::AlignItems::Center},
|
||||
{Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::Center},
|
||||
{Rml::PropertyId::Width, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::FlexGrow, rml_number(1.0f)},
|
||||
{Rml::PropertyId::RowGap, rml_dp(12.0f)},
|
||||
{Rml::PropertyId::ColumnGap, rml_dp(12.0f)},
|
||||
});
|
||||
auto* heading = add_label(wrap, tabLabel, LabelStyle::Large);
|
||||
set_props(heading, {{Rml::PropertyId::TextAlign, Rml::Style::TextAlign::Center}});
|
||||
auto* sub = add_label(wrap, "Not yet ported.", LabelStyle::Body);
|
||||
set_props(sub, {
|
||||
{Rml::PropertyId::TextAlign, Rml::Style::TextAlign::Center},
|
||||
{Rml::PropertyId::Color, rml_color(theme::TextDim)},
|
||||
});
|
||||
}
|
||||
|
||||
void build_body() {
|
||||
m_window->set_right_pane_visible(m_tab == Tab::Graphics);
|
||||
Rml::Element* body = m_window->body();
|
||||
switch (m_tab) {
|
||||
case Tab::Graphics:
|
||||
build_graphics_tab(body);
|
||||
build_description_pane();
|
||||
break;
|
||||
default:
|
||||
build_placeholder_tab(body, kTabs[static_cast<size_t>(m_tab)].label);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void rebuild() {
|
||||
if (!m_open) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rml::Context* context = aurora::rmlui::get_context();
|
||||
if (context == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string preferredFocus =
|
||||
m_pendingFocusId.empty() ? current_focus_id() : m_pendingFocusId;
|
||||
m_pendingFocusId.clear();
|
||||
|
||||
close_document();
|
||||
|
||||
m_document = context->CreateDocument();
|
||||
if (m_document == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
style_document(m_document);
|
||||
Rml::Element* screen = add_screen();
|
||||
|
||||
m_window = std::make_unique<Window>(screen, "game-menu", [this] { request_close(); });
|
||||
|
||||
for (const TabDef& tab : kTabs) {
|
||||
const std::string tabId = tab.id;
|
||||
m_window->add_tab(tabId, tab.label, [this, tabId] { m_pendingTabId = tabId; });
|
||||
}
|
||||
m_window->set_selected_tab(kTabs[static_cast<size_t>(m_tab)].id);
|
||||
|
||||
build_body();
|
||||
|
||||
m_document->AddEventListener(Rml::EventId::Keydown, this);
|
||||
m_document->Show();
|
||||
|
||||
focus_id(preferredFocus.empty() ? first_focus_id() : preferredFocus);
|
||||
sync_description_pane();
|
||||
}
|
||||
|
||||
void request_close() { m_requestClose = true; }
|
||||
|
||||
void switch_tab(int direction) {
|
||||
const int count = kTabs.size();
|
||||
const int next = (static_cast<int>(m_tab) + direction + count) % count;
|
||||
m_pendingTabId = kTabs[static_cast<size_t>(next)].id;
|
||||
}
|
||||
|
||||
void apply_tab_selection(std::string_view tabId) {
|
||||
for (size_t i = 0; i < kTabs.size(); ++i) {
|
||||
if (tabId == kTabs[i].id) {
|
||||
if (m_tab == static_cast<Tab>(i)) {
|
||||
return;
|
||||
}
|
||||
m_tab = static_cast<Tab>(i);
|
||||
m_pendingFocusId.clear();
|
||||
rebuild();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string current_focus_id() const {
|
||||
if (Rml::Context* context = aurora::rmlui::get_context()) {
|
||||
if (Rml::Element* focused = context->GetFocusElement()) {
|
||||
return focused->GetId();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string first_focus_id() const {
|
||||
return m_focusIds.empty() ? std::string{} : m_focusIds.front();
|
||||
}
|
||||
|
||||
int focus_index() const {
|
||||
const std::string id = current_focus_id();
|
||||
if (id.empty()) {
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < static_cast<int>(m_focusIds.size()); ++i) {
|
||||
if (m_focusIds[i] == id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void focus_id(std::string_view id) {
|
||||
if (m_document == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (!id.empty()) {
|
||||
if (Rml::Element* element = m_document->GetElementById(std::string(id))) {
|
||||
element->Focus(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const std::string fallback = first_focus_id();
|
||||
if (!fallback.empty()) {
|
||||
if (Rml::Element* element = m_document->GetElementById(fallback)) {
|
||||
element->Focus(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void move_focus(int direction) {
|
||||
if (m_focusIds.empty()) {
|
||||
return;
|
||||
}
|
||||
const int index = focus_index();
|
||||
if (index < 0) {
|
||||
focus_id(m_focusIds.front());
|
||||
return;
|
||||
}
|
||||
const int next = index + direction;
|
||||
if (next < 0 || next >= static_cast<int>(m_focusIds.size())) {
|
||||
return;
|
||||
}
|
||||
focus_id(m_focusIds[static_cast<size_t>(next)]);
|
||||
}
|
||||
|
||||
void queue_activate_focused() {
|
||||
const std::string id = current_focus_id();
|
||||
if (!id.empty()) {
|
||||
m_requestedActivation = id;
|
||||
}
|
||||
}
|
||||
|
||||
void queue_cycle_focused(int direction) {
|
||||
const std::string id = current_focus_id();
|
||||
if (!id.empty()) {
|
||||
m_requestedCycleId = id;
|
||||
m_requestedCycleDirection = direction;
|
||||
}
|
||||
}
|
||||
|
||||
bool handle_key(Rml::Input::KeyIdentifier key) {
|
||||
switch (key) {
|
||||
case Rml::Input::KI_UP:
|
||||
move_focus(-1);
|
||||
return true;
|
||||
case Rml::Input::KI_DOWN:
|
||||
move_focus(1);
|
||||
return true;
|
||||
case Rml::Input::KI_LEFT:
|
||||
queue_cycle_focused(-1);
|
||||
return true;
|
||||
case Rml::Input::KI_RIGHT:
|
||||
queue_cycle_focused(1);
|
||||
return true;
|
||||
case Rml::Input::KI_F16:
|
||||
switch_tab(-1);
|
||||
return true;
|
||||
case Rml::Input::KI_F17:
|
||||
switch_tab(1);
|
||||
return true;
|
||||
case Rml::Input::KI_RETURN:
|
||||
queue_activate_focused();
|
||||
return true;
|
||||
case Rml::Input::KI_ESCAPE:
|
||||
case Rml::Input::KI_F15:
|
||||
request_close();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Screen s_screen;
|
||||
|
||||
} // namespace
|
||||
|
||||
bool initialize() {
|
||||
return s_screen.initialize();
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
s_screen.shutdown();
|
||||
}
|
||||
|
||||
bool is_active() {
|
||||
return s_screen.is_open();
|
||||
}
|
||||
|
||||
void toggle() {
|
||||
s_screen.toggle();
|
||||
}
|
||||
|
||||
void set_active(bool active) {
|
||||
s_screen.set_open(active);
|
||||
}
|
||||
|
||||
void handle_event(const SDL_Event& event) {
|
||||
s_screen.handle_event(event);
|
||||
}
|
||||
|
||||
void update() {
|
||||
s_screen.update();
|
||||
}
|
||||
|
||||
} // namespace dusk::ui::game_menu
|
||||
@@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_events.h>
|
||||
|
||||
namespace dusk::ui::game_menu {
|
||||
|
||||
bool initialize();
|
||||
void shutdown();
|
||||
|
||||
bool is_active();
|
||||
void toggle();
|
||||
void set_active(bool active);
|
||||
|
||||
void handle_event(const SDL_Event& event);
|
||||
void update();
|
||||
|
||||
} // namespace dusk::ui::game_menu
|
||||
@@ -1,201 +0,0 @@
|
||||
#include "game_option.hpp"
|
||||
|
||||
#include "control_surface.hpp"
|
||||
#include "element.hpp"
|
||||
#include "focus_border.hpp"
|
||||
#include "label.hpp"
|
||||
#include "theme.hpp"
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
GameOption::GameOption(Rml::Element* parent, std::string_view id, std::string_view title,
|
||||
std::string_view value, std::string_view detail, std::function<void()> pressedCallback)
|
||||
: m_pressedCallback(std::move(pressedCallback)) {
|
||||
using namespace theme;
|
||||
|
||||
m_element = append(parent, "button", id,
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::Position, Rml::Style::Position::Relative},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Row},
|
||||
{Rml::PropertyId::AlignItems, Rml::Style::AlignItems::Center},
|
||||
{Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::SpaceBetween},
|
||||
{Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox},
|
||||
{Rml::PropertyId::RowGap, rml_dp(16.0f)},
|
||||
{Rml::PropertyId::ColumnGap, rml_dp(16.0f)},
|
||||
{Rml::PropertyId::Width, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::PaddingTop, rml_dp(16.0f)},
|
||||
{Rml::PropertyId::PaddingRight, rml_dp(16.0f)},
|
||||
{Rml::PropertyId::PaddingBottom, rml_dp(16.0f)},
|
||||
{Rml::PropertyId::PaddingLeft, rml_dp(16.0f)},
|
||||
{Rml::PropertyId::BorderTopWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderRightWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderBottomWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderLeftWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderTopLeftRadius, rml_dp(BorderRadiusSmall)},
|
||||
{Rml::PropertyId::BorderTopRightRadius, rml_dp(BorderRadiusSmall)},
|
||||
{Rml::PropertyId::BorderBottomRightRadius, rml_dp(BorderRadiusSmall)},
|
||||
{Rml::PropertyId::BorderBottomLeftRadius, rml_dp(BorderRadiusSmall)},
|
||||
{Rml::PropertyId::BackgroundColor, rml_color(Transparent)},
|
||||
{Rml::PropertyId::BorderTopColor, rml_color(ElevatedBorder, 0)},
|
||||
{Rml::PropertyId::BorderRightColor, rml_color(ElevatedBorder, 0)},
|
||||
{Rml::PropertyId::BorderBottomColor, rml_color(ElevatedBorder, 0)},
|
||||
{Rml::PropertyId::BorderLeftColor, rml_color(ElevatedBorder, 0)},
|
||||
{Rml::PropertyId::Color, rml_color(TextDim)},
|
||||
{Rml::PropertyId::Cursor, rml_string("pointer")},
|
||||
{Rml::PropertyId::TabIndex, Rml::Style::TabIndex::Auto},
|
||||
{Rml::PropertyId::NavUp, Rml::Style::Nav::Auto},
|
||||
{Rml::PropertyId::NavDown, Rml::Style::Nav::Auto},
|
||||
{Rml::PropertyId::NavLeft, Rml::Style::Nav::Auto},
|
||||
{Rml::PropertyId::NavRight, Rml::Style::Nav::Auto},
|
||||
{Rml::PropertyId::Opacity, rml_number(1.0f)},
|
||||
{Rml::PropertyId::FontFamily, rml_string("Inter")},
|
||||
});
|
||||
|
||||
add_focus_border(m_element, BorderRadiusSmall);
|
||||
|
||||
auto* left = append(m_element, "div", {},
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column},
|
||||
{Rml::PropertyId::RowGap, rml_dp(4.0f)},
|
||||
{Rml::PropertyId::ColumnGap, rml_dp(4.0f)},
|
||||
{Rml::PropertyId::MinWidth, rml_px(0.0f)},
|
||||
{Rml::PropertyId::Width, rml_px(0.0f)},
|
||||
{Rml::PropertyId::FlexGrow, rml_number(1.0f)},
|
||||
{Rml::PropertyId::FlexShrink, rml_number(1.0f)},
|
||||
{Rml::PropertyId::PointerEvents, Rml::Style::PointerEvents::None},
|
||||
});
|
||||
|
||||
m_title = add_label(left, title, LabelStyle::Large);
|
||||
set_props(m_title, {
|
||||
{Rml::PropertyId::Color, rml_color(TextDim)},
|
||||
{Rml::PropertyId::FontSize, rml_dp(28.0f)},
|
||||
{Rml::PropertyId::LetterSpacing, rml_dp(1.0f)},
|
||||
});
|
||||
|
||||
if (!value.empty() || !detail.empty()) {
|
||||
auto* right = append(m_element, "div", {},
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column},
|
||||
{Rml::PropertyId::AlignItems, Rml::Style::AlignItems::FlexEnd},
|
||||
{Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::Center},
|
||||
{Rml::PropertyId::RowGap, rml_dp(4.0f)},
|
||||
{Rml::PropertyId::ColumnGap, rml_dp(4.0f)},
|
||||
{Rml::PropertyId::MinWidth, rml_dp(170.0f)},
|
||||
{Rml::PropertyId::MaxWidth, rml_percent(48.0f)},
|
||||
{Rml::PropertyId::FlexShrink, rml_number(0.0f)},
|
||||
{Rml::PropertyId::PointerEvents, Rml::Style::PointerEvents::None},
|
||||
});
|
||||
|
||||
if (!value.empty()) {
|
||||
m_value = add_label(right, value, LabelStyle::Body);
|
||||
set_props(
|
||||
m_value, {
|
||||
{Rml::PropertyId::Color, rml_color(TextDim)},
|
||||
{Rml::PropertyId::TextAlign, Rml::Style::TextAlign::Right},
|
||||
{Rml::PropertyId::OverflowX, Rml::Style::Overflow::Hidden},
|
||||
{Rml::PropertyId::OverflowY, Rml::Style::Overflow::Hidden},
|
||||
{Rml::PropertyId::TextOverflow, Rml::Style::TextOverflow::Ellipsis},
|
||||
{Rml::PropertyId::WhiteSpace, Rml::Style::WhiteSpace::Nowrap},
|
||||
});
|
||||
}
|
||||
|
||||
if (!detail.empty()) {
|
||||
m_detail = add_label(right, detail, LabelStyle::Annotation);
|
||||
set_props(m_detail, {
|
||||
{Rml::PropertyId::Color, rml_color(TextDim)},
|
||||
{Rml::PropertyId::TextAlign, Rml::Style::TextAlign::Right},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
m_element->AddEventListener(Rml::EventId::Click, this);
|
||||
m_element->AddEventListener(Rml::EventId::Focus, this);
|
||||
m_element->AddEventListener(Rml::EventId::Blur, this);
|
||||
m_element->AddEventListener(Rml::EventId::Mouseover, this);
|
||||
m_element->AddEventListener(Rml::EventId::Mouseout, this);
|
||||
apply_style();
|
||||
}
|
||||
|
||||
GameOption::~GameOption() {
|
||||
if (m_element == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_element->RemoveEventListener(Rml::EventId::Click, this);
|
||||
m_element->RemoveEventListener(Rml::EventId::Focus, this);
|
||||
m_element->RemoveEventListener(Rml::EventId::Blur, this);
|
||||
m_element->RemoveEventListener(Rml::EventId::Mouseover, this);
|
||||
m_element->RemoveEventListener(Rml::EventId::Mouseout, this);
|
||||
m_element = nullptr;
|
||||
}
|
||||
|
||||
void GameOption::ProcessEvent(Rml::Event& event) {
|
||||
switch (event.GetId()) {
|
||||
case Rml::EventId::Click:
|
||||
if (m_pressedCallback) {
|
||||
m_pressedCallback();
|
||||
}
|
||||
break;
|
||||
case Rml::EventId::Focus:
|
||||
m_focused = true;
|
||||
apply_style();
|
||||
break;
|
||||
case Rml::EventId::Blur:
|
||||
m_focused = false;
|
||||
apply_style();
|
||||
break;
|
||||
case Rml::EventId::Mouseover:
|
||||
m_hovered = true;
|
||||
apply_style();
|
||||
break;
|
||||
case Rml::EventId::Mouseout:
|
||||
m_hovered = false;
|
||||
apply_style();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Rml::Element* GameOption::element() const {
|
||||
return m_element;
|
||||
}
|
||||
|
||||
std::string GameOption::id() const {
|
||||
return m_element == nullptr ? std::string{} : m_element->GetId();
|
||||
}
|
||||
|
||||
void GameOption::set_value(std::string_view value) {
|
||||
set_text(m_value, value);
|
||||
}
|
||||
|
||||
void GameOption::apply_style() {
|
||||
if (m_element == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool active = m_hovered || m_focused;
|
||||
apply_control_surface_style(
|
||||
m_element, control_surface_style(ControlSurfaceTone::Quiet), active);
|
||||
m_element->SetProperty(
|
||||
Rml::PropertyId::Color, rml_color(active ? theme::TextActive : theme::TextDim));
|
||||
m_title->SetProperty(
|
||||
Rml::PropertyId::Color, rml_color(active ? theme::TextActive : theme::TextDim));
|
||||
if (m_value != nullptr) {
|
||||
m_value->SetProperty(
|
||||
Rml::PropertyId::Color, rml_color(active ? theme::TextActive : theme::TextDim));
|
||||
}
|
||||
if (m_detail != nullptr) {
|
||||
m_detail->SetProperty(Rml::PropertyId::Color, rml_color(theme::TextDim));
|
||||
}
|
||||
set_focus_border_visible(m_element, m_focused);
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -1,42 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <RmlUi/Core/EventListener.h>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace Rml {
|
||||
class Element;
|
||||
}
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
class GameOption : public Rml::EventListener {
|
||||
public:
|
||||
GameOption(Rml::Element* parent, std::string_view id, std::string_view title,
|
||||
std::string_view value, std::string_view detail, std::function<void()> pressedCallback);
|
||||
~GameOption() override;
|
||||
|
||||
GameOption(const GameOption&) = delete;
|
||||
GameOption& operator=(const GameOption&) = delete;
|
||||
|
||||
void ProcessEvent(Rml::Event& event) override;
|
||||
|
||||
Rml::Element* element() const;
|
||||
std::string id() const;
|
||||
void set_value(std::string_view value);
|
||||
|
||||
private:
|
||||
Rml::Element* m_element = nullptr;
|
||||
Rml::Element* m_title = nullptr;
|
||||
Rml::Element* m_value = nullptr;
|
||||
Rml::Element* m_detail = nullptr;
|
||||
std::function<void()> m_pressedCallback;
|
||||
bool m_hovered = false;
|
||||
bool m_focused = false;
|
||||
|
||||
void apply_style();
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -1,53 +0,0 @@
|
||||
#include "label.hpp"
|
||||
|
||||
#include "element.hpp"
|
||||
#include "theme.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
void apply_label_style(Rml::Element* element, LabelStyle style) {
|
||||
using namespace theme;
|
||||
|
||||
switch (style) {
|
||||
case LabelStyle::Annotation:
|
||||
set_props(element, {
|
||||
{Rml::PropertyId::FontSize, rml_dp(18.0f)},
|
||||
{Rml::PropertyId::LetterSpacing, rml_dp(2.0f)},
|
||||
{Rml::PropertyId::FontWeight, Rml::Style::FontWeight::Normal},
|
||||
{Rml::PropertyId::Color, rml_color(TextDim)},
|
||||
});
|
||||
break;
|
||||
case LabelStyle::Body:
|
||||
set_props(element, {
|
||||
{Rml::PropertyId::FontSize, rml_dp(20.0f)},
|
||||
{Rml::PropertyId::LetterSpacing, rml_px(0.0f)},
|
||||
{Rml::PropertyId::FontWeight, Rml::Style::FontWeight::Normal},
|
||||
{Rml::PropertyId::Color, rml_color(Text)},
|
||||
});
|
||||
break;
|
||||
case LabelStyle::Medium:
|
||||
set_props(element, {
|
||||
{Rml::PropertyId::FontSize, rml_dp(28.0f)},
|
||||
{Rml::PropertyId::LetterSpacing, rml_dp(3.0f)},
|
||||
{Rml::PropertyId::FontWeight, Rml::Style::FontWeight::Bold},
|
||||
{Rml::PropertyId::Color, rml_color(Text)},
|
||||
});
|
||||
break;
|
||||
case LabelStyle::Large:
|
||||
set_props(element, {
|
||||
{Rml::PropertyId::FontSize, rml_dp(36.0f)},
|
||||
{Rml::PropertyId::LetterSpacing, rml_dp(4.0f)},
|
||||
{Rml::PropertyId::FontWeight, Rml::Style::FontWeight::Bold},
|
||||
{Rml::PropertyId::Color, rml_color(Text)},
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Rml::Element* add_label(Rml::Element* parent, std::string_view text, LabelStyle style) {
|
||||
Rml::Element* label = append_text(parent, "div", text);
|
||||
apply_label_style(label, style);
|
||||
return label;
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <RmlUi/Core/Element.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
enum class LabelStyle {
|
||||
Annotation,
|
||||
Body,
|
||||
Medium,
|
||||
Large,
|
||||
};
|
||||
|
||||
Rml::Element* add_label(Rml::Element* parent, std::string_view text, LabelStyle style);
|
||||
void apply_label_style(Rml::Element* element, LabelStyle style);
|
||||
|
||||
} // namespace dusk::ui
|
||||
@@ -1,138 +0,0 @@
|
||||
#include "prelaunch_layout.hpp"
|
||||
|
||||
#include "element.hpp"
|
||||
#include "label.hpp"
|
||||
#include "theme.hpp"
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
|
||||
namespace dusk::ui::prelaunch::layout {
|
||||
|
||||
void style_document(Rml::ElementDocument* document) {
|
||||
using namespace theme;
|
||||
|
||||
set_props(document, {
|
||||
{Rml::PropertyId::Width, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::Height, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::MarginTop, rml_px(0.0f)},
|
||||
{Rml::PropertyId::MarginRight, rml_px(0.0f)},
|
||||
{Rml::PropertyId::MarginBottom, rml_px(0.0f)},
|
||||
{Rml::PropertyId::MarginLeft, rml_px(0.0f)},
|
||||
{Rml::PropertyId::PaddingTop, rml_px(0.0f)},
|
||||
{Rml::PropertyId::PaddingRight, rml_px(0.0f)},
|
||||
{Rml::PropertyId::PaddingBottom, rml_px(0.0f)},
|
||||
{Rml::PropertyId::PaddingLeft, rml_px(0.0f)},
|
||||
{Rml::PropertyId::FontFamily, rml_string("Inter")},
|
||||
{Rml::PropertyId::BackgroundColor, rml_color(Background1)},
|
||||
{Rml::PropertyId::Color, rml_color(Text)},
|
||||
});
|
||||
}
|
||||
|
||||
Rml::Element* add_screen(Rml::ElementDocument* document, ScreenLayout layout) {
|
||||
using namespace theme;
|
||||
|
||||
const bool compact = layout == ScreenLayout::CompactSplit;
|
||||
return append(document, "div", "prelaunch-screen",
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::Position, Rml::Style::Position::Absolute},
|
||||
{Rml::PropertyId::Left, rml_px(0.0f)},
|
||||
{Rml::PropertyId::Top, rml_px(0.0f)},
|
||||
{Rml::PropertyId::Right, rml_px(0.0f)},
|
||||
{Rml::PropertyId::Bottom, rml_px(0.0f)},
|
||||
{Rml::PropertyId::FlexDirection,
|
||||
compact ? Rml::Style::FlexDirection::Row : Rml::Style::FlexDirection::Column},
|
||||
{Rml::PropertyId::AlignItems, Rml::Style::AlignItems::Center},
|
||||
{Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::Center},
|
||||
{Rml::PropertyId::RowGap, rml_dp(compact ? 28.0f : 24.0f)},
|
||||
{Rml::PropertyId::ColumnGap, rml_dp(compact ? 28.0f : 24.0f)},
|
||||
{Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox},
|
||||
{Rml::PropertyId::PaddingTop, rml_dp(compact ? 24.0f : 48.0f)},
|
||||
{Rml::PropertyId::PaddingRight, rml_dp(compact ? 24.0f : 28.0f)},
|
||||
{Rml::PropertyId::PaddingBottom, rml_dp(compact ? 24.0f : 48.0f)},
|
||||
{Rml::PropertyId::PaddingLeft, rml_dp(compact ? 24.0f : 28.0f)},
|
||||
{Rml::PropertyId::BackgroundColor, rml_color(Background1)},
|
||||
});
|
||||
}
|
||||
|
||||
Rml::Element* add_brand(Rml::Element* parent, std::string_view logoPath, bool compact) {
|
||||
auto* brand = append(parent, "div", {},
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column},
|
||||
{Rml::PropertyId::AlignItems, Rml::Style::AlignItems::Center},
|
||||
{Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::Center},
|
||||
{Rml::PropertyId::RowGap, rml_dp(compact ? 8.0f : 12.0f)},
|
||||
{Rml::PropertyId::ColumnGap, rml_dp(compact ? 8.0f : 12.0f)},
|
||||
{Rml::PropertyId::Width, compact ? rml_dp(260.0f) : rml_percent(100.0f)},
|
||||
{Rml::PropertyId::MaxWidth, compact ? rml_percent(32.0f) : rml_dp(720.0f)},
|
||||
{Rml::PropertyId::FlexShrink, rml_number(compact ? 0.0f : 1.0f)},
|
||||
});
|
||||
|
||||
auto* subtitle = add_label(brand, "Twilit Realm presents", LabelStyle::Annotation);
|
||||
set_props(subtitle, {
|
||||
{Rml::PropertyId::TextAlign, Rml::Style::TextAlign::Center},
|
||||
{Rml::PropertyId::FontSize, rml_dp(compact ? 14.0f : 18.0f)},
|
||||
});
|
||||
|
||||
if (!logoPath.empty()) {
|
||||
auto* logo = append(brand, "img");
|
||||
logo->SetAttribute("src", std::string(logoPath));
|
||||
set_props(logo, {
|
||||
{Rml::PropertyId::Width, rml_dp(compact ? 220.0f : 360.0f)},
|
||||
{Rml::PropertyId::MaxWidth, rml_percent(compact ? 100.0f : 70.0f)},
|
||||
{Rml::PropertyId::Height, Rml::Style::Height::Auto},
|
||||
});
|
||||
} else {
|
||||
auto* title = add_label(brand, "Dusk", LabelStyle::Large);
|
||||
set_props(title, {
|
||||
{Rml::PropertyId::FontSize, rml_dp(compact ? 42.0f : 54.0f)},
|
||||
{Rml::PropertyId::LetterSpacing, rml_dp(compact ? 3.0f : 4.0f)},
|
||||
});
|
||||
}
|
||||
return brand;
|
||||
}
|
||||
|
||||
Rml::Element* add_heading(Rml::Element* parent, std::string_view title) {
|
||||
auto* heading = add_label(parent, title, LabelStyle::Large);
|
||||
set_props(heading, {
|
||||
{Rml::PropertyId::Width, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::MaxWidth, rml_dp(840.0f)},
|
||||
{Rml::PropertyId::TextAlign, Rml::Style::TextAlign::Left},
|
||||
});
|
||||
return heading;
|
||||
}
|
||||
|
||||
Rml::Element* add_panel(Rml::Element* parent, bool wide, bool compact) {
|
||||
using namespace theme;
|
||||
|
||||
return append(parent, "div", {},
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column},
|
||||
{Rml::PropertyId::RowGap, rml_dp(12.0f)},
|
||||
{Rml::PropertyId::ColumnGap, rml_dp(12.0f)},
|
||||
{Rml::PropertyId::Width, rml_dp(wide ? 840.0f : 520.0f)},
|
||||
{Rml::PropertyId::MaxWidth, rml_percent(compact ? 62.0f : 100.0f)},
|
||||
{Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox},
|
||||
{Rml::PropertyId::PaddingTop, rml_dp(compact ? 16.0f : 20.0f)},
|
||||
{Rml::PropertyId::PaddingRight, rml_dp(compact ? 16.0f : 20.0f)},
|
||||
{Rml::PropertyId::PaddingBottom, rml_dp(compact ? 16.0f : 20.0f)},
|
||||
{Rml::PropertyId::PaddingLeft, rml_dp(compact ? 16.0f : 20.0f)},
|
||||
{Rml::PropertyId::BorderTopWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderRightWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderBottomWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderLeftWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderTopLeftRadius, rml_dp(BorderRadiusMedium)},
|
||||
{Rml::PropertyId::BorderTopRightRadius, rml_dp(BorderRadiusMedium)},
|
||||
{Rml::PropertyId::BorderBottomRightRadius, rml_dp(BorderRadiusMedium)},
|
||||
{Rml::PropertyId::BorderBottomLeftRadius, rml_dp(BorderRadiusMedium)},
|
||||
{Rml::PropertyId::BorderTopColor, rml_color(ElevatedBorder)},
|
||||
{Rml::PropertyId::BorderRightColor, rml_color(ElevatedBorder)},
|
||||
{Rml::PropertyId::BorderBottomColor, rml_color(ElevatedBorder)},
|
||||
{Rml::PropertyId::BorderLeftColor, rml_color(ElevatedBorder)},
|
||||
{Rml::PropertyId::BackgroundColor, rml_color(ElevatedSoft)},
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace dusk::ui::prelaunch::layout
|
||||
@@ -1,22 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <RmlUi/Core/Element.h>
|
||||
#include <RmlUi/Core/ElementDocument.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace dusk::ui::prelaunch::layout {
|
||||
|
||||
enum class ScreenLayout {
|
||||
Standard,
|
||||
CompactSplit,
|
||||
};
|
||||
|
||||
void style_document(Rml::ElementDocument* document);
|
||||
Rml::Element* add_screen(
|
||||
Rml::ElementDocument* document, ScreenLayout layout = ScreenLayout::Standard);
|
||||
Rml::Element* add_brand(Rml::Element* parent, std::string_view logoPath, bool compact = false);
|
||||
Rml::Element* add_heading(Rml::Element* parent, std::string_view title);
|
||||
Rml::Element* add_panel(Rml::Element* parent, bool wide, bool compact = false);
|
||||
|
||||
} // namespace dusk::ui::prelaunch::layout
|
||||
@@ -1,933 +0,0 @@
|
||||
#include "prelaunch_screen.hpp"
|
||||
|
||||
#include "button.hpp"
|
||||
#include "disc_state.hpp"
|
||||
#include "game_option.hpp"
|
||||
#include "prelaunch_layout.hpp"
|
||||
#include "ui.hpp"
|
||||
|
||||
#include "../file_select.hpp"
|
||||
#include "../iso_validate.hpp"
|
||||
#include "dusk/config.hpp"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/settings.h"
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
#include <RmlUi/Core/ElementDocument.h>
|
||||
#include <SDL3/SDL_dialog.h>
|
||||
#include <SDL3/SDL_filesystem.h>
|
||||
#include <SDL3/SDL_keycode.h>
|
||||
#include <SDL3/SDL_misc.h>
|
||||
#include <aurora/aurora.h>
|
||||
#include <dolphin/card.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "aurora/lib/window.hpp"
|
||||
|
||||
#include <aurora/rmlui.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace dusk::ui::prelaunch {
|
||||
namespace {
|
||||
|
||||
enum class View {
|
||||
Main,
|
||||
Options,
|
||||
LanguageSelect,
|
||||
GraphicsBackendSelect,
|
||||
SaveFileTypeSelect,
|
||||
};
|
||||
|
||||
struct BackendChoice {
|
||||
AuroraBackend backend = BACKEND_AUTO;
|
||||
std::string id;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
constexpr std::array<const char*, 5> kLanguageNames = {
|
||||
"English",
|
||||
"German",
|
||||
"French",
|
||||
"Spanish",
|
||||
"Italian",
|
||||
};
|
||||
|
||||
constexpr std::array<SDL_DialogFileFilter, 2> kGameDiscFileFilters{{
|
||||
{"Game Disc Images", "iso;gcm;ciso;gcz;nfs;rvz;wbfs;wia;tgc"},
|
||||
{"All Files", "*"},
|
||||
}};
|
||||
|
||||
std::string iso_validation_error_message(iso::ValidationError code) {
|
||||
switch (code) {
|
||||
case iso::ValidationError::IOError:
|
||||
return "Unable to read selected disc image";
|
||||
case iso::ValidationError::InvalidImage:
|
||||
return "Unable to interpret selected file as a disc image";
|
||||
case iso::ValidationError::WrongGame:
|
||||
return "Disc is for a different game";
|
||||
case iso::ValidationError::WrongVersion:
|
||||
return "Disc is for an unsupported version. Only NTSC & PAL GameCube are "
|
||||
"supported at this time";
|
||||
case iso::ValidationError::ExecutableMismatch:
|
||||
return "Disc contains modified executable files";
|
||||
case iso::ValidationError::Success:
|
||||
return {};
|
||||
case iso::ValidationError::Unknown:
|
||||
default:
|
||||
return "Unknown disc validation error";
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
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 card_type_name(CARDFileType type) {
|
||||
switch (type) {
|
||||
case CARD_GCIFOLDER:
|
||||
return "GCI Folder";
|
||||
case CARD_RAWIMAGE:
|
||||
return "Card Image";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path resource_path(const char* filename) {
|
||||
const char* basePath = SDL_GetBasePath();
|
||||
if (basePath == nullptr) {
|
||||
return std::filesystem::path("res") / filename;
|
||||
}
|
||||
return std::filesystem::path(basePath) / "res" / filename;
|
||||
}
|
||||
|
||||
std::string display_path(std::string_view path) {
|
||||
const char* home = SDL_GetUserFolder(SDL_FOLDER_HOME);
|
||||
if (home == nullptr || home[0] == '\0') {
|
||||
home = std::getenv("HOME");
|
||||
}
|
||||
if (home == nullptr || home[0] == '\0') {
|
||||
return std::string(path);
|
||||
}
|
||||
|
||||
std::string homePath(home);
|
||||
while (homePath.size() > 1 && homePath.back() == '/') {
|
||||
homePath.pop_back();
|
||||
}
|
||||
|
||||
if (path == homePath) {
|
||||
return "~";
|
||||
}
|
||||
|
||||
if (path.size() > homePath.size() && path.substr(0, homePath.size()) == homePath &&
|
||||
path[homePath.size()] == '/')
|
||||
{
|
||||
return "~" + std::string(path.substr(homePath.size()));
|
||||
}
|
||||
|
||||
return std::string(path);
|
||||
}
|
||||
|
||||
std::vector<BackendChoice> backend_choices() {
|
||||
std::vector<BackendChoice> choices;
|
||||
choices.push_back({BACKEND_AUTO, std::string(backend_id(BACKEND_AUTO)),
|
||||
std::string(backend_name(BACKEND_AUTO))});
|
||||
|
||||
size_t backendCount = 0;
|
||||
const AuroraBackend* availableBackends = aurora_get_available_backends(&backendCount);
|
||||
for (size_t i = 0; i < backendCount; ++i) {
|
||||
const AuroraBackend backend = availableBackends[i];
|
||||
choices.push_back(
|
||||
{backend, std::string(backend_id(backend)), std::string(backend_name(backend))});
|
||||
}
|
||||
|
||||
return choices;
|
||||
}
|
||||
|
||||
class Screen : public Rml::EventListener {
|
||||
public:
|
||||
bool initialize() {
|
||||
if (m_initialized) {
|
||||
return true;
|
||||
}
|
||||
if (!ui::initialize()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_selectedIsoPath = getSettings().backend.isoPath.getValue();
|
||||
validate_selected_iso(false);
|
||||
m_initialGraphicsBackend = getSettings().backend.graphicsBackend.getValue();
|
||||
m_initialized = true;
|
||||
|
||||
if (is_selected_path_valid() && getSettings().backend.skipPreLaunchUI.getValue()) {
|
||||
IsGameLaunched = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
set_active(true);
|
||||
rebuild();
|
||||
if (m_document == nullptr) {
|
||||
shutdown();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
close_document();
|
||||
set_active(false);
|
||||
m_initialized = false;
|
||||
m_focusIds.clear();
|
||||
}
|
||||
|
||||
bool is_active() const { return m_initialized && ui::is_active(); }
|
||||
|
||||
void handle_event(const SDL_Event& event) { ui::handle_event(event); }
|
||||
|
||||
void update() {
|
||||
if (m_requestedBack) {
|
||||
m_requestedBack = false;
|
||||
back();
|
||||
if (!is_active()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_requestedActivation.empty()) {
|
||||
const std::string requestedActivation = m_requestedActivation;
|
||||
m_requestedActivation.clear();
|
||||
activate(requestedActivation);
|
||||
if (!is_active()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_requestedCycleDirection != 0) {
|
||||
const std::string requestedCycleId = m_requestedCycleId;
|
||||
const int requestedCycleDirection = m_requestedCycleDirection;
|
||||
m_requestedCycleId.clear();
|
||||
m_requestedCycleDirection = 0;
|
||||
cycle_option(requestedCycleId, requestedCycleDirection);
|
||||
if (!is_active()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_selected_path_valid() && getSettings().backend.skipPreLaunchUI.getValue()) {
|
||||
IsGameLaunched = true;
|
||||
shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
ui::update();
|
||||
rebuild_if_layout_changed();
|
||||
}
|
||||
|
||||
void ProcessEvent(Rml::Event& event) override {
|
||||
if (event.GetId() == Rml::EventId::Keydown) {
|
||||
const auto key = static_cast<Rml::Input::KeyIdentifier>(
|
||||
event.GetParameter<int>("key_identifier", Rml::Input::KI_UNKNOWN));
|
||||
if (handle_key(key)) {
|
||||
event.StopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_initialized = false;
|
||||
View m_view = View::Main;
|
||||
Rml::ElementDocument* m_document = nullptr;
|
||||
std::vector<std::string> m_focusIds;
|
||||
std::vector<std::unique_ptr<Button> > m_buttons;
|
||||
std::unique_ptr<DiscState> m_discState;
|
||||
std::vector<std::unique_ptr<GameOption> > m_options;
|
||||
std::string m_pendingFocusId;
|
||||
std::string m_requestedActivation;
|
||||
std::string m_requestedCycleId;
|
||||
int m_requestedCycleDirection = 0;
|
||||
bool m_requestedBack = false;
|
||||
std::string m_selectedIsoPath;
|
||||
std::string m_errorString;
|
||||
std::string m_initialGraphicsBackend;
|
||||
bool m_compactLayout = false;
|
||||
bool m_selectedIsoValid = false;
|
||||
bool m_isPal = false;
|
||||
|
||||
bool selected_path_exists() const {
|
||||
#if TARGET_ANDROID
|
||||
return !m_selectedIsoPath.empty();
|
||||
#else
|
||||
return !m_selectedIsoPath.empty() && SDL_GetPathInfo(m_selectedIsoPath.c_str(), nullptr);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool is_selected_path_valid() const { return m_selectedIsoValid; }
|
||||
|
||||
void validate_selected_iso(bool save_valid_path) {
|
||||
m_errorString.clear();
|
||||
m_selectedIsoValid = false;
|
||||
m_isPal = false;
|
||||
|
||||
if (m_selectedIsoPath.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selected_path_exists()) {
|
||||
m_errorString = "Selected disc image does not exist";
|
||||
return;
|
||||
}
|
||||
|
||||
const iso::ValidationError validationResult = iso::validate(m_selectedIsoPath.c_str());
|
||||
if (validationResult != iso::ValidationError::Success) {
|
||||
m_errorString = iso_validation_error_message(validationResult);
|
||||
return;
|
||||
}
|
||||
|
||||
m_selectedIsoValid = true;
|
||||
m_isPal = iso::isPal(m_selectedIsoPath.c_str());
|
||||
if (save_valid_path) {
|
||||
getSettings().backend.isoPath.setValue(m_selectedIsoPath);
|
||||
Save();
|
||||
}
|
||||
}
|
||||
|
||||
void close_document() {
|
||||
if (m_document == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_document->RemoveEventListener(Rml::EventId::Keydown, this);
|
||||
m_buttons.clear();
|
||||
m_discState.reset();
|
||||
m_options.clear();
|
||||
m_focusIds.clear();
|
||||
m_document->Close();
|
||||
m_document = nullptr;
|
||||
}
|
||||
|
||||
std::string logo_path() const {
|
||||
const auto logo_path = resource_path("logo-mascot.png");
|
||||
if (std::filesystem::exists(logo_path)) {
|
||||
return logo_path.string();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string selected_disc_text() const {
|
||||
if (!m_selectedIsoPath.empty()) {
|
||||
return display_path(m_selectedIsoPath);
|
||||
}
|
||||
|
||||
return "Select a disc...";
|
||||
}
|
||||
|
||||
std::string disc_status_text() const {
|
||||
if (!m_errorString.empty()) {
|
||||
return m_errorString;
|
||||
}
|
||||
if (is_selected_path_valid()) {
|
||||
return fmt::format("Disc region: {}", m_isPal ? "PAL" : "NTSC");
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool should_use_compact_layout() const {
|
||||
Rml::Context* context = aurora::rmlui::get_context();
|
||||
if (context == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Rml::Vector2i dimensions = context->GetDimensions();
|
||||
float dp_ratio = context->GetDensityIndependentPixelRatio();
|
||||
if (dp_ratio <= 0.0f) {
|
||||
dp_ratio = 1.0f;
|
||||
}
|
||||
|
||||
const float width = static_cast<float>(dimensions.x) / dp_ratio;
|
||||
const float height = static_cast<float>(dimensions.y) / dp_ratio;
|
||||
return height < 680.0f && width >= 720.0f;
|
||||
}
|
||||
|
||||
void rebuild_if_layout_changed() {
|
||||
const bool compactLayout = should_use_compact_layout();
|
||||
if (compactLayout == m_compactLayout) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Rml::Context* context = aurora::rmlui::get_context()) {
|
||||
if (Rml::Element* focused = context->GetFocusElement()) {
|
||||
m_pendingFocusId = focused->GetId();
|
||||
}
|
||||
}
|
||||
rebuild();
|
||||
}
|
||||
|
||||
void queue_activation(std::string id) { m_requestedActivation = std::move(id); }
|
||||
|
||||
void queue_back() { m_requestedBack = true; }
|
||||
|
||||
void queue_cycle(std::string id, int direction) {
|
||||
m_requestedCycleId = std::move(id);
|
||||
m_requestedCycleDirection = direction;
|
||||
}
|
||||
|
||||
void add_button_control(
|
||||
Rml::Element* parent, std::string_view id, std::string_view text, ButtonVariant variant) {
|
||||
const std::string idString(id);
|
||||
m_focusIds.push_back(idString);
|
||||
m_buttons.push_back(std::make_unique<Button>(
|
||||
parent, idString, text, variant, [this, idString] { queue_activation(idString); }));
|
||||
}
|
||||
|
||||
void add_option_control(Rml::Element* parent, std::string_view id, std::string_view title,
|
||||
std::string_view value, std::string_view detail) {
|
||||
const std::string idString(id);
|
||||
m_focusIds.push_back(idString);
|
||||
m_options.push_back(std::make_unique<GameOption>(parent, idString, title, value, detail,
|
||||
[this, idString] { queue_activation(idString); }));
|
||||
}
|
||||
|
||||
void add_disc_control(Rml::Element* parent) {
|
||||
const std::string idString("select-disc");
|
||||
m_focusIds.push_back(idString);
|
||||
m_discState =
|
||||
std::make_unique<DiscState>(parent, idString, selected_disc_text(), disc_status_text(),
|
||||
!m_errorString.empty(), [this, idString] { queue_activation(idString); });
|
||||
}
|
||||
|
||||
void build_main(Rml::Element* screen) {
|
||||
m_focusIds.clear();
|
||||
m_buttons.clear();
|
||||
m_discState.reset();
|
||||
m_options.clear();
|
||||
|
||||
layout::add_brand(screen, logo_path(), m_compactLayout);
|
||||
Rml::Element* panel = layout::add_panel(screen, false, m_compactLayout);
|
||||
add_disc_control(panel);
|
||||
if (is_selected_path_valid()) {
|
||||
add_button_control(panel, "start", "Start Game", ButtonVariant::Primary);
|
||||
}
|
||||
|
||||
add_button_control(panel, "options", "Options", ButtonVariant::Quiet);
|
||||
}
|
||||
|
||||
AuroraBackend configured_backend() const {
|
||||
AuroraBackend backend = BACKEND_AUTO;
|
||||
if (!try_parse_backend(getSettings().backend.graphicsBackend.getValue(), backend)) {
|
||||
backend = BACKEND_AUTO;
|
||||
}
|
||||
return backend;
|
||||
}
|
||||
|
||||
std::string configured_backend_id() const {
|
||||
return std::string(backend_id(configured_backend()));
|
||||
}
|
||||
|
||||
std::string first_options_focus_id() const { return m_isPal ? "language" : "graphics-backend"; }
|
||||
|
||||
void build_options(Rml::Element* screen) {
|
||||
m_focusIds.clear();
|
||||
m_buttons.clear();
|
||||
m_discState.reset();
|
||||
m_options.clear();
|
||||
|
||||
layout::add_heading(screen, "Options");
|
||||
Rml::Element* panel = layout::add_panel(screen, true);
|
||||
|
||||
if (m_isPal) {
|
||||
const auto selectedLanguage = getSettings().game.language.getValue();
|
||||
add_option_control(panel, "language", "Language",
|
||||
kLanguageNames[static_cast<u8>(selectedLanguage)], "");
|
||||
}
|
||||
|
||||
const AuroraBackend backend = configured_backend();
|
||||
const std::string restartDetail =
|
||||
getSettings().backend.graphicsBackend.getValue() != m_initialGraphicsBackend ?
|
||||
"Restart required" :
|
||||
"";
|
||||
add_option_control(
|
||||
panel, "graphics-backend", "Graphics Backend", backend_name(backend), restartDetail);
|
||||
|
||||
const auto fileType =
|
||||
static_cast<CARDFileType>(getSettings().backend.cardFileType.getValue());
|
||||
add_option_control(panel, "save-file-type", "Save File Type", card_type_name(fileType), "");
|
||||
|
||||
add_button_control(panel, "back", "Back", ButtonVariant::Quiet);
|
||||
}
|
||||
|
||||
void build_language_select(Rml::Element* screen) {
|
||||
m_focusIds.clear();
|
||||
m_buttons.clear();
|
||||
m_discState.reset();
|
||||
m_options.clear();
|
||||
|
||||
layout::add_heading(screen, "Language");
|
||||
Rml::Element* panel = layout::add_panel(screen, true);
|
||||
|
||||
const auto selectedLanguage = getSettings().game.language.getValue();
|
||||
for (size_t i = 0; i < kLanguageNames.size(); ++i) {
|
||||
const std::string id = fmt::format("language-{}", i);
|
||||
add_option_control(panel, id, kLanguageNames[i],
|
||||
i == static_cast<size_t>(selectedLanguage) ? "Current" : "", "");
|
||||
}
|
||||
|
||||
add_button_control(panel, "back", "Back", ButtonVariant::Quiet);
|
||||
}
|
||||
|
||||
void build_graphics_backend_select(Rml::Element* screen) {
|
||||
m_focusIds.clear();
|
||||
m_buttons.clear();
|
||||
m_discState.reset();
|
||||
m_options.clear();
|
||||
|
||||
layout::add_heading(screen, "Graphics Backend");
|
||||
Rml::Element* panel = layout::add_panel(screen, true);
|
||||
|
||||
const std::string currentBackendId = configured_backend_id();
|
||||
for (const BackendChoice& choice : backend_choices()) {
|
||||
const std::string id = "backend-" + choice.id;
|
||||
add_option_control(
|
||||
panel, id, choice.name, choice.id == currentBackendId ? "Current" : "", "");
|
||||
}
|
||||
|
||||
add_button_control(panel, "back", "Back", ButtonVariant::Quiet);
|
||||
}
|
||||
|
||||
void build_save_fileType_select(Rml::Element* screen) {
|
||||
m_focusIds.clear();
|
||||
m_buttons.clear();
|
||||
m_discState.reset();
|
||||
m_options.clear();
|
||||
|
||||
layout::add_heading(screen, "Save File Type");
|
||||
Rml::Element* panel = layout::add_panel(screen, true);
|
||||
|
||||
const auto fileType =
|
||||
static_cast<CARDFileType>(getSettings().backend.cardFileType.getValue());
|
||||
add_option_control(panel, "save-gci-folder", "GCI Folder",
|
||||
fileType == CARD_GCIFOLDER ? "Current" : "", "");
|
||||
add_option_control(
|
||||
panel, "save-card-image", "Card Image", fileType == CARD_RAWIMAGE ? "Current" : "", "");
|
||||
|
||||
add_button_control(panel, "back", "Back", ButtonVariant::Quiet);
|
||||
}
|
||||
|
||||
void rebuild() {
|
||||
Rml::Context* context = aurora::rmlui::get_context();
|
||||
if (context == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string preferredFocus = choose_focus_after_rebuild();
|
||||
close_document();
|
||||
|
||||
m_document = context->CreateDocument();
|
||||
if (m_document == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_compactLayout = should_use_compact_layout();
|
||||
const bool compactMainLayout = m_view == View::Main && m_compactLayout;
|
||||
|
||||
layout::style_document(m_document);
|
||||
Rml::Element* screen =
|
||||
layout::add_screen(m_document, compactMainLayout ? layout::ScreenLayout::CompactSplit :
|
||||
layout::ScreenLayout::Standard);
|
||||
if (m_view == View::Main) {
|
||||
build_main(screen);
|
||||
} else if (m_view == View::Options) {
|
||||
build_options(screen);
|
||||
} else if (m_view == View::LanguageSelect) {
|
||||
build_language_select(screen);
|
||||
} else if (m_view == View::GraphicsBackendSelect) {
|
||||
build_graphics_backend_select(screen);
|
||||
} else if (m_view == View::SaveFileTypeSelect) {
|
||||
build_save_fileType_select(screen);
|
||||
}
|
||||
|
||||
m_document->AddEventListener(Rml::EventId::Keydown, this);
|
||||
|
||||
m_document->Show();
|
||||
focus_id(preferredFocus.empty() ? first_focus_id() : preferredFocus);
|
||||
}
|
||||
|
||||
std::string choose_focus_after_rebuild() const {
|
||||
if (!m_pendingFocusId.empty()) {
|
||||
return m_pendingFocusId;
|
||||
}
|
||||
if (Rml::Context* context = aurora::rmlui::get_context()) {
|
||||
if (Rml::Element* focused = context->GetFocusElement()) {
|
||||
return focused->GetId();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string first_focus_id() const {
|
||||
return m_focusIds.empty() ? std::string{} : m_focusIds.front();
|
||||
}
|
||||
|
||||
int focus_index() const {
|
||||
Rml::Context* context = aurora::rmlui::get_context();
|
||||
if (context == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
const Rml::Element* focused = context->GetFocusElement();
|
||||
if (focused == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const std::string& id = focused->GetId();
|
||||
for (int i = 0; i < static_cast<int>(m_focusIds.size()); ++i) {
|
||||
if (m_focusIds[i] == id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void focus_id(std::string_view id) {
|
||||
if (m_document == nullptr || id.empty()) {
|
||||
return;
|
||||
}
|
||||
if (Rml::Element* element = m_document->GetElementById(std::string(id))) {
|
||||
element->Focus(true);
|
||||
m_pendingFocusId.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void move_focus(int direction) {
|
||||
if (m_focusIds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int index = focus_index();
|
||||
if (index < 0) {
|
||||
focus_id(m_focusIds.front());
|
||||
return;
|
||||
}
|
||||
|
||||
const int nextIndex = index + direction;
|
||||
if (nextIndex < 0 || nextIndex >= static_cast<int>(m_focusIds.size())) {
|
||||
return;
|
||||
}
|
||||
|
||||
focus_id(m_focusIds[static_cast<size_t>(nextIndex)]);
|
||||
}
|
||||
|
||||
bool handle_key(Rml::Input::KeyIdentifier key) {
|
||||
switch (key) {
|
||||
case Rml::Input::KI_UP:
|
||||
move_focus(-1);
|
||||
return true;
|
||||
case Rml::Input::KI_DOWN:
|
||||
move_focus(1);
|
||||
return true;
|
||||
case Rml::Input::KI_LEFT:
|
||||
queue_cycle_focused(-1);
|
||||
return true;
|
||||
case Rml::Input::KI_RIGHT:
|
||||
queue_cycle_focused(1);
|
||||
return true;
|
||||
case Rml::Input::KI_RETURN:
|
||||
queue_focused_activation();
|
||||
return true;
|
||||
case Rml::Input::KI_ESCAPE:
|
||||
case Rml::Input::KI_F15:
|
||||
queue_back();
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void queue_focused_activation() {
|
||||
Rml::Context* context = aurora::rmlui::get_context();
|
||||
if (context == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (Rml::Element* focused = context->GetFocusElement()) {
|
||||
queue_activation(focused->GetId());
|
||||
}
|
||||
}
|
||||
|
||||
void queue_cycle_focused(int direction) {
|
||||
Rml::Context* context = aurora::rmlui::get_context();
|
||||
if (context == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (Rml::Element* focused = context->GetFocusElement()) {
|
||||
queue_cycle(focused->GetId(), direction);
|
||||
}
|
||||
}
|
||||
|
||||
static void file_dialog_callback(void* userdata, const char* path, const char* error) {
|
||||
auto* self = static_cast<Screen*>(userdata);
|
||||
if (self == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (error != nullptr) {
|
||||
self->m_selectedIsoPath.clear();
|
||||
self->m_selectedIsoValid = false;
|
||||
self->m_isPal = false;
|
||||
self->m_errorString = fmt::format("File dialog error: {}", error);
|
||||
self->m_pendingFocusId = "select-disc";
|
||||
self->rebuild();
|
||||
return;
|
||||
}
|
||||
|
||||
if (path == nullptr) {
|
||||
self->m_pendingFocusId =
|
||||
self->m_view == View::Options ? self->first_options_focus_id() : "select-disc";
|
||||
self->rebuild();
|
||||
return;
|
||||
}
|
||||
|
||||
self->m_selectedIsoPath = path;
|
||||
self->validate_selected_iso(true);
|
||||
self->m_pendingFocusId =
|
||||
self->m_selectedIsoValid ?
|
||||
(self->m_view == View::Options ? self->first_options_focus_id() : "start") :
|
||||
(self->m_view == View::Options ? self->first_options_focus_id() : "select-disc");
|
||||
self->rebuild();
|
||||
}
|
||||
|
||||
void show_file_select() {
|
||||
ShowFileSelect(&file_dialog_callback, this, aurora::window::get_sdl_window(),
|
||||
kGameDiscFileFilters.data(), kGameDiscFileFilters.size(), nullptr, false);
|
||||
}
|
||||
|
||||
void activate(std::string_view id) {
|
||||
if (id == "start") {
|
||||
if (is_selected_path_valid()) {
|
||||
IsGameLaunched = true;
|
||||
shutdown();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (id == "select-disc") {
|
||||
show_file_select();
|
||||
return;
|
||||
}
|
||||
|
||||
if (id == "options") {
|
||||
m_view = View::Options;
|
||||
m_pendingFocusId = first_options_focus_id();
|
||||
rebuild();
|
||||
return;
|
||||
}
|
||||
|
||||
if (id == "language" && m_isPal) {
|
||||
m_view = View::LanguageSelect;
|
||||
const auto selectedLanguage = getSettings().game.language.getValue();
|
||||
m_pendingFocusId = fmt::format("language-{}", static_cast<int>(selectedLanguage));
|
||||
rebuild();
|
||||
return;
|
||||
}
|
||||
|
||||
if (id == "graphics-backend") {
|
||||
m_view = View::GraphicsBackendSelect;
|
||||
m_pendingFocusId = "backend-" + configured_backend_id();
|
||||
rebuild();
|
||||
return;
|
||||
}
|
||||
|
||||
if (id == "save-file-type") {
|
||||
const auto fileType =
|
||||
static_cast<CARDFileType>(getSettings().backend.cardFileType.getValue());
|
||||
m_view = View::SaveFileTypeSelect;
|
||||
m_pendingFocusId = fileType == CARD_GCIFOLDER ? "save-gci-folder" : "save-card-image";
|
||||
rebuild();
|
||||
return;
|
||||
}
|
||||
|
||||
if (id == "back") {
|
||||
back();
|
||||
return;
|
||||
}
|
||||
|
||||
select_option(id);
|
||||
}
|
||||
|
||||
void back() {
|
||||
if (m_view == View::Options) {
|
||||
m_view = View::Main;
|
||||
m_pendingFocusId = is_selected_path_valid() ? "start" : "select-disc";
|
||||
rebuild();
|
||||
} else if (m_view == View::LanguageSelect) {
|
||||
m_view = View::Options;
|
||||
m_pendingFocusId = "language";
|
||||
rebuild();
|
||||
} else if (m_view == View::GraphicsBackendSelect) {
|
||||
m_view = View::Options;
|
||||
m_pendingFocusId = "graphics-backend";
|
||||
rebuild();
|
||||
} else if (m_view == View::SaveFileTypeSelect) {
|
||||
m_view = View::Options;
|
||||
m_pendingFocusId = "save-file-type";
|
||||
rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
void cycle_option(std::string_view id, int direction) {
|
||||
if (m_view == View::LanguageSelect || m_view == View::GraphicsBackendSelect ||
|
||||
m_view == View::SaveFileTypeSelect)
|
||||
{
|
||||
move_focus(direction);
|
||||
}
|
||||
}
|
||||
|
||||
void select_option(std::string_view id) {
|
||||
const std::string idString(id);
|
||||
if (idString.rfind("language-", 0) == 0 && m_isPal) {
|
||||
const int languageIndex = std::stoi(idString.substr(std::string("language-").size()));
|
||||
getSettings().game.language.setValue(static_cast<GameLanguage>(languageIndex));
|
||||
Save();
|
||||
m_view = View::Options;
|
||||
m_pendingFocusId = "language";
|
||||
rebuild();
|
||||
return;
|
||||
}
|
||||
|
||||
if (idString.rfind("backend-", 0) == 0) {
|
||||
const std::string selectedBackend = idString.substr(std::string("backend-").size());
|
||||
AuroraBackend backend = BACKEND_AUTO;
|
||||
if (try_parse_backend(selectedBackend, backend)) {
|
||||
getSettings().backend.graphicsBackend.setValue(selectedBackend);
|
||||
Save();
|
||||
}
|
||||
m_view = View::Options;
|
||||
m_pendingFocusId = "graphics-backend";
|
||||
rebuild();
|
||||
return;
|
||||
}
|
||||
|
||||
if (id == "save-gci-folder" || id == "save-card-image") {
|
||||
getSettings().backend.cardFileType.setValue(
|
||||
id == "save-gci-folder" ? CARD_GCIFOLDER : CARD_RAWIMAGE);
|
||||
Save();
|
||||
m_view = View::Options;
|
||||
m_pendingFocusId = "save-file-type";
|
||||
rebuild();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Screen s_screen;
|
||||
|
||||
} // namespace
|
||||
|
||||
bool initialize() {
|
||||
return s_screen.initialize();
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
s_screen.shutdown();
|
||||
}
|
||||
|
||||
bool is_active() {
|
||||
return s_screen.is_active();
|
||||
}
|
||||
|
||||
void handle_event(const SDL_Event& event) {
|
||||
s_screen.handle_event(event);
|
||||
}
|
||||
|
||||
void update() {
|
||||
s_screen.update();
|
||||
}
|
||||
|
||||
} // namespace dusk::ui::prelaunch
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDL3/SDL_events.h>
|
||||
|
||||
namespace dusk::ui::prelaunch {
|
||||
|
||||
bool initialize();
|
||||
void shutdown();
|
||||
bool is_active();
|
||||
void handle_event(const SDL_Event& event);
|
||||
void update();
|
||||
|
||||
} // namespace dusk::ui::prelaunch
|
||||
@@ -1,3 +0,0 @@
|
||||
#include "theme.hpp"
|
||||
|
||||
namespace dusk::ui::theme {} // namespace dusk::ui::theme
|
||||
@@ -1,71 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <RmlUi/Core/Property.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace theme {
|
||||
|
||||
struct Color {
|
||||
int r = 255;
|
||||
int g = 255;
|
||||
int b = 255;
|
||||
int a = 255;
|
||||
};
|
||||
|
||||
inline constexpr Color Background1{12, 18, 17, 255};
|
||||
inline constexpr Color Text{225, 236, 231, 255};
|
||||
inline constexpr Color TextActive{248, 255, 251, 255};
|
||||
inline constexpr Color TextDim{160, 191, 182, 255};
|
||||
inline constexpr Color Primary{69, 184, 170, 255};
|
||||
inline constexpr Color PrimaryLight{135, 225, 211, 255};
|
||||
inline constexpr Color Secondary{184, 113, 5, 255};
|
||||
inline constexpr Color Elevated{160, 191, 182, 44};
|
||||
inline constexpr Color ElevatedSoft{47, 56, 55, 154};
|
||||
inline constexpr Color ElevatedBorder{160, 191, 182, 96};
|
||||
inline constexpr Color Transparent{0, 0, 0, 0};
|
||||
inline constexpr Color Danger{192, 30, 24, 255};
|
||||
|
||||
inline constexpr Color WindowSurface{21, 22, 16, 178};
|
||||
inline constexpr Color WindowTitleOverlay{217, 217, 217, 26};
|
||||
inline constexpr Color WindowDivider{146, 135, 91, 255};
|
||||
inline constexpr Color WindowAccent{194, 164, 45, 255};
|
||||
inline constexpr Color WindowAccentSoft{204, 184, 119, 255};
|
||||
inline constexpr Color WindowItemSurface{17, 16, 10, 128};
|
||||
inline constexpr Color WindowGlyph{224, 219, 200, 255};
|
||||
inline constexpr float WindowTabBarHeight = 66.0f;
|
||||
|
||||
inline constexpr float BorderRadiusSmall = 8.0f;
|
||||
inline constexpr float BorderRadiusMedium = 12.0f;
|
||||
inline constexpr float BorderWidth = 2.0f;
|
||||
|
||||
} // namespace theme
|
||||
|
||||
static Rml::Property rml_color(theme::Color color, int opacity = -1) {
|
||||
if (opacity >= 0) {
|
||||
color.a = std::clamp(opacity, 0, 255);
|
||||
}
|
||||
return Rml::Property(Rml::Colourb(color.r, color.g, color.b, color.a), Rml::Unit::COLOUR);
|
||||
}
|
||||
|
||||
static Rml::Property rml_dp(float value) {
|
||||
return Rml::Property(value, Rml::Unit::DP);
|
||||
}
|
||||
|
||||
static Rml::Property rml_percent(float value) {
|
||||
return Rml::Property(value, Rml::Unit::PERCENT);
|
||||
}
|
||||
|
||||
static Rml::Property rml_px(float value) {
|
||||
return Rml::Property(value, Rml::Unit::PX);
|
||||
}
|
||||
|
||||
static Rml::Property rml_number(float value) {
|
||||
return Rml::Property(value, Rml::Unit::NUMBER);
|
||||
}
|
||||
|
||||
static Rml::Property rml_string(Rml::String value) {
|
||||
return Rml::Property(std::move(value), Rml::Unit::STRING);
|
||||
}
|
||||
} // namespace dusk::ui
|
||||
+16
-204
@@ -1,109 +1,23 @@
|
||||
#include "ui.hpp"
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
#include <RmlUi_Platform_SDL.h>
|
||||
#include <SDL3/SDL_filesystem.h>
|
||||
|
||||
#include <aurora/rmlui.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
constexpr auto k_repeatStartDelay = std::chrono::milliseconds{500};
|
||||
constexpr auto k_repeatRate = std::chrono::milliseconds{55};
|
||||
constexpr float k_stickPressThreshold = 0.55f;
|
||||
constexpr float k_stickReleaseThreshold = 0.18f;
|
||||
|
||||
bool s_initialized = false;
|
||||
bool s_active = false;
|
||||
bool s_controllerPrimary = false;
|
||||
bool s_awaitStickReturnX = false;
|
||||
bool s_awaitStickReturnY = false;
|
||||
Rml::Input::KeyIdentifier sLatestControllerKey = Rml::Input::KI_UNKNOWN;
|
||||
Clock::time_point sNextRepeatTime = {};
|
||||
|
||||
std::filesystem::path resource_path(const char* filename) {
|
||||
const char* basePath = SDL_GetBasePath();
|
||||
if (basePath == nullptr) {
|
||||
return std::filesystem::path("res") / filename;
|
||||
}
|
||||
return std::filesystem::path(basePath) / "res" / filename;
|
||||
}
|
||||
|
||||
void load_font(const char* filename, bool fallback = false) {
|
||||
Rml::LoadFontFace(resource_path(filename).string(), fallback);
|
||||
}
|
||||
|
||||
Rml::Context* context() {
|
||||
return aurora::rmlui::get_context();
|
||||
}
|
||||
|
||||
bool send_key_down(Rml::Input::KeyIdentifier key) {
|
||||
if (key == Rml::Input::KI_UNKNOWN || context() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
context()->ProcessKeyDown(key, RmlSDL::GetKeyModifierState());
|
||||
return true;
|
||||
}
|
||||
|
||||
void send_controller_key(Rml::Input::KeyIdentifier key) {
|
||||
if (!send_key_down(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
s_controllerPrimary = true;
|
||||
sLatestControllerKey = key;
|
||||
sNextRepeatTime = Clock::now() + k_repeatStartDelay;
|
||||
}
|
||||
|
||||
Rml::Input::KeyIdentifier key_from_gamepad_button(Uint8 button) {
|
||||
switch (button) {
|
||||
case SDL_GAMEPAD_BUTTON_SOUTH:
|
||||
case SDL_GAMEPAD_BUTTON_START:
|
||||
return key_for_menu_action(MenuAction::Accept);
|
||||
case SDL_GAMEPAD_BUTTON_WEST:
|
||||
return key_for_menu_action(MenuAction::Back);
|
||||
case SDL_GAMEPAD_BUTTON_BACK:
|
||||
return key_for_menu_action(MenuAction::Toggle);
|
||||
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
|
||||
return key_for_menu_action(MenuAction::TabLeft);
|
||||
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
|
||||
return key_for_menu_action(MenuAction::TabRight);
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_UP:
|
||||
return Rml::Input::KI_UP;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
|
||||
return Rml::Input::KI_DOWN;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
|
||||
return Rml::Input::KI_LEFT;
|
||||
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
|
||||
return Rml::Input::KI_RIGHT;
|
||||
default:
|
||||
return Rml::Input::KI_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
Rml::Input::KeyIdentifier key_from_gamepad_axis(
|
||||
const SDL_GamepadAxisEvent& event, float axisValue) {
|
||||
switch (event.axis) {
|
||||
case SDL_GAMEPAD_AXIS_LEFTX:
|
||||
return axisValue < 0.0f ? Rml::Input::KI_LEFT : Rml::Input::KI_RIGHT;
|
||||
case SDL_GAMEPAD_AXIS_LEFTY:
|
||||
return axisValue < 0.0f ? Rml::Input::KI_UP : Rml::Input::KI_DOWN;
|
||||
default:
|
||||
return Rml::Input::KI_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool initialize() {
|
||||
static bool s_initialized = false;
|
||||
|
||||
bool initialize() noexcept {
|
||||
if (s_initialized) {
|
||||
return true;
|
||||
}
|
||||
@@ -111,134 +25,32 @@ bool initialize() {
|
||||
return false;
|
||||
}
|
||||
|
||||
load_font("Inter-Regular.ttf");
|
||||
load_font("Inter-Bold.ttf");
|
||||
load_font("NotoMono-Regular.ttf", true);
|
||||
load_font("FiraSans-Regular.ttf", true);
|
||||
load_font("FiraSansCondensed-Regular.ttf");
|
||||
load_font("FiraSansCondensed-Bold.ttf");
|
||||
|
||||
s_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void shutdown() {
|
||||
s_active = false;
|
||||
void shutdown() noexcept {
|
||||
s_initialized = false;
|
||||
sLatestControllerKey = Rml::Input::KI_UNKNOWN;
|
||||
}
|
||||
|
||||
bool is_active() {
|
||||
return s_active;
|
||||
void handle_event(const SDL_Event& event) noexcept {
|
||||
// TODO
|
||||
}
|
||||
|
||||
void set_active(bool active) {
|
||||
s_active = active;
|
||||
if (!active) {
|
||||
sLatestControllerKey = Rml::Input::KI_UNKNOWN;
|
||||
}
|
||||
void update() noexcept {
|
||||
// TODO
|
||||
}
|
||||
|
||||
Rml::Input::KeyIdentifier key_for_menu_action(MenuAction action) {
|
||||
switch (action) {
|
||||
case MenuAction::Accept:
|
||||
return Rml::Input::KI_RETURN;
|
||||
case MenuAction::Back:
|
||||
return Rml::Input::KI_F15;
|
||||
case MenuAction::Toggle:
|
||||
return Rml::Input::KI_ESCAPE;
|
||||
case MenuAction::TabLeft:
|
||||
return Rml::Input::KI_F16;
|
||||
case MenuAction::TabRight:
|
||||
return Rml::Input::KI_F17;
|
||||
case MenuAction::None:
|
||||
default:
|
||||
return Rml::Input::KI_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
MenuAction menu_action_from_key(Rml::Input::KeyIdentifier key) {
|
||||
if (key == key_for_menu_action(MenuAction::Accept)) {
|
||||
return MenuAction::Accept;
|
||||
}
|
||||
if (key == key_for_menu_action(MenuAction::Back)) {
|
||||
return MenuAction::Back;
|
||||
}
|
||||
if (key == key_for_menu_action(MenuAction::Toggle)) {
|
||||
return MenuAction::Toggle;
|
||||
}
|
||||
if (key == key_for_menu_action(MenuAction::TabLeft)) {
|
||||
return MenuAction::TabLeft;
|
||||
}
|
||||
if (key == key_for_menu_action(MenuAction::TabRight)) {
|
||||
return MenuAction::TabRight;
|
||||
}
|
||||
return MenuAction::None;
|
||||
}
|
||||
|
||||
void handle_event(const SDL_Event& event) {
|
||||
if (!s_active || context() == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
if (!s_controllerPrimary || event.motion.xrel != 0.0f || event.motion.yrel != 0.0f) {
|
||||
s_controllerPrimary = false;
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
case SDL_EVENT_MOUSE_WHEEL:
|
||||
s_controllerPrimary = false;
|
||||
break;
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
|
||||
send_controller_key(key_from_gamepad_button(event.gbutton.button));
|
||||
break;
|
||||
case SDL_EVENT_GAMEPAD_BUTTON_UP: {
|
||||
const Rml::Input::KeyIdentifier key = key_from_gamepad_button(event.gbutton.button);
|
||||
if (key == sLatestControllerKey) {
|
||||
sLatestControllerKey = Rml::Input::KI_UNKNOWN;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SDL_EVENT_GAMEPAD_AXIS_MOTION: {
|
||||
if (event.gaxis.axis != SDL_GAMEPAD_AXIS_LEFTX &&
|
||||
event.gaxis.axis != SDL_GAMEPAD_AXIS_LEFTY)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const float axisValue = static_cast<float>(event.gaxis.value) / 32768.0f;
|
||||
bool& awaitStickReturn =
|
||||
event.gaxis.axis == SDL_GAMEPAD_AXIS_LEFTX ? s_awaitStickReturnX : s_awaitStickReturnY;
|
||||
|
||||
if (std::abs(axisValue) > k_stickPressThreshold) {
|
||||
if (!awaitStickReturn) {
|
||||
awaitStickReturn = true;
|
||||
send_controller_key(key_from_gamepad_axis(event.gaxis, axisValue));
|
||||
}
|
||||
s_controllerPrimary = true;
|
||||
} else if (awaitStickReturn && std::abs(axisValue) < k_stickReleaseThreshold) {
|
||||
const Rml::Input::KeyIdentifier key = key_from_gamepad_axis(event.gaxis, axisValue);
|
||||
if (key == sLatestControllerKey) {
|
||||
sLatestControllerKey = Rml::Input::KI_UNKNOWN;
|
||||
}
|
||||
awaitStickReturn = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void update() {
|
||||
if (!s_active || sLatestControllerKey == Rml::Input::KI_UNKNOWN || context() == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto currentTime = Clock::now();
|
||||
if (currentTime >= sNextRepeatTime) {
|
||||
send_key_down(sLatestControllerKey);
|
||||
sNextRepeatTime += k_repeatRate;
|
||||
std::filesystem::path resource_path(const std::filesystem::path& filename) noexcept {
|
||||
const char* basePath = SDL_GetBasePath();
|
||||
if (basePath == nullptr) {
|
||||
return std::filesystem::path("res") / filename;
|
||||
}
|
||||
return std::filesystem::path(basePath) / "res" / filename;
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
|
||||
+7
-19
@@ -1,29 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <RmlUi/Core/Input.h>
|
||||
#include <SDL3/SDL_events.h>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace dusk::ui {
|
||||
|
||||
enum class MenuAction {
|
||||
None,
|
||||
Accept,
|
||||
Back,
|
||||
Toggle,
|
||||
TabLeft,
|
||||
TabRight,
|
||||
};
|
||||
bool initialize() noexcept;
|
||||
void shutdown() noexcept;
|
||||
|
||||
bool initialize();
|
||||
void shutdown();
|
||||
void handle_event(const SDL_Event& event) noexcept;
|
||||
void update() noexcept;
|
||||
|
||||
bool is_active();
|
||||
void set_active(bool active);
|
||||
|
||||
void handle_event(const SDL_Event& event);
|
||||
void update();
|
||||
|
||||
Rml::Input::KeyIdentifier key_for_menu_action(MenuAction action);
|
||||
MenuAction menu_action_from_key(Rml::Input::KeyIdentifier key);
|
||||
std::filesystem::path resource_path(const std::filesystem::path& filename) noexcept;
|
||||
|
||||
} // namespace dusk::ui
|
||||
|
||||
+232
-427
@@ -1,464 +1,269 @@
|
||||
#include "window.hpp"
|
||||
#include "element.hpp"
|
||||
#include "focus_border.hpp"
|
||||
#include "theme.hpp"
|
||||
|
||||
#include <RmlUi/Core.h>
|
||||
#include <RmlUi/Core/PropertyDictionary.h>
|
||||
#include <RmlUi/Core/StyleSheetSpecification.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include "aurora/rmlui.hpp"
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
|
||||
WindowTab::WindowTab(Rml::Element* parent, std::string_view id, std::string_view label,
|
||||
std::function<void()> selectedCallback)
|
||||
: m_selectedCallback(std::move(selectedCallback)) {
|
||||
using namespace theme;
|
||||
|
||||
m_element = append(parent, "button", id,
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::Position, Rml::Style::Position::Relative},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column},
|
||||
{Rml::PropertyId::AlignItems, Rml::Style::AlignItems::Center},
|
||||
{Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::Center},
|
||||
{Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox},
|
||||
{Rml::PropertyId::Height, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::PaddingLeft, rml_dp(20.0f)},
|
||||
{Rml::PropertyId::PaddingRight, rml_dp(20.0f)},
|
||||
{Rml::PropertyId::BackgroundColor, rml_color(Transparent)},
|
||||
{Rml::PropertyId::BorderTopWidth, rml_px(0.0f)},
|
||||
{Rml::PropertyId::BorderRightWidth, rml_px(0.0f)},
|
||||
{Rml::PropertyId::BorderBottomWidth, rml_px(0.0f)},
|
||||
{Rml::PropertyId::BorderLeftWidth, rml_px(0.0f)},
|
||||
{Rml::PropertyId::Cursor, rml_string("pointer")},
|
||||
{Rml::PropertyId::TabIndex, Rml::Style::TabIndex::Auto},
|
||||
{Rml::PropertyId::NavUp, Rml::Style::Nav::Auto},
|
||||
{Rml::PropertyId::NavDown, Rml::Style::Nav::Auto},
|
||||
{Rml::PropertyId::NavLeft, Rml::Style::Nav::Auto},
|
||||
{Rml::PropertyId::NavRight, Rml::Style::Nav::Auto},
|
||||
{Rml::PropertyId::FontFamily, rml_string("Inter")},
|
||||
});
|
||||
|
||||
add_focus_border(m_element, BorderRadiusSmall);
|
||||
|
||||
m_label = append(m_element, "span", {},
|
||||
{
|
||||
{Rml::PropertyId::PointerEvents, Rml::Style::PointerEvents::None},
|
||||
{Rml::PropertyId::FontSize, rml_dp(20.0f)},
|
||||
{Rml::PropertyId::LetterSpacing, rml_dp(1.0f)},
|
||||
{Rml::PropertyId::FontWeight, Rml::Style::FontWeight::Bold},
|
||||
{Rml::PropertyId::TextAlign, Rml::Style::TextAlign::Center},
|
||||
{Rml::PropertyId::Color, rml_color(Text)},
|
||||
});
|
||||
set_text(m_label, label);
|
||||
|
||||
m_indicator = append(m_element, "div", {},
|
||||
{
|
||||
{Rml::PropertyId::Position, Rml::Style::Position::Absolute},
|
||||
{Rml::PropertyId::Left, rml_px(0.0f)},
|
||||
{Rml::PropertyId::Right, rml_px(0.0f)},
|
||||
{Rml::PropertyId::Bottom, rml_dp(-BorderWidth)},
|
||||
{Rml::PropertyId::Height, rml_dp(2.0f)},
|
||||
{Rml::PropertyId::BackgroundColor, rml_color(WindowAccent, 0)},
|
||||
{Rml::PropertyId::PointerEvents, Rml::Style::PointerEvents::None},
|
||||
});
|
||||
|
||||
m_element->AddEventListener(Rml::EventId::Click, this);
|
||||
m_element->AddEventListener(Rml::EventId::Focus, this);
|
||||
m_element->AddEventListener(Rml::EventId::Blur, this);
|
||||
m_element->AddEventListener(Rml::EventId::Mouseover, this);
|
||||
m_element->AddEventListener(Rml::EventId::Mouseout, this);
|
||||
apply_style();
|
||||
}
|
||||
|
||||
WindowTab::~WindowTab() {
|
||||
if (m_element == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_element->RemoveEventListener(Rml::EventId::Click, this);
|
||||
m_element->RemoveEventListener(Rml::EventId::Focus, this);
|
||||
m_element->RemoveEventListener(Rml::EventId::Blur, this);
|
||||
m_element->RemoveEventListener(Rml::EventId::Mouseover, this);
|
||||
m_element->RemoveEventListener(Rml::EventId::Mouseout, this);
|
||||
m_element = nullptr;
|
||||
}
|
||||
|
||||
void WindowTab::ProcessEvent(Rml::Event& event) {
|
||||
switch (event.GetId()) {
|
||||
case Rml::EventId::Click:
|
||||
if (m_selectedCallback) {
|
||||
m_selectedCallback();
|
||||
const Rml::String kWindowDocumentRml = R"RML(
|
||||
<rml>
|
||||
<head>
|
||||
<title>Window</title>
|
||||
<style>
|
||||
*, *:before, *:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
break;
|
||||
case Rml::EventId::Focus:
|
||||
m_focused = true;
|
||||
apply_style();
|
||||
break;
|
||||
case Rml::EventId::Blur:
|
||||
m_focused = false;
|
||||
apply_style();
|
||||
break;
|
||||
case Rml::EventId::Mouseover:
|
||||
m_hovered = true;
|
||||
apply_style();
|
||||
break;
|
||||
case Rml::EventId::Mouseout:
|
||||
m_hovered = false;
|
||||
apply_style();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string WindowTab::id() const {
|
||||
return m_element == nullptr ? std::string{} : m_element->GetId();
|
||||
}
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 64dp;
|
||||
font-family: "Fira Sans";
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 15dp;
|
||||
color: #E0DBC8;
|
||||
}
|
||||
|
||||
void WindowTab::set_selected(bool selected) {
|
||||
if (m_selected == selected) {
|
||||
return;
|
||||
}
|
||||
m_selected = selected;
|
||||
apply_style();
|
||||
}
|
||||
.window {
|
||||
max-width: 1088dp;
|
||||
max-height: 768dp;
|
||||
margin: auto;
|
||||
border-radius: 14dp;
|
||||
overflow: hidden;
|
||||
border: 2dp #92875B;
|
||||
backdrop-filter: blur(5dp);
|
||||
box-shadow: 0 0 25dp 5dp;
|
||||
background-color: rgba(21, 22, 16, 90%);
|
||||
}
|
||||
|
||||
void WindowTab::apply_style() {
|
||||
using namespace theme;
|
||||
.window .tab-bar {
|
||||
display: flex;
|
||||
height: 64dp;
|
||||
background-color: rgba(217, 217, 217, 10%);
|
||||
font-family: "Fira Sans Condensed";
|
||||
font-weight: bold;
|
||||
font-size: 18dp;
|
||||
text-transform: uppercase;
|
||||
border-bottom: 2dp #92875B;
|
||||
}
|
||||
|
||||
if (m_element == nullptr) {
|
||||
return;
|
||||
.window .tab-bar .tab {
|
||||
padding: 0 24dp;
|
||||
line-height: 64dp;
|
||||
opacity: 0.25;
|
||||
tab-index: auto;
|
||||
nav: horizontal;
|
||||
focus: auto;
|
||||
}
|
||||
|
||||
.window .tab-bar .tab.active {
|
||||
opacity: 1;
|
||||
border-bottom: 4dp #C2A42D;
|
||||
font-effect: glow(0dp 4dp 0dp 4dp black);
|
||||
decorator: linear-gradient(to bottom, rgba(194, 164, 45, 0%) 0%, rgba(194, 164, 45, 15%) 100%);
|
||||
}
|
||||
|
||||
.window .tab-bar .tab:focus-visible {
|
||||
opacity: 1;
|
||||
font-effect: glow(0dp 4dp 0dp 4dp black);
|
||||
decorator: linear-gradient(to bottom, rgba(194, 164, 45, 0%) 0%, rgba(194, 164, 45, 15%) 100%);
|
||||
}
|
||||
|
||||
.window .content {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.window .content .pane {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
flex: 1 1 0;
|
||||
height: 100%;
|
||||
padding: 24dp;
|
||||
gap: 8dp;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.window .content .pane:not(:last-of-type) {
|
||||
border-right: 1dp #92875B;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
font-size: 22dp;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.button {
|
||||
text-align: center;
|
||||
background-color: rgba(17, 16, 10, 20%);
|
||||
opacity: 0.9;
|
||||
padding: 8dp 16dp;
|
||||
border-radius: 14dp;
|
||||
box-shadow: rgba(146, 135, 91, 25%) 0 0 0 1dp;
|
||||
font-size: 20dp;
|
||||
transition: background-color 0.1s linear-in-out, opacity 0.1s linear-in-out;
|
||||
}
|
||||
|
||||
.button.active, .button:hover {
|
||||
background-color: rgba(204, 184, 119, 20%);
|
||||
box-shadow: #C2A42D 0 0 0 2dp;
|
||||
}
|
||||
|
||||
.button.selected, .button:active {
|
||||
opacity: 1;
|
||||
background-color: rgba(204, 184, 119, 40%);
|
||||
box-shadow: #C2A42D 0 0 0 2dp;
|
||||
}
|
||||
|
||||
.select-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8dp;
|
||||
background-color: rgba(17, 16, 10, 20%);
|
||||
opacity: 0.9;
|
||||
padding: 8dp 16dp;
|
||||
border-radius: 14dp;
|
||||
box-shadow: rgba(146, 135, 91, 25%) 0 0 0 1dp;
|
||||
transition: background-color 0.1s linear-in-out, opacity 0.1s linear-in-out;
|
||||
}
|
||||
|
||||
.select-button.active, .select-button:hover {
|
||||
background-color: rgba(204, 184, 119, 20%);
|
||||
box-shadow: #C2A42D 0 0 0 2dp;
|
||||
}
|
||||
|
||||
.select-button.selected, .select-button:active {
|
||||
opacity: 1;
|
||||
background-color: rgba(204, 184, 119, 40%);
|
||||
box-shadow: #C2A42D 0 0 0 2dp;
|
||||
}
|
||||
|
||||
.select-button .key {
|
||||
font-family: "Fira Sans Condensed";
|
||||
font-weight: bold;
|
||||
font-size: 18dp;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.select-button .value {
|
||||
margin-left: auto;
|
||||
font-size: 20dp;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body data-model="window">
|
||||
<div class="window">
|
||||
<div class="tab-bar">
|
||||
<button class="tab"
|
||||
data-for="tab, i : tabs"
|
||||
data-class-active="i == active_tab"
|
||||
data-event-click="set_active_tab(i)">
|
||||
{{ tab.label }}
|
||||
</button>
|
||||
</div>
|
||||
<div id="content" class="content"></div>
|
||||
</div>
|
||||
</body>
|
||||
</rml>
|
||||
)RML";
|
||||
|
||||
bool setup_window_model(Rml::Context* context, WindowModel& model, Rml::DataModelHandle& handle) {
|
||||
Rml::DataModelConstructor constructor = context->CreateDataModel("window");
|
||||
if (!constructor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool active = m_hovered || m_focused;
|
||||
|
||||
int textOpacity;
|
||||
if (m_selected) {
|
||||
textOpacity = 255;
|
||||
} else if (active) {
|
||||
textOpacity = 200;
|
||||
if (auto tab_handle = constructor.RegisterStruct<WindowTab>()) {
|
||||
tab_handle.RegisterMember("label", &WindowTab::label);
|
||||
} else {
|
||||
textOpacity = 110;
|
||||
}
|
||||
const Color textColor = m_selected ? TextActive : Text;
|
||||
m_label->SetProperty(Rml::PropertyId::Color, rml_color(textColor, textOpacity));
|
||||
|
||||
if (m_indicator != nullptr) {
|
||||
const int indicatorOpacity = m_selected ? 255 : (active ? 96 : 0);
|
||||
m_indicator->SetProperty(
|
||||
Rml::PropertyId::BackgroundColor, rml_color(WindowAccent, indicatorOpacity));
|
||||
return false;
|
||||
}
|
||||
|
||||
set_focus_border_visible(m_element, m_focused);
|
||||
if (!constructor.RegisterArray<std::vector<WindowTab> >()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
constructor.Bind("active_tab", &model.activeTab);
|
||||
constructor.Bind("tabs", &model.tabs);
|
||||
constructor.BindEventCallback("set_active_tab", &WindowModel::set_active_tab, &model);
|
||||
|
||||
handle = constructor.GetModelHandle();
|
||||
return true;
|
||||
}
|
||||
|
||||
Window::Window(Rml::Element* parent, std::string_view id, std::function<void()> closeCallback)
|
||||
: m_closeCallback(std::move(closeCallback)) {
|
||||
using namespace theme;
|
||||
} // namespace
|
||||
|
||||
m_element = append(parent, "div", id,
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column},
|
||||
{Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox},
|
||||
{Rml::PropertyId::Width, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::MaxWidth, rml_dp(1088.0f)},
|
||||
{Rml::PropertyId::BorderTopWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderRightWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderBottomWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderLeftWidth, rml_dp(BorderWidth)},
|
||||
{Rml::PropertyId::BorderTopLeftRadius, rml_dp(BorderRadiusMedium)},
|
||||
{Rml::PropertyId::BorderTopRightRadius, rml_dp(BorderRadiusMedium)},
|
||||
{Rml::PropertyId::BorderBottomRightRadius, rml_dp(BorderRadiusMedium)},
|
||||
{Rml::PropertyId::BorderBottomLeftRadius, rml_dp(BorderRadiusMedium)},
|
||||
{Rml::PropertyId::BorderTopColor, rml_color(ElevatedBorder)},
|
||||
{Rml::PropertyId::BorderRightColor, rml_color(ElevatedBorder)},
|
||||
{Rml::PropertyId::BorderBottomColor, rml_color(ElevatedBorder)},
|
||||
{Rml::PropertyId::BorderLeftColor, rml_color(ElevatedBorder)},
|
||||
{Rml::PropertyId::BackgroundColor, rml_color(WindowSurface)},
|
||||
{Rml::PropertyId::OverflowX, Rml::Style::Overflow::Hidden},
|
||||
{Rml::PropertyId::OverflowY, Rml::Style::Overflow::Hidden},
|
||||
});
|
||||
set_props(m_element, {
|
||||
{"backdrop-filter", "blur(5dp)"},
|
||||
{"box-shadow", "0 0 25dp 5dp"},
|
||||
});
|
||||
void WindowModel::set_active_tab(
|
||||
Rml::DataModelHandle model, Rml::Event& event, const Rml::VariantList& arguments) {
|
||||
if (arguments.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_tabBar = append(m_element, "div", {},
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::Position, Rml::Style::Position::Relative},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Row},
|
||||
{Rml::PropertyId::AlignItems, Rml::Style::AlignItems::Center},
|
||||
{Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox},
|
||||
{Rml::PropertyId::Width, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::Height, rml_dp(WindowTabBarHeight)},
|
||||
{Rml::PropertyId::MinHeight, rml_dp(WindowTabBarHeight)},
|
||||
{Rml::PropertyId::PaddingLeft, rml_dp(12.0f)},
|
||||
{Rml::PropertyId::PaddingRight, rml_dp(12.0f)},
|
||||
{Rml::PropertyId::RowGap, rml_dp(4.0f)},
|
||||
{Rml::PropertyId::ColumnGap, rml_dp(4.0f)},
|
||||
{Rml::PropertyId::BackgroundColor, rml_color(WindowTitleOverlay)},
|
||||
{Rml::PropertyId::BorderBottomWidth, rml_dp(BorderWidth * 1.5f)},
|
||||
{Rml::PropertyId::BorderBottomColor, rml_color(WindowDivider)},
|
||||
});
|
||||
const int tabIndex = arguments[0].Get<int>();
|
||||
if (tabIndex < 0 || tabIndex >= static_cast<int>(tabs.size()) || tabIndex == activeTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_tabStrip = append(m_tabBar, "div", {},
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Row},
|
||||
{Rml::PropertyId::AlignItems, Rml::Style::AlignItems::Stretch},
|
||||
{Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::FlexStart},
|
||||
{Rml::PropertyId::FlexGrow, rml_number(1.0f)},
|
||||
{Rml::PropertyId::FlexShrink, rml_number(1.0f)},
|
||||
{Rml::PropertyId::MinWidth, rml_px(0.0f)},
|
||||
{Rml::PropertyId::Height, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::RowGap, rml_dp(4.0f)},
|
||||
{Rml::PropertyId::ColumnGap, rml_dp(4.0f)},
|
||||
});
|
||||
activeTab = tabIndex;
|
||||
model.DirtyVariable("active_tab");
|
||||
|
||||
const std::string closeId = id.empty() ? std::string{} : std::string(id) + "-close";
|
||||
m_closeButton = append(m_tabBar, "button", closeId,
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::Position, Rml::Style::Position::Relative},
|
||||
{Rml::PropertyId::AlignItems, Rml::Style::AlignItems::Center},
|
||||
{Rml::PropertyId::JustifyContent, Rml::Style::JustifyContent::Center},
|
||||
{Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox},
|
||||
{Rml::PropertyId::Width, rml_dp(36.0f)},
|
||||
{Rml::PropertyId::Height, rml_dp(36.0f)},
|
||||
{Rml::PropertyId::FlexShrink, rml_number(0.0f)},
|
||||
{Rml::PropertyId::BorderTopWidth, rml_px(0.0f)},
|
||||
{Rml::PropertyId::BorderRightWidth, rml_px(0.0f)},
|
||||
{Rml::PropertyId::BorderBottomWidth, rml_px(0.0f)},
|
||||
{Rml::PropertyId::BorderLeftWidth, rml_px(0.0f)},
|
||||
{Rml::PropertyId::BorderTopLeftRadius, rml_dp(BorderRadiusSmall)},
|
||||
{Rml::PropertyId::BorderTopRightRadius, rml_dp(BorderRadiusSmall)},
|
||||
{Rml::PropertyId::BorderBottomRightRadius, rml_dp(BorderRadiusSmall)},
|
||||
{Rml::PropertyId::BorderBottomLeftRadius, rml_dp(BorderRadiusSmall)},
|
||||
{Rml::PropertyId::BackgroundColor, rml_color(Transparent)},
|
||||
{Rml::PropertyId::Cursor, rml_string("pointer")},
|
||||
{Rml::PropertyId::TabIndex, Rml::Style::TabIndex::Auto},
|
||||
{Rml::PropertyId::FontFamily, rml_string("Inter")},
|
||||
});
|
||||
add_focus_border(m_closeButton, BorderRadiusSmall);
|
||||
// Replace window content with new tab content
|
||||
auto* currentElem = event.GetCurrentElement();
|
||||
if (currentElem == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto* doc = currentElem->GetOwnerDocument();
|
||||
if (doc == nullptr) {
|
||||
return;
|
||||
}
|
||||
auto* content = doc->GetElementById("content");
|
||||
if (content == nullptr) {
|
||||
return;
|
||||
}
|
||||
while (content->GetNumChildren() > 0) {
|
||||
content->RemoveChild(content->GetFirstChild());
|
||||
}
|
||||
if (tabs[tabIndex].setContent) {
|
||||
tabs[tabIndex].setContent(content);
|
||||
}
|
||||
}
|
||||
|
||||
auto* closeGlyph = append(m_closeButton, "span", {},
|
||||
{
|
||||
{Rml::PropertyId::FontSize, rml_dp(22.0f)},
|
||||
{Rml::PropertyId::FontWeight, Rml::Style::FontWeight::Normal},
|
||||
{Rml::PropertyId::Color, rml_color(WindowGlyph)},
|
||||
{Rml::PropertyId::PointerEvents, Rml::Style::PointerEvents::None},
|
||||
});
|
||||
set_text(closeGlyph, "\xc3\x97");
|
||||
|
||||
m_closeButton->AddEventListener(Rml::EventId::Click, this);
|
||||
m_closeButton->AddEventListener(Rml::EventId::Focus, this);
|
||||
m_closeButton->AddEventListener(Rml::EventId::Blur, this);
|
||||
m_closeButton->AddEventListener(Rml::EventId::Mouseover, this);
|
||||
m_closeButton->AddEventListener(Rml::EventId::Mouseout, this);
|
||||
|
||||
m_contentRow = append(m_element, "div", {},
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Row},
|
||||
{Rml::PropertyId::AlignItems, Rml::Style::AlignItems::Stretch},
|
||||
{Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox},
|
||||
{Rml::PropertyId::Width, rml_percent(100.0f)},
|
||||
{Rml::PropertyId::FlexGrow, rml_number(1.0f)},
|
||||
{Rml::PropertyId::FlexShrink, rml_number(1.0f)},
|
||||
{Rml::PropertyId::MinHeight, rml_px(0.0f)},
|
||||
{Rml::PropertyId::MinWidth, rml_px(0.0f)},
|
||||
{Rml::PropertyId::RowGap, rml_dp(20.0f)},
|
||||
{Rml::PropertyId::ColumnGap, rml_dp(20.0f)},
|
||||
});
|
||||
|
||||
m_leftPane = append(m_contentRow, "div", {},
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column},
|
||||
{Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox},
|
||||
{Rml::PropertyId::MinWidth, rml_px(0.0f)},
|
||||
{Rml::PropertyId::MinHeight, rml_px(0.0f)},
|
||||
{Rml::PropertyId::PaddingTop, rml_dp(24.0f)},
|
||||
{Rml::PropertyId::PaddingRight, rml_dp(24.0f)},
|
||||
{Rml::PropertyId::PaddingBottom, rml_dp(24.0f)},
|
||||
{Rml::PropertyId::PaddingLeft, rml_dp(24.0f)},
|
||||
{Rml::PropertyId::RowGap, rml_dp(12.0f)},
|
||||
{Rml::PropertyId::ColumnGap, rml_dp(12.0f)},
|
||||
});
|
||||
|
||||
m_rightPane = append(m_contentRow, "div", {},
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::None},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column},
|
||||
{Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox},
|
||||
{Rml::PropertyId::MinWidth, rml_px(0.0f)},
|
||||
{Rml::PropertyId::MinHeight, rml_px(0.0f)},
|
||||
{Rml::PropertyId::PaddingTop, rml_dp(24.0f)},
|
||||
{Rml::PropertyId::PaddingBottom, rml_dp(24.0f)},
|
||||
{Rml::PropertyId::PaddingRight, rml_dp(24.0f)},
|
||||
{Rml::PropertyId::PaddingLeft, rml_dp(8.0f)},
|
||||
{Rml::PropertyId::AlignItems, Rml::Style::AlignItems::FlexStart},
|
||||
{Rml::PropertyId::OverflowY, Rml::Style::Overflow::Auto},
|
||||
});
|
||||
|
||||
m_rightPane = append(m_contentRow, "div", {},
|
||||
{
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::None},
|
||||
{Rml::PropertyId::FlexDirection, Rml::Style::FlexDirection::Column},
|
||||
{Rml::PropertyId::BoxSizing, Rml::Style::BoxSizing::BorderBox},
|
||||
{Rml::PropertyId::MinWidth, rml_px(0.0f)},
|
||||
{Rml::PropertyId::MinHeight, rml_px(0.0f)},
|
||||
{Rml::PropertyId::PaddingTop, rml_dp(24.0f)},
|
||||
{Rml::PropertyId::PaddingBottom, rml_dp(24.0f)},
|
||||
{Rml::PropertyId::PaddingRight, rml_dp(24.0f)},
|
||||
{Rml::PropertyId::PaddingLeft, rml_dp(8.0f)},
|
||||
{Rml::PropertyId::AlignItems, Rml::Style::AlignItems::FlexStart},
|
||||
{Rml::PropertyId::OverflowY, Rml::Style::Overflow::Auto},
|
||||
});
|
||||
|
||||
apply_pane_layout();
|
||||
apply_close_style();
|
||||
Window::Window(WindowModel model) : mModel(std::move(model)) {
|
||||
auto* context = aurora::rmlui::get_context();
|
||||
if (context == nullptr) {
|
||||
return;
|
||||
}
|
||||
setup_window_model(context, mModel, mModelHandle);
|
||||
mDocument = context->LoadDocumentFromMemory(kWindowDocumentRml);
|
||||
if (mDocument == nullptr) {
|
||||
return;
|
||||
}
|
||||
mModel.tabs[0].setContent(mDocument->GetElementById("content"));
|
||||
}
|
||||
|
||||
Window::~Window() {
|
||||
m_tabs.clear();
|
||||
if (m_closeButton != nullptr) {
|
||||
m_closeButton->RemoveEventListener(Rml::EventId::Click, this);
|
||||
m_closeButton->RemoveEventListener(Rml::EventId::Focus, this);
|
||||
m_closeButton->RemoveEventListener(Rml::EventId::Blur, this);
|
||||
m_closeButton->RemoveEventListener(Rml::EventId::Mouseover, this);
|
||||
m_closeButton->RemoveEventListener(Rml::EventId::Mouseout, this);
|
||||
m_closeButton = nullptr;
|
||||
}
|
||||
m_element = nullptr;
|
||||
}
|
||||
|
||||
void Window::ProcessEvent(Rml::Event& event) {
|
||||
if (event.GetTargetElement() != m_closeButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.GetId()) {
|
||||
case Rml::EventId::Click:
|
||||
if (m_closeCallback) {
|
||||
m_closeCallback();
|
||||
}
|
||||
break;
|
||||
case Rml::EventId::Focus:
|
||||
m_closeFocused = true;
|
||||
apply_close_style();
|
||||
break;
|
||||
case Rml::EventId::Blur:
|
||||
m_closeFocused = false;
|
||||
apply_close_style();
|
||||
break;
|
||||
case Rml::EventId::Mouseover:
|
||||
m_closeHovered = true;
|
||||
apply_close_style();
|
||||
break;
|
||||
case Rml::EventId::Mouseout:
|
||||
m_closeHovered = false;
|
||||
apply_close_style();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
auto* context = aurora::rmlui::get_context();
|
||||
if (context != nullptr && mDocument != nullptr) {
|
||||
context->UnloadDocument(mDocument);
|
||||
mDocument = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
WindowTab* Window::add_tab(
|
||||
std::string_view id, std::string_view label, std::function<void()> selectedCallback) {
|
||||
if (m_tabStrip == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::string idString(id);
|
||||
auto wrapped = [this, idString, cb = std::move(selectedCallback)]() {
|
||||
set_selected_tab(idString);
|
||||
if (cb) {
|
||||
cb();
|
||||
}
|
||||
};
|
||||
auto tab = std::make_unique<WindowTab>(m_tabStrip, idString, label, std::move(wrapped));
|
||||
WindowTab* raw = tab.get();
|
||||
m_tabs.push_back(std::move(tab));
|
||||
return raw;
|
||||
}
|
||||
|
||||
void Window::set_selected_tab(std::string_view id) {
|
||||
for (auto& tab : m_tabs) {
|
||||
tab->set_selected(tab->id() == id);
|
||||
void Window::show() {
|
||||
if (mDocument != nullptr) {
|
||||
mDocument->Show();
|
||||
}
|
||||
}
|
||||
|
||||
std::string Window::selected_tab_id() const {
|
||||
for (const auto& tab : m_tabs) {
|
||||
if (tab->is_selected()) {
|
||||
return tab->id();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void Window::set_right_pane_visible(bool visible) {
|
||||
if (m_rightPaneVisible == visible) {
|
||||
return;
|
||||
}
|
||||
m_rightPaneVisible = visible;
|
||||
apply_pane_layout();
|
||||
}
|
||||
|
||||
void Window::apply_pane_layout() {
|
||||
using namespace theme;
|
||||
|
||||
if (m_leftPane == nullptr || m_rightPane == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_rightPaneVisible) {
|
||||
set_props(m_leftPane, {
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::FlexGrow, rml_number(1.0f)},
|
||||
{Rml::PropertyId::FlexShrink, rml_number(1.0f)},
|
||||
{Rml::PropertyId::FlexBasis, rml_px(0.0f)},
|
||||
{Rml::PropertyId::MinWidth, rml_px(0.0f)},
|
||||
});
|
||||
set_props(m_rightPane, {
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::FlexGrow, rml_number(0.0f)},
|
||||
{Rml::PropertyId::FlexShrink, rml_number(0.0f)},
|
||||
{Rml::PropertyId::FlexBasis, rml_percent(40.0f)},
|
||||
{Rml::PropertyId::MinWidth, rml_px(0.0f)},
|
||||
});
|
||||
} else {
|
||||
set_props(
|
||||
m_leftPane, {
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::Flex},
|
||||
{Rml::PropertyId::FlexGrow, rml_number(1.0f)},
|
||||
{Rml::PropertyId::FlexShrink, rml_number(1.0f)},
|
||||
{Rml::PropertyId::FlexBasis, Rml::Style::LengthPercentageAuto::Auto},
|
||||
{Rml::PropertyId::Width, rml_percent(100.0f)},
|
||||
});
|
||||
set_props(m_rightPane, {
|
||||
{Rml::PropertyId::Display, Rml::Style::Display::None},
|
||||
});
|
||||
void Window::hide() {
|
||||
if (mDocument != nullptr) {
|
||||
mDocument->Hide();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::apply_close_style() {
|
||||
using namespace theme;
|
||||
|
||||
if (m_closeButton == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool active = m_closeHovered || m_closeFocused;
|
||||
m_closeButton->SetProperty(Rml::PropertyId::BackgroundColor,
|
||||
active ? rml_color(WindowAccent, 56) : rml_color(Transparent));
|
||||
set_focus_border_visible(m_closeButton, m_closeFocused);
|
||||
}
|
||||
} // namespace dusk::ui
|
||||
|
||||
+23
-72
@@ -1,84 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <RmlUi/Core/EventListener.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace Rml {
|
||||
class Element;
|
||||
}
|
||||
#include <RmlUi/Core/DataModelHandle.h>
|
||||
#include <RmlUi/Core/ElementDocument.h>
|
||||
|
||||
namespace dusk::ui {
|
||||
class WindowTab : public Rml::EventListener {
|
||||
public:
|
||||
WindowTab(Rml::Element* parent, std::string_view id, std::string_view label,
|
||||
std::function<void()> selectedCallback);
|
||||
~WindowTab() override;
|
||||
|
||||
WindowTab(const WindowTab&) = delete;
|
||||
WindowTab& operator=(const WindowTab&) = delete;
|
||||
|
||||
void ProcessEvent(Rml::Event& event) override;
|
||||
|
||||
Rml::Element* element() const { return m_element; }
|
||||
std::string id() const;
|
||||
|
||||
void set_selected(bool selected);
|
||||
bool is_selected() const { return m_selected; }
|
||||
|
||||
private:
|
||||
Rml::Element* m_element = nullptr;
|
||||
Rml::Element* m_label = nullptr;
|
||||
Rml::Element* m_indicator = nullptr;
|
||||
std::function<void()> m_selectedCallback;
|
||||
bool m_hovered = false;
|
||||
bool m_focused = false;
|
||||
bool m_selected = false;
|
||||
|
||||
void apply_style();
|
||||
struct WindowTab {
|
||||
Rml::String label;
|
||||
std::function<void(Rml::Element*)> setContent;
|
||||
};
|
||||
|
||||
class Window : public Rml::EventListener {
|
||||
struct WindowModel {
|
||||
int activeTab = 0;
|
||||
std::vector<WindowTab> tabs;
|
||||
|
||||
void set_active_tab(
|
||||
Rml::DataModelHandle model, Rml::Event& event, const Rml::VariantList& arguments);
|
||||
};
|
||||
|
||||
class Window {
|
||||
public:
|
||||
Window(Rml::Element* parent, std::string_view id, std::function<void()> closeCallback = {});
|
||||
~Window() override;
|
||||
Window(WindowModel model);
|
||||
~Window();
|
||||
|
||||
Window(const Window&) = delete;
|
||||
Window& operator=(const Window&) = delete;
|
||||
|
||||
void ProcessEvent(Rml::Event& event) override;
|
||||
|
||||
Rml::Element* element() const { return m_element; }
|
||||
Rml::Element* body() const { return m_leftPane; }
|
||||
Rml::Element* right_pane() const { return m_rightPane; }
|
||||
void set_right_pane_visible(bool visible);
|
||||
|
||||
Rml::Element* tab_strip() const { return m_tabStrip; }
|
||||
|
||||
WindowTab* add_tab(
|
||||
std::string_view id, std::string_view label, std::function<void()> selectedCallback);
|
||||
void set_selected_tab(std::string_view id);
|
||||
std::string selected_tab_id() const;
|
||||
void show();
|
||||
void hide();
|
||||
|
||||
private:
|
||||
Rml::Element* m_element = nullptr;
|
||||
Rml::Element* m_tabBar = nullptr;
|
||||
Rml::Element* m_tabStrip = nullptr;
|
||||
Rml::Element* m_closeButton = nullptr;
|
||||
Rml::Element* m_contentRow = nullptr;
|
||||
Rml::Element* m_leftPane = nullptr;
|
||||
Rml::Element* m_rightPane = nullptr;
|
||||
bool m_rightPaneVisible = false;
|
||||
std::function<void()> m_closeCallback;
|
||||
std::vector<std::unique_ptr<WindowTab> > m_tabs;
|
||||
bool m_closeHovered = false;
|
||||
bool m_closeFocused = false;
|
||||
|
||||
void apply_close_style();
|
||||
void apply_pane_layout();
|
||||
WindowModel mModel;
|
||||
Rml::DataModelHandle mModelHandle;
|
||||
Rml::ElementDocument* mDocument;
|
||||
};
|
||||
} // namespace dusk::ui
|
||||
|
||||
} // namespace dusk::ui
|
||||
+28
-19
@@ -56,8 +56,8 @@
|
||||
#include "dusk/logging.h"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/imgui/ImGuiConsole.hpp"
|
||||
#include "dusk/ui/game_menu.hpp"
|
||||
#include "dusk/ui/prelaunch_screen.hpp"
|
||||
#include "dusk/ui/ui.hpp"
|
||||
#include "dusk/ui/editor.hpp"
|
||||
#include "version.h"
|
||||
|
||||
#include <aurora/aurora.h>
|
||||
@@ -77,6 +77,7 @@
|
||||
#include "tracy/Tracy.hpp"
|
||||
#include "f_pc/f_pc_draw.h"
|
||||
#include "tracy/Tracy.hpp"
|
||||
#include <RmlUi/Core.h>
|
||||
|
||||
// --- GLOBALS ---
|
||||
s8 mDoMain::developmentMode = -1;
|
||||
@@ -135,23 +136,18 @@ AuroraStats dusk::lastFrameAuroraStats;
|
||||
float dusk::frameUsagePct = 0.0f;
|
||||
|
||||
bool launchUILoop() {
|
||||
const bool useRmlPrelaunch = dusk::ui::prelaunch::initialize();
|
||||
|
||||
while (dusk::IsRunning && !dusk::IsGameLaunched) {
|
||||
const AuroraEvent* event = aurora_update();
|
||||
while (event != nullptr && event->type != AURORA_NONE) {
|
||||
switch (event->type) {
|
||||
case AURORA_SDL_EVENT:
|
||||
if (useRmlPrelaunch) {
|
||||
dusk::ui::prelaunch::handle_event(event->sdl);
|
||||
}
|
||||
// dusk::g_imguiConsole.HandleSDLEvent(event->sdl);
|
||||
dusk::ui::handle_event(event->sdl);
|
||||
dusk::g_imguiConsole.HandleSDLEvent(event->sdl);
|
||||
break;
|
||||
case AURORA_DISPLAY_SCALE_CHANGED:
|
||||
// dusk::ImGuiEngine_Initialize(event->windowSize.scale);
|
||||
dusk::ImGuiEngine_Initialize(event->windowSize.scale);
|
||||
break;
|
||||
case AURORA_EXIT:
|
||||
dusk::ui::prelaunch::shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -163,15 +159,13 @@ bool launchUILoop() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (useRmlPrelaunch) {
|
||||
dusk::ui::prelaunch::update();
|
||||
}
|
||||
dusk::g_imguiConsole.PreDraw();
|
||||
|
||||
dusk::g_imguiConsole.PostDraw();
|
||||
|
||||
aurora_end_frame();
|
||||
}
|
||||
|
||||
dusk::ui::prelaunch::shutdown();
|
||||
|
||||
return dusk::IsRunning;
|
||||
}
|
||||
|
||||
@@ -214,7 +208,6 @@ void main01(void) {
|
||||
OSReport("Entering Main Loop (main01)...\n");
|
||||
|
||||
dusk::game_clock::ensure_initialized();
|
||||
dusk::ui::game_menu::initialize();
|
||||
|
||||
do {
|
||||
// 1. Update Window Events
|
||||
@@ -224,7 +217,8 @@ void main01(void) {
|
||||
case AURORA_NONE:
|
||||
goto eventsDone;
|
||||
case AURORA_SDL_EVENT:
|
||||
dusk::ui::game_menu::handle_event(event->sdl);
|
||||
dusk::ui::handle_event(event->sdl);
|
||||
dusk::g_imguiConsole.HandleSDLEvent(event->sdl);
|
||||
if (event->sdl.type == SDL_EVENT_WINDOW_FOCUS_LOST &&
|
||||
dusk::getSettings().game.pauseOnFocusLost) {
|
||||
dusk::IsFocusPaused = true;
|
||||
@@ -236,6 +230,9 @@ void main01(void) {
|
||||
dusk::game_clock::reset_frame_timer();
|
||||
}
|
||||
break;
|
||||
case AURORA_DISPLAY_SCALE_CHANGED:
|
||||
dusk::ImGuiEngine_Initialize(event->windowSize.scale);
|
||||
break;
|
||||
case AURORA_EXIT:
|
||||
goto exit;
|
||||
}
|
||||
@@ -260,7 +257,7 @@ void main01(void) {
|
||||
|
||||
mDoGph_gInf_c::updateRenderSize();
|
||||
|
||||
dusk::ui::game_menu::update();
|
||||
dusk::ui::update();
|
||||
|
||||
const auto pacing = dusk::game_clock::advance_main_loop();
|
||||
if (pacing.is_interpolating) {
|
||||
@@ -313,7 +310,7 @@ void main01(void) {
|
||||
} while (dusk::IsRunning);
|
||||
|
||||
exit:;
|
||||
dusk::ui::game_menu::shutdown();
|
||||
dusk::ui::shutdown();
|
||||
}
|
||||
|
||||
static bool IsBackendAvailable(AuroraBackend backend) {
|
||||
@@ -359,6 +356,11 @@ static AuroraBackend ResolveDesiredBackend(const cxxopts::ParseResult& parsedArg
|
||||
return desiredBackend;
|
||||
}
|
||||
|
||||
static void aurora_imgui_init_callback(const AuroraWindowSize* size) {
|
||||
dusk::ImGuiEngine_Initialize(size->scale);
|
||||
dusk::ImGuiEngine_AddTextures();
|
||||
}
|
||||
|
||||
static void ApplyCVarOverrides(const cxxopts::OptionValue& option) {
|
||||
if (option.count() == 0) {
|
||||
return;
|
||||
@@ -559,6 +561,7 @@ int game_main(int argc, char* argv[]) {
|
||||
config.mem1Size = 256 * 1024 * 1024;
|
||||
config.mem2Size = 24 * 1024 * 1024;
|
||||
config.allowJoystickBackgroundEvents = true;
|
||||
config.imGuiInitCallback = &aurora_imgui_init_callback;
|
||||
config.allowTextureReplacements = true;
|
||||
config.allowTextureDumps = false;
|
||||
auroraInfo = aurora_initialize(argc, argv, &config);
|
||||
@@ -581,6 +584,11 @@ int game_main(int argc, char* argv[]) {
|
||||
|
||||
dusk::audio::SetMasterVolume(dusk::getSettings().audio.masterVolume / 100.0f);
|
||||
dusk::audio::SetEnableReverb(dusk::getSettings().audio.enableReverb);
|
||||
dusk::ui::initialize();
|
||||
|
||||
// TODO: just for testing
|
||||
dusk::ui::EditorWindow editorWindow;
|
||||
editorWindow.show();
|
||||
|
||||
std::string dvd_path;
|
||||
bool dvd_opened = false;
|
||||
@@ -659,6 +667,7 @@ int game_main(int argc, char* argv[]) {
|
||||
#ifdef DUSK_DISCORD_RPC
|
||||
dusk::discord::Shutdown();
|
||||
#endif
|
||||
dusk::ui::shutdown();
|
||||
aurora_shutdown();
|
||||
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user