UI: Add controller input

This commit is contained in:
Luke Street
2026-05-01 21:24:12 -06:00
parent 4c09d8b910
commit 2e4c2b5b46
14 changed files with 707 additions and 49 deletions
+2
View File
@@ -1474,6 +1474,8 @@ set(DUSK_FILES
src/dusk/ui/editor.hpp
src/dusk/ui/event.cpp
src/dusk/ui/event.hpp
src/dusk/ui/input.cpp
src/dusk/ui/input.hpp
src/dusk/ui/nav_types.hpp
src/dusk/ui/number_button.cpp
src/dusk/ui/number_button.hpp
+22
View File
@@ -17,6 +17,17 @@ Rml::ElementDocument* load_document(const Rml::String& source) {
} // namespace
Document::Document(const Rml::String& source) : mDocument(load_document(source)) {
// Block keydown events while hidden (except for Menu)
listen(
Rml::EventId::Keydown,
[this](Rml::Event& event) {
const auto cmd = map_nav_event(event);
if (cmd != NavCommand::Menu && !visible()) {
event.StopPropagation();
}
},
true);
listen(Rml::EventId::Keydown, [this](Rml::Event& event) {
const auto cmd = map_nav_event(event);
if (cmd != NavCommand::None && handle_nav_command(event, cmd)) {
@@ -76,7 +87,18 @@ bool Document::can_destroy() const {
return *mDocument->GetProperty(Rml::PropertyId::Visibility) == Rml::Style::Visibility::Hidden;
}
bool Document::visible() const {
if (mDocument == nullptr) {
return false;
}
return *mDocument->GetProperty(Rml::PropertyId::Visibility) == Rml::Style::Visibility::Visible;
}
bool Document::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (cmd == NavCommand::Menu) {
toggle_top_document();
return true;
}
return false;
}
+2 -1
View File
@@ -17,6 +17,7 @@ public:
virtual void hide();
virtual void update();
virtual bool focus();
virtual bool visible() const;
void listen(Rml::Element* element, Rml::EventId event, ScopedEventListener::Callback callback,
bool capture = false);
@@ -33,4 +34,4 @@ protected:
std::vector<std::unique_ptr<ScopedEventListener> > mListeners;
};
} // namespace dusk::ui
} // namespace dusk::ui
+610
View File
@@ -0,0 +1,610 @@
#include "input.hpp"
#include "ui.hpp"
#include <RmlUi/Core.h>
#include <SDL3/SDL_gamepad.h>
#include <SDL3/SDL_timer.h>
#include <aurora/rmlui.hpp>
#include <dolphin/pad.h>
#include <algorithm>
#include <array>
namespace dusk::ui {
namespace {
constexpr double kGamepadRepeatInitialDelay = 0.32;
constexpr double kGamepadRepeatStartInterval = 0.12;
constexpr double kGamepadRepeatMinInterval = 0.045;
constexpr double kGamepadRepeatRampDuration = 1.0;
constexpr double kGamepadMenuChordGraceDuration = 0.12;
constexpr Sint16 kGamepadAxisPressThreshold = 16384;
constexpr Sint16 kGamepadAxisReleaseThreshold = 12000;
constexpr int kGamepadAxisDirectionCount = SDL_GAMEPAD_AXIS_COUNT * 2;
struct GamepadRepeatState {
Rml::Input::KeyIdentifier key = Rml::Input::KI_UNKNOWN;
double pressedAt = 0.0;
double nextRepeatAt = 0.0;
bool held = false;
bool repeatable = false;
bool pending = false;
};
bool sPadInputBlocked = false;
std::array<GamepadRepeatState, SDL_GAMEPAD_BUTTON_COUNT> sGamepadButtonRepeats;
std::array<GamepadRepeatState, kGamepadAxisDirectionCount> sGamepadAxisRepeats;
std::array<u32, PAD_MAX_CONTROLLERS> sPadHoldMasks;
std::array<bool, PAD_MAX_CONTROLLERS> sMenuChordConsumed;
double now_seconds() noexcept {
return static_cast<double>(SDL_GetTicksNS()) / 1000000000.0;
}
bool is_menu_chord_part(PADButton button) noexcept {
return button == PAD_TRIGGER_R || button == PAD_BUTTON_START;
}
bool has_menu_chord_part_held(u32 port) noexcept {
if (port >= sPadHoldMasks.size()) {
return false;
}
const u32 held = sPadHoldMasks[port];
return (held & (PAD_TRIGGER_R | PAD_BUTTON_START)) != 0;
}
bool should_block_pad_for_menu_chord() noexcept {
for (u32 port = 0; port < sPadHoldMasks.size(); ++port) {
if (sMenuChordConsumed[port] && has_menu_chord_part_held(port)) {
return true;
}
}
return false;
}
PADButton pad_button_from_axis(PADAxis axis) noexcept {
switch (axis) {
case PAD_AXIS_TRIGGER_R:
return PAD_TRIGGER_R;
case PAD_AXIS_TRIGGER_L:
return PAD_TRIGGER_L;
default:
return 0;
}
}
void set_pad_button_held(u32 port, PADButton button, bool held) noexcept {
if (port >= sPadHoldMasks.size() || button == 0) {
return;
}
if (held) {
sPadHoldMasks[port] |= button;
} else {
sPadHoldMasks[port] &= ~button;
}
}
bool is_menu_chord(u32 port) noexcept {
if (port >= sPadHoldMasks.size()) {
return false;
}
const u32 held = sPadHoldMasks[port];
return (held & PAD_TRIGGER_R) != 0 && (held & PAD_BUTTON_START) != 0;
}
bool any_menu_chord() noexcept {
return std::any_of(sPadHoldMasks.begin(), sPadHoldMasks.end(),
[](u32 held) { return (held & PAD_TRIGGER_R) != 0 && (held & PAD_BUTTON_START) != 0; });
}
Rml::Input::KeyIdentifier map_pad_button(PADButton button) noexcept {
switch (button) {
case PAD_BUTTON_UP:
return Rml::Input::KI_UP;
case PAD_BUTTON_DOWN:
return Rml::Input::KI_DOWN;
case PAD_BUTTON_LEFT:
return Rml::Input::KI_LEFT;
case PAD_BUTTON_RIGHT:
return Rml::Input::KI_RIGHT;
case PAD_BUTTON_B:
return Rml::Input::KI_ESCAPE;
case PAD_BUTTON_A:
return Rml::Input::KI_RETURN;
case PAD_TRIGGER_R:
return Rml::Input::KI_NEXT;
case PAD_TRIGGER_L:
return Rml::Input::KI_PRIOR;
default:
return Rml::Input::KI_UNKNOWN;
}
}
Rml::Input::KeyIdentifier map_pad_axis(PADAxis axis) noexcept {
switch (axis) {
case PAD_AXIS_LEFT_X_POS:
return Rml::Input::KI_RIGHT;
case PAD_AXIS_LEFT_X_NEG:
return Rml::Input::KI_LEFT;
case PAD_AXIS_LEFT_Y_POS:
return Rml::Input::KI_UP;
case PAD_AXIS_LEFT_Y_NEG:
return Rml::Input::KI_DOWN;
case PAD_AXIS_TRIGGER_R:
return Rml::Input::KI_NEXT;
case PAD_AXIS_TRIGGER_L:
return Rml::Input::KI_PRIOR;
default:
return Rml::Input::KI_UNKNOWN;
}
}
Rml::Input::KeyIdentifier map_raw_gamepad_button(SDL_GamepadButton button) noexcept {
switch (button) {
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;
case SDL_GAMEPAD_BUTTON_EAST:
return Rml::Input::KI_ESCAPE;
case SDL_GAMEPAD_BUTTON_SOUTH:
return Rml::Input::KI_RETURN;
case SDL_GAMEPAD_BUTTON_BACK:
return Rml::Input::KI_PAUSE;
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
return Rml::Input::KI_NEXT;
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
return Rml::Input::KI_PRIOR;
default:
return Rml::Input::KI_UNKNOWN;
}
}
Rml::Input::KeyIdentifier map_raw_button_alias(SDL_GamepadButton button) noexcept {
switch (button) {
case SDL_GAMEPAD_BUTTON_BACK:
return Rml::Input::KI_PAUSE;
case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER:
return Rml::Input::KI_NEXT;
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
return Rml::Input::KI_PRIOR;
default:
return Rml::Input::KI_UNKNOWN;
}
}
Rml::Input::KeyIdentifier map_raw_gamepad_axis(SDL_GamepadAxis axis, PADAxisSign sign) noexcept {
switch (axis) {
case SDL_GAMEPAD_AXIS_LEFTX:
return sign == AXIS_SIGN_POSITIVE ? Rml::Input::KI_RIGHT : Rml::Input::KI_LEFT;
case SDL_GAMEPAD_AXIS_LEFTY:
return sign == AXIS_SIGN_NEGATIVE ? Rml::Input::KI_UP : Rml::Input::KI_DOWN;
case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER:
return sign == AXIS_SIGN_POSITIVE ? Rml::Input::KI_NEXT : Rml::Input::KI_UNKNOWN;
case SDL_GAMEPAD_AXIS_LEFT_TRIGGER:
return sign == AXIS_SIGN_POSITIVE ? Rml::Input::KI_PRIOR : Rml::Input::KI_UNKNOWN;
default:
return Rml::Input::KI_UNKNOWN;
}
}
bool find_event_port(SDL_JoystickID instance, u32& port) noexcept {
for (u32 candidate = 0; candidate < PAD_MAX_CONTROLLERS; ++candidate) {
const s32 index = PADGetIndexForPort(candidate);
if (index < 0) {
continue;
}
SDL_Gamepad* gamepad = PADGetSDLGamepadForIndex(static_cast<u32>(index));
if (gamepad == nullptr) {
continue;
}
SDL_Joystick* joystick = SDL_GetGamepadJoystick(gamepad);
if (joystick != nullptr && SDL_GetJoystickID(joystick) == instance) {
port = candidate;
return true;
}
}
return false;
}
bool find_mapped_pad_button(u32 port, SDL_GamepadButton nativeButton, PADButton& button) noexcept {
u32 buttonCount = 0;
PADButtonMapping* buttons = PADGetButtonMappings(port, &buttonCount);
if (buttons != nullptr) {
for (u32 i = 0; i < buttonCount; ++i) {
if (buttons[i].nativeButton == static_cast<u32>(nativeButton)) {
button = buttons[i].padButton;
return true;
}
}
}
u32 axisCount = 0;
PADAxisMapping* axes = PADGetAxisMappings(port, &axisCount);
if (axes != nullptr) {
for (u32 i = 0; i < axisCount; ++i) {
if (axes[i].nativeButton == nativeButton) {
button = pad_button_from_axis(axes[i].padAxis);
return button != 0;
}
}
}
return false;
}
bool find_mapped_pad_axis(
u32 port, SDL_GamepadAxis nativeAxis, PADAxisSign sign, PADAxis& axis) noexcept {
u32 buttonCount = 0;
PADGetButtonMappings(port, &buttonCount);
u32 axisCount = 0;
PADAxisMapping* axes = PADGetAxisMappings(port, &axisCount);
if (axes == nullptr) {
return false;
}
for (u32 i = 0; i < axisCount; ++i) {
const PADSignedNativeAxis mappedAxis = axes[i].nativeAxis;
if (mappedAxis.nativeAxis == nativeAxis && mappedAxis.sign == sign) {
axis = axes[i].padAxis;
return true;
}
}
return false;
}
bool find_event_pad_button(
const SDL_GamepadButtonEvent& event, u32& port, PADButton& button) noexcept {
return find_event_port(event.which, port) &&
find_mapped_pad_button(port, static_cast<SDL_GamepadButton>(event.button), button);
}
Rml::Input::KeyIdentifier map_gamepad_button(const SDL_GamepadButtonEvent& event) noexcept {
const auto nativeButton = static_cast<SDL_GamepadButton>(event.button);
if (nativeButton == SDL_GAMEPAD_BUTTON_BACK) {
return Rml::Input::KI_PAUSE;
}
u32 port = 0;
if (!find_event_port(event.which, port)) {
return map_raw_gamepad_button(nativeButton);
}
PADButton button = 0;
if (find_mapped_pad_button(port, nativeButton, button)) {
const auto key = map_pad_button(button);
return key == Rml::Input::KI_UNKNOWN ? map_raw_button_alias(nativeButton) : key;
}
return map_raw_button_alias(nativeButton);
}
Rml::Input::KeyIdentifier map_gamepad_axis(
const SDL_GamepadAxisEvent& event, PADAxisSign sign) noexcept {
u32 port = 0;
if (!find_event_port(event.which, port)) {
return map_raw_gamepad_axis(static_cast<SDL_GamepadAxis>(event.axis), sign);
}
PADAxis axis = 0;
if (find_mapped_pad_axis(port, static_cast<SDL_GamepadAxis>(event.axis), sign, axis)) {
return map_pad_axis(axis);
}
return Rml::Input::KI_UNKNOWN;
}
bool is_repeatable_key(Rml::Input::KeyIdentifier key) noexcept {
switch (key) {
case Rml::Input::KI_UP:
case Rml::Input::KI_DOWN:
case Rml::Input::KI_LEFT:
case Rml::Input::KI_RIGHT:
case Rml::Input::KI_NEXT:
case Rml::Input::KI_PRIOR:
return true;
default:
return false;
}
}
double repeat_interval(double heldFor) noexcept {
const double ramp = std::clamp(heldFor / kGamepadRepeatRampDuration, 0.0, 1.0);
return kGamepadRepeatStartInterval +
(kGamepadRepeatMinInterval - kGamepadRepeatStartInterval) * ramp;
}
GamepadRepeatState* button_repeat_state(SDL_GamepadButton button) noexcept {
const auto index = static_cast<int>(button);
if (index < 0 || index >= static_cast<int>(sGamepadButtonRepeats.size())) {
return nullptr;
}
return &sGamepadButtonRepeats[index];
}
GamepadRepeatState* axis_repeat_state(SDL_GamepadAxis axis, PADAxisSign sign) noexcept {
const auto axisIndex = static_cast<int>(axis);
if (axisIndex < 0 || axisIndex >= SDL_GAMEPAD_AXIS_COUNT) {
return nullptr;
}
const int directionOffset = sign == AXIS_SIGN_POSITIVE ? 0 : 1;
return &sGamepadAxisRepeats[axisIndex * 2 + directionOffset];
}
void clear_gamepad_repeats() noexcept {
for (auto& repeat : sGamepadButtonRepeats) {
repeat = {};
}
for (auto& repeat : sGamepadAxisRepeats) {
repeat = {};
}
sPadHoldMasks.fill(0);
sMenuChordConsumed.fill(false);
}
void begin_gamepad_key(GamepadRepeatState& repeat, Rml::Input::KeyIdentifier key) noexcept {
if (repeat.held) {
return;
}
const double now = now_seconds();
repeat.key = key;
repeat.pressedAt = now;
repeat.held = true;
repeat.repeatable = is_repeatable_key(key);
repeat.nextRepeatAt = repeat.repeatable ? now + kGamepadRepeatInitialDelay : 0.0;
repeat.pending = false;
}
void begin_pending_gamepad_key(GamepadRepeatState& repeat, Rml::Input::KeyIdentifier key) noexcept {
if (repeat.held) {
return;
}
const double now = now_seconds();
repeat.key = key;
repeat.pressedAt = now;
repeat.held = true;
repeat.repeatable = is_repeatable_key(key);
repeat.nextRepeatAt = 0.0;
repeat.pending = true;
}
void consume_menu_chord(u32 port, Rml::Context& context) noexcept {
if (port < sMenuChordConsumed.size()) {
sMenuChordConsumed[port] = true;
}
auto cancel_next = [&context](GamepadRepeatState& repeat) {
if (!repeat.held || repeat.key != Rml::Input::KI_NEXT) {
return;
}
if (!repeat.pending) {
context.ProcessKeyUp(repeat.key, 0);
}
repeat = {};
};
for (auto& repeat : sGamepadButtonRepeats) {
cancel_next(repeat);
}
for (auto& repeat : sGamepadAxisRepeats) {
cancel_next(repeat);
}
}
void update_menu_chord_release(u32 port) noexcept {
if (port >= sMenuChordConsumed.size() || has_menu_chord_part_held(port)) {
return;
}
sMenuChordConsumed[port] = false;
}
bool should_defer_menu_chord_part(PADButton button, Rml::Input::KeyIdentifier key) noexcept {
return button == PAD_TRIGGER_R && key == Rml::Input::KI_NEXT;
}
void process_axis_direction(
Rml::Context& context, const SDL_GamepadAxisEvent& event, PADAxisSign sign) noexcept {
GamepadRepeatState* repeat = axis_repeat_state(static_cast<SDL_GamepadAxis>(event.axis), sign);
if (repeat == nullptr) {
return;
}
const bool active = sign == AXIS_SIGN_POSITIVE ? event.value >= kGamepadAxisPressThreshold :
event.value <= -kGamepadAxisPressThreshold;
const bool released = sign == AXIS_SIGN_POSITIVE ? event.value <= kGamepadAxisReleaseThreshold :
event.value >= -kGamepadAxisReleaseThreshold;
u32 port = 0;
PADAxis padAxis = 0;
const bool hasPadAxis =
find_event_port(event.which, port) &&
find_mapped_pad_axis(port, static_cast<SDL_GamepadAxis>(event.axis), sign, padAxis);
const PADButton heldPadButton = hasPadAxis ? pad_button_from_axis(padAxis) : 0;
if (repeat->held) {
if (released) {
if (!repeat->pending) {
context.ProcessKeyUp(repeat->key, 0);
}
set_pad_button_held(port, heldPadButton, false);
*repeat = {};
update_menu_chord_release(port);
}
return;
}
if (!active) {
return;
}
set_pad_button_held(port, heldPadButton, true);
const bool chorded = heldPadButton == PAD_TRIGGER_R && is_menu_chord(port);
if (chorded) {
consume_menu_chord(port, context);
}
const auto key = chorded ? Rml::Input::KI_PAUSE : map_gamepad_axis(event, sign);
if (key == Rml::Input::KI_UNKNOWN) {
return;
}
if (!chorded && should_defer_menu_chord_part(heldPadButton, key)) {
begin_pending_gamepad_key(*repeat, key);
return;
}
begin_gamepad_key(*repeat, key);
context.ProcessMouseLeave();
context.ProcessKeyDown(key, 0);
}
} // namespace
void sync_input_block() noexcept {
const bool shouldBlock = any_document_visible() || should_block_pad_for_menu_chord();
if (sPadInputBlocked == shouldBlock) {
return;
}
PADBlockInput(shouldBlock);
sPadInputBlocked = shouldBlock;
}
void release_input_block() noexcept {
if (!sPadInputBlocked) {
return;
}
PADBlockInput(false);
sPadInputBlocked = false;
}
void reset_input_state() noexcept {
clear_gamepad_repeats();
}
void handle_event(const SDL_Event& event) noexcept {
if (event.type == SDL_EVENT_GAMEPAD_REMOVED || event.type == SDL_EVENT_WINDOW_FOCUS_LOST) {
reset_input_state();
sync_input_block();
return;
}
if (event.type != SDL_EVENT_GAMEPAD_BUTTON_DOWN && event.type != SDL_EVENT_GAMEPAD_BUTTON_UP &&
event.type != SDL_EVENT_GAMEPAD_AXIS_MOTION)
{
return;
}
auto* context = aurora::rmlui::get_context();
if (context == nullptr) {
return;
}
if (event.type == SDL_EVENT_GAMEPAD_AXIS_MOTION) {
process_axis_direction(*context, event.gaxis, AXIS_SIGN_POSITIVE);
process_axis_direction(*context, event.gaxis, AXIS_SIGN_NEGATIVE);
sync_input_block();
return;
}
auto* repeat = button_repeat_state(static_cast<SDL_GamepadButton>(event.gbutton.button));
u32 port = 0;
PADButton button = 0;
const bool hasPadButton = find_event_pad_button(event.gbutton, port, button);
if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) {
set_pad_button_held(port, button, true);
const bool chorded = hasPadButton && is_menu_chord_part(button) && is_menu_chord(port);
if (chorded) {
consume_menu_chord(port, *context);
}
const auto key = chorded ? Rml::Input::KI_PAUSE : map_gamepad_button(event.gbutton);
if (key != Rml::Input::KI_UNKNOWN) {
bool deferred = false;
if (repeat != nullptr) {
if (!chorded && should_defer_menu_chord_part(button, key)) {
begin_pending_gamepad_key(*repeat, key);
deferred = true;
} else {
begin_gamepad_key(*repeat, key);
}
}
if (!deferred) {
context->ProcessMouseLeave();
context->ProcessKeyDown(key, 0);
}
}
} else {
const auto key = repeat != nullptr && repeat->held ? repeat->key : Rml::Input::KI_UNKNOWN;
const bool wasPending = repeat != nullptr && repeat->pending;
set_pad_button_held(port, button, false);
update_menu_chord_release(port);
if (key != Rml::Input::KI_UNKNOWN) {
if (repeat != nullptr) {
*repeat = {};
}
if (!wasPending) {
context->ProcessKeyUp(key, 0);
}
}
}
sync_input_block();
}
void update_input() noexcept {
auto* context = aurora::rmlui::get_context();
if (context != nullptr) {
const double now = now_seconds();
auto process_repeats = [context, now](auto& repeats) {
for (auto& repeat : repeats) {
if (!repeat.held) {
continue;
}
if (repeat.pending) {
if (now < repeat.pressedAt + kGamepadMenuChordGraceDuration) {
continue;
}
repeat.pending = false;
repeat.pressedAt = now;
repeat.nextRepeatAt =
repeat.repeatable ? now + kGamepadRepeatInitialDelay : 0.0;
context->ProcessMouseLeave();
context->ProcessKeyDown(repeat.key, 0);
continue;
}
if (!repeat.repeatable || now < repeat.nextRepeatAt ||
(repeat.key == Rml::Input::KI_NEXT && any_menu_chord()))
{
continue;
}
context->ProcessKeyDown(repeat.key, 0);
const double heldFor = now - repeat.pressedAt;
repeat.nextRepeatAt = now + repeat_interval(heldFor);
}
};
process_repeats(sGamepadButtonRepeats);
process_repeats(sGamepadAxisRepeats);
} else {
reset_input_state();
}
}
} // namespace dusk::ui
+10
View File
@@ -0,0 +1,10 @@
#pragma once
namespace dusk::ui {
void update_input() noexcept;
void reset_input_state() noexcept;
void sync_input_block() noexcept;
void release_input_block() noexcept;
} // namespace dusk::ui
+1
View File
@@ -12,6 +12,7 @@ enum class NavCommand {
Previous, // L1
Confirm, // A
Cancel, // B
Menu, // Back/Minus, or R + Start
};
} // namespace dusk::ui
+12 -8
View File
@@ -60,9 +60,8 @@ void set_value(GraphicsOption option, int value) {
getSettings().game.shadowResolutionMultiplier.setValue(value);
break;
case GraphicsOption::BloomMode:
getSettings().game.bloomMode.setValue(
static_cast<BloomMode>(std::clamp(value, static_cast<int>(BloomMode::Off),
static_cast<int>(BloomMode::Dusk))));
getSettings().game.bloomMode.setValue(static_cast<BloomMode>(std::clamp(
value, static_cast<int>(BloomMode::Off), static_cast<int>(BloomMode::Dusk))));
break;
case GraphicsOption::BloomMultiplier:
getSettings().game.bloomMultiplier.setValue(std::clamp(value, 0, 100) / 100.0f);
@@ -209,10 +208,12 @@ Overlay::Overlay(OverlayProps props)
if (auto* footer = mDocument->GetElementById("footer")) {
auto& returnButton = add_component<Button>(footer, "\xE2\x86\x90 Return", "footer-button")
.on_pressed(pop_document);
.on_pressed(pop_document);
returnButton.root()->SetClass("return", true);
auto& resetButton = add_component<Button>(footer, "Reset to default", "footer-button")
.on_pressed([this] { reset_default(); });
auto& resetButton =
add_component<Button>(footer, "Reset to default", "footer-button").on_pressed([this] {
reset_default();
});
resetButton.root()->SetClass("reset", true);
}
@@ -221,7 +222,7 @@ Overlay::Overlay(OverlayProps props)
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mRoot &&
*mRoot->GetProperty(Rml::PropertyId::Visibility) == Rml::Style::Visibility::Visible &&
mDismissed)
!mRoot->HasAttribute("open"))
{
Document::hide();
}
@@ -235,7 +236,6 @@ void Overlay::show() {
void Overlay::hide() {
mRoot->RemoveAttribute("open");
mDismissed = true;
}
void Overlay::update() {
@@ -254,6 +254,10 @@ bool Overlay::focus() {
return false;
}
bool Overlay::visible() const {
return mRoot->HasAttribute("open");
}
bool Overlay::handle_nav_command(Rml::Event& event, NavCommand cmd) {
if (cmd == NavCommand::Cancel) {
pop_document();
+1
View File
@@ -63,6 +63,7 @@ public:
void hide() override;
void update() override;
bool focus() override;
bool visible() const override;
protected:
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
+5 -19
View File
@@ -47,7 +47,7 @@ Popup::Popup() : Document(kDocumentSource), mRoot(mDocument->GetElementById("pop
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mRoot &&
*mRoot->GetProperty(Rml::PropertyId::Visibility) == Rml::Style::Visibility::Visible &&
!mVisible)
!mRoot->HasAttribute("open"))
{
Document::hide();
}
@@ -58,39 +58,25 @@ Popup::Popup() : Document(kDocumentSource), mRoot(mDocument->GetElementById("pop
}
void Popup::show() {
if (mDocument == nullptr || mVisible) {
return;
}
Document::show();
mRoot->SetAttribute("open", "");
mTabBar->set_active_tab(-1);
mVisible = true;
}
void Popup::hide() {
if (mDocument == nullptr) {
mVisible = false;
return;
}
if (!mVisible) {
return;
}
mRoot->RemoveAttribute("open");
mVisible = false;
}
void Popup::toggle() {
if (is_visible()) {
if (visible()) {
hide();
} else {
show();
}
}
bool Popup::is_visible() const {
return mVisible;
bool Popup::visible() const {
return mRoot->HasAttribute("open");
}
bool Popup::handle_nav_command(Rml::Event& event, NavCommand cmd) {
@@ -98,7 +84,7 @@ bool Popup::handle_nav_command(Rml::Event& event, NavCommand cmd) {
toggle();
return true;
}
return false;
return Document::handle_nav_command(event, cmd);
}
bool Popup::focus() {
+1 -1
View File
@@ -18,9 +18,9 @@ public:
void show() override;
void hide() override;
bool focus() override;
bool visible() const override;
void toggle();
bool is_visible() const;
protected:
bool handle_nav_command(Rml::Event& event, NavCommand cmd) override;
+28 -4
View File
@@ -8,6 +8,7 @@
#include <filesystem>
#include "aurora/lib/window.hpp"
#include "input.hpp"
#include "window.hpp"
namespace dusk::ui {
@@ -45,13 +46,11 @@ bool initialize() noexcept {
void shutdown() noexcept {
sDocuments.clear();
reset_input_state();
release_input_block();
sInitialized = false;
}
void handle_event(const SDL_Event& event) noexcept {
// TODO
}
Document& push_document(std::unique_ptr<Document> doc, bool show) noexcept {
if (auto* doc = top_document()) {
doc->hide();
@@ -61,6 +60,7 @@ Document& push_document(std::unique_ptr<Document> doc, bool show) noexcept {
if (show) {
ret.show();
}
sync_input_block();
return ret;
}
@@ -75,6 +75,26 @@ void pop_document() noexcept {
if (auto* doc = top_document()) {
doc->show();
}
sync_input_block();
}
void toggle_top_document() noexcept {
auto* doc = top_document();
if (doc == nullptr) {
return;
}
if (doc->visible()) {
doc->hide();
} else {
doc->show();
}
sync_input_block();
}
bool any_document_visible() noexcept {
return std::any_of(sDocuments.begin(), sDocuments.end(),
[](const OpenDocument& doc) { return doc.doc != nullptr && doc.doc->visible(); });
}
Document* top_document() noexcept {
@@ -87,6 +107,7 @@ Document* top_document() noexcept {
}
void update() noexcept {
update_input();
for (const auto& doc : sDocuments) {
doc.doc->update();
}
@@ -94,6 +115,7 @@ void update() noexcept {
std::remove_if(sDocuments.begin(), sDocuments.end(),
[](const OpenDocument& doc) { return doc.pendingDestroy && doc.doc->can_destroy(); }),
sDocuments.end());
sync_input_block();
}
std::filesystem::path resource_path(const std::filesystem::path& filename) noexcept {
@@ -145,6 +167,8 @@ NavCommand map_nav_event(const Rml::Event& event) noexcept {
return NavCommand::Cancel;
case Rml::Input::KeyIdentifier::KI_RETURN:
return NavCommand::Confirm;
case Rml::Input::KeyIdentifier::KI_PAUSE:
return NavCommand::Menu;
case Rml::Input::KeyIdentifier::KI_NEXT:
return NavCommand::Next;
case Rml::Input::KeyIdentifier::KI_PRIOR:
+2
View File
@@ -34,6 +34,8 @@ void update() noexcept;
Document& push_document(std::unique_ptr<Document> doc, bool show = true) noexcept;
void pop_document() noexcept;
void toggle_top_document() noexcept;
bool any_document_visible() noexcept;
Document* top_document() noexcept;
Popup& add_popup(std::unique_ptr<Popup> popup) noexcept;
+10 -16
View File
@@ -66,7 +66,7 @@ Window::Window() : Document(kDocumentSource), mRoot(mDocument->GetElementById("w
listen(mRoot, Rml::EventId::Transitionend, [this](Rml::Event& event) {
if (event.GetTargetElement() == mRoot &&
*mRoot->GetProperty(Rml::PropertyId::Visibility) == Rml::Style::Visibility::Visible &&
!mVisible)
!mRoot->HasAttribute("open"))
{
Document::hide();
}
@@ -88,26 +88,13 @@ Window::Window() : Document(kDocumentSource), mRoot(mDocument->GetElementById("w
}
void Window::show() {
if (mVisible) {
return;
}
Document::show();
mRoot->SetAttribute("open", "");
mVisible = true;
}
void Window::hide() {
if (mDocument == nullptr) {
mVisible = false;
return;
}
if (!mVisible) {
return;
}
mRoot->RemoveAttribute("open");
mVisible = false;
// Document will be hidden after transition
}
void Window::update() {
@@ -171,6 +158,10 @@ bool Window::focus() {
return mTabBar->focus();
}
bool Window::visible() const {
return mRoot->HasAttribute("open");
}
bool Window::handle_nav_command(Rml::Event& event, NavCommand cmd) {
auto* target = event.GetTargetElement();
if (cmd != NavCommand::Next && cmd != NavCommand::Previous && target->Closest("content")) {
@@ -187,7 +178,10 @@ bool Window::handle_nav_command(Rml::Event& event, NavCommand cmd) {
pop_document();
return true;
}
return mTabBar->handle_nav_command(event, cmd);
if (mTabBar->handle_nav_command(event, cmd)) {
return true;
}
return Document::handle_nav_command(event, cmd);
}
bool Window::handle_content_nav(Rml::Event& event, NavCommand cmd) noexcept {
+1
View File
@@ -27,6 +27,7 @@ public:
void hide() override;
void update() override;
bool focus() override;
bool visible() const override;
bool set_active_tab(int index);
protected: