UI: Implement controller config

Resolves #622
This commit is contained in:
Luke Street
2026-05-03 17:16:15 -06:00
parent 742f4938f2
commit 924dbc7715
10 changed files with 737 additions and 17 deletions
+1 -1
+2
View File
@@ -1469,6 +1469,8 @@ set(DUSK_FILES
src/dusk/ui/button.hpp
src/dusk/ui/component.cpp
src/dusk/ui/component.hpp
src/dusk/ui/controller_config.cpp
src/dusk/ui/controller_config.hpp
src/dusk/ui/document.cpp
src/dusk/ui/document.hpp
src/dusk/ui/editor.cpp
+617
View File
@@ -0,0 +1,617 @@
#include "controller_config.hpp"
#include "bool_button.hpp"
#include "button.hpp"
#include "pane.hpp"
#include "select_button.hpp"
#include <SDL3/SDL_gamepad.h>
#include <fmt/format.h>
#include <array>
#include <string>
#include <utility>
#include <vector>
namespace dusk::ui {
namespace {
Rml::String current_controller_name(int port) {
const char* name = PADGetName(port);
return name == nullptr ? "None" : name;
}
Rml::String controller_index_name(u32 index) {
const char* name = PADGetNameForControllerIndex(index);
if (name == nullptr) {
return fmt::format("Controller {}", index + 1);
}
return name;
}
SDL_Gamepad* gamepad_for_port(int port) {
const s32 index = PADGetIndexForPort(port);
if (index < 0) {
return nullptr;
}
return PADGetSDLGamepadForIndex(static_cast<u32>(index));
}
struct SpecificButtonName {
SDL_GamepadType type;
const char* name;
};
struct ButtonNames {
SDL_GamepadButton button;
std::vector<SpecificButtonName> names;
};
// clang-format off
const std::vector<ButtonNames> kGamepadButtonNames = {
{ SDL_GAMEPAD_BUTTON_LEFT_STICK, {
{SDL_GAMEPAD_TYPE_PS3, "L3"},
{SDL_GAMEPAD_TYPE_PS4, "L3"},
{SDL_GAMEPAD_TYPE_PS5, "L3"},
{SDL_GAMEPAD_TYPE_XBOX360, "Left Stick"},
{SDL_GAMEPAD_TYPE_XBOXONE, "Left Stick"},
{SDL_GAMEPAD_TYPE_GAMECUBE, "Control Stick"},
}},
{ SDL_GAMEPAD_BUTTON_RIGHT_STICK, {
{SDL_GAMEPAD_TYPE_PS3, "R3"},
{SDL_GAMEPAD_TYPE_PS4, "R3"},
{SDL_GAMEPAD_TYPE_PS5, "R3"},
{SDL_GAMEPAD_TYPE_XBOX360, "Right Stick"},
{SDL_GAMEPAD_TYPE_XBOXONE, "Right Stick"},
{SDL_GAMEPAD_TYPE_GAMECUBE, "C Stick"},
}},
{ SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, {
{SDL_GAMEPAD_TYPE_PS3, "L1"},
{SDL_GAMEPAD_TYPE_PS4, "L1"},
{SDL_GAMEPAD_TYPE_PS5, "L1"},
{SDL_GAMEPAD_TYPE_XBOX360, "LB"},
{SDL_GAMEPAD_TYPE_XBOXONE, "LB"},
}},
{ SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, {
{SDL_GAMEPAD_TYPE_PS3, "R1"},
{SDL_GAMEPAD_TYPE_PS4, "R1"},
{SDL_GAMEPAD_TYPE_PS5, "R1"},
{SDL_GAMEPAD_TYPE_XBOX360, "RB"},
{SDL_GAMEPAD_TYPE_XBOXONE, "RB"},
{SDL_GAMEPAD_TYPE_GAMECUBE, "Z"},
}},
{ SDL_GAMEPAD_BUTTON_BACK, {
{SDL_GAMEPAD_TYPE_PS3, "Select"},
{SDL_GAMEPAD_TYPE_PS4, "Share"},
{SDL_GAMEPAD_TYPE_PS5, "Create"},
{SDL_GAMEPAD_TYPE_XBOX360, "Back"},
{SDL_GAMEPAD_TYPE_XBOXONE, "View"},
}},
{ SDL_GAMEPAD_BUTTON_START, {
{SDL_GAMEPAD_TYPE_PS3, "Start"},
{SDL_GAMEPAD_TYPE_PS4, "Options"},
{SDL_GAMEPAD_TYPE_PS5, "Options"},
{SDL_GAMEPAD_TYPE_XBOX360, "Start"},
{SDL_GAMEPAD_TYPE_XBOXONE, "Menu"},
{SDL_GAMEPAD_TYPE_GAMECUBE, "Start/Pause"},
}},
};
// clang-format on
Rml::String native_button_name(SDL_Gamepad* gamepad, u32 buttonUntyped) {
if (buttonUntyped == PAD_NATIVE_BUTTON_INVALID) {
return "Not bound";
}
auto button = static_cast<SDL_GamepadButton>(buttonUntyped);
if (gamepad != nullptr) {
switch (SDL_GetGamepadButtonLabel(gamepad, button)) {
case SDL_GAMEPAD_BUTTON_LABEL_A:
return "A";
case SDL_GAMEPAD_BUTTON_LABEL_B:
return "B";
case SDL_GAMEPAD_BUTTON_LABEL_X:
return "X";
case SDL_GAMEPAD_BUTTON_LABEL_Y:
return "Y";
case SDL_GAMEPAD_BUTTON_LABEL_CROSS:
return "Cross";
case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE:
return "Circle";
case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE:
return "Triangle";
case SDL_GAMEPAD_BUTTON_LABEL_SQUARE:
return "Square";
default:
break;
}
}
const SDL_GamepadType type =
gamepad != nullptr ? SDL_GetGamepadType(gamepad) : SDL_GAMEPAD_TYPE_UNKNOWN;
for (const auto& buttonNames : kGamepadButtonNames) {
if (buttonNames.button != button) {
continue;
}
for (const auto& name : buttonNames.names) {
if (name.type == type) {
return name.name;
}
}
}
switch (button) {
case SDL_GAMEPAD_BUTTON_DPAD_LEFT:
return "D-pad left";
case SDL_GAMEPAD_BUTTON_DPAD_RIGHT:
return "D-pad right";
case SDL_GAMEPAD_BUTTON_DPAD_UP:
return "D-pad up";
case SDL_GAMEPAD_BUTTON_DPAD_DOWN:
return "D-pad down";
default:
break;
}
if (const char* name = PADGetNativeButtonName(buttonUntyped)) {
return name;
}
return "Unknown";
}
Rml::String native_axis_name(const PADAxisMapping& mapping, SDL_Gamepad* gamepad) {
if (mapping.nativeAxis.nativeAxis != -1) {
Rml::String value = PADGetNativeAxisName(mapping.nativeAxis);
if (mapping.padAxis != PAD_AXIS_TRIGGER_L && mapping.padAxis != PAD_AXIS_TRIGGER_R) {
value += mapping.nativeAxis.sign == AXIS_SIGN_POSITIVE ? "+" : "-";
}
return value;
}
if (mapping.nativeButton != -1) {
return native_button_name(gamepad, static_cast<u32>(mapping.nativeButton));
}
return "Not bound";
}
bool is_dpad_button(PADButton button) {
return button == PAD_BUTTON_UP || button == PAD_BUTTON_DOWN || button == PAD_BUTTON_LEFT ||
button == PAD_BUTTON_RIGHT;
}
bool is_action_button(PADButton button) {
return button == PAD_BUTTON_A || button == PAD_BUTTON_B || button == PAD_BUTTON_X ||
button == PAD_BUTTON_Y || button == PAD_BUTTON_START || button == PAD_TRIGGER_Z;
}
bool input_neutral(int port) {
if (port < 0) {
return true;
}
return PADGetNativeButtonPressed(port) == -1 && PADGetNativeAxisPulled(port).nativeAxis == -1;
}
} // namespace
ControllerConfigWindow::ControllerConfigWindow() {
listen(
Rml::EventId::Keydown,
[this](Rml::Event& event) {
if (capture_active() || mSuppressNavigationUntilNeutral) {
event.StopPropagation();
}
},
true);
if (auto* context = mDocument != nullptr ? mDocument->GetContext() : nullptr) {
if (auto* root = context->GetRootElement()) {
mListeners.emplace_back(std::make_unique<ScopedEventListener>(
root, "controllerchange", [this](Rml::Event&) { refresh_controller_page(); }));
}
}
for (int port = PAD_CHAN0; port < PAD_CHANMAX; ++port) {
add_tab(fmt::format("Port {}", port + 1), [this, port](Rml::Element* content) {
if (mPendingPort != -1 && mPendingPort != port) {
cancel_pending_binding();
}
build_port_tab(content, port);
});
}
}
void ControllerConfigWindow::hide(bool close) {
cancel_pending_binding();
Window::hide(close);
}
void ControllerConfigWindow::update() {
poll_pending_binding();
Window::update();
}
void ControllerConfigWindow::build_port_tab(Rml::Element* content, int port) {
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
mRightPane = &rightPane;
mActivePort = port;
auto showPage = [this, &rightPane, port](Page page) {
mPage = page;
render_page(rightPane, port, page);
};
auto addPageButton = [&leftPane, showPage](Page page, Rml::String key, auto getValue) {
leftPane
.add_select_button({
.key = std::move(key),
.getValue = std::move(getValue),
})
.on_focus([showPage, page](Rml::Event&) { showPage(page); })
.on_pressed([showPage, page] { showPage(page); });
};
addPageButton(Page::Controller, "Controller", [port] { return current_controller_name(port); });
addPageButton(Page::Buttons, "Buttons", [] { return Rml::String(">"); });
addPageButton(Page::Triggers, "Triggers", [] { return Rml::String(">"); });
addPageButton(Page::Sticks, "Sticks", [] { return Rml::String(">"); });
leftPane.add_section("Options");
leftPane
.add_child<BoolButton>(BoolButton::Props{
.key = "Enable Dead Zones",
.getValue =
[port] {
PADDeadZones* deadZones = PADGetDeadZones(port);
return deadZones != nullptr && deadZones->useDeadzones;
},
.setValue =
[port](bool value) {
if (PADDeadZones* deadZones = PADGetDeadZones(port)) {
deadZones->useDeadzones = value;
PADSerializeMappings();
}
},
.isDisabled = [port] { return PADGetDeadZones(port) == nullptr; },
})
.on_focus([&rightPane](Rml::Event&) {
rightPane.clear();
rightPane.add_text("Apply configured dead zones to the sticks and analog triggers.");
});
leftPane
.add_child<BoolButton>(BoolButton::Props{
.key = "Emulate Triggers",
.getValue =
[port] {
PADDeadZones* deadZones = PADGetDeadZones(port);
return deadZones != nullptr && deadZones->emulateTriggers;
},
.setValue =
[port](bool value) {
if (PADDeadZones* deadZones = PADGetDeadZones(port)) {
deadZones->emulateTriggers = value;
PADSerializeMappings();
}
},
.isDisabled = [port] { return PADGetDeadZones(port) == nullptr; },
})
.on_focus([&rightPane](Rml::Event&) {
rightPane.clear();
rightPane.add_text("Treat analog trigger movement as digital L and R button input.");
});
render_page(rightPane, port, mPage);
}
void ControllerConfigWindow::render_page(Pane& pane, int port, Page page) {
pane.clear();
switch (page) {
case Page::Controller: {
const u32 controllerCount = PADCount();
if (controllerCount == 0) {
pane.add_text("No controllers detected");
break;
}
pane.add_button({
.text = "None",
.isSelected = [port] { return PADGetIndexForPort(port) < 0; },
})
.on_pressed([this, port] {
cancel_pending_binding();
PADClearPort(port);
PADSerializeMappings();
});
for (u32 i = 0; i < controllerCount; ++i) {
pane.add_button(
{
.text = controller_index_name(i),
.isSelected =
[port, i] { return PADGetIndexForPort(port) == static_cast<s32>(i); },
})
.on_pressed([this, port, i] {
cancel_pending_binding();
PADSetPortForIndex(i, port);
PADSerializeMappings();
});
}
break;
}
case Page::Buttons: {
u32 buttonCount = 0;
PADButtonMapping* mappings = PADGetButtonMappings(port, &buttonCount);
if (mappings == nullptr) {
pane.add_text("No controller selected");
break;
}
SDL_Gamepad* gamepad = gamepad_for_port(port);
pane.add_section("Buttons");
for (u32 i = 0; i < buttonCount; ++i) {
PADButtonMapping& mapping = mappings[i];
if (!is_action_button(mapping.padButton)) {
continue;
}
pane.add_select_button({
.key = PADGetButtonName(mapping.padButton),
.getValue =
[this, &mapping, gamepad] {
if (mPendingButtonMapping == &mapping) {
return pending_button_label();
}
return native_button_name(
gamepad, mapping.nativeButton);
},
})
.on_pressed([this, port, &mapping] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingButtonMapping = &mapping;
});
}
pane.add_section("D-Pad");
for (u32 i = 0; i < buttonCount; ++i) {
PADButtonMapping& mapping = mappings[i];
if (!is_dpad_button(mapping.padButton)) {
continue;
}
pane.add_select_button({
.key = PADGetButtonName(mapping.padButton),
.getValue =
[this, &mapping, gamepad] {
if (mPendingButtonMapping == &mapping) {
return pending_button_label();
}
return native_button_name(
gamepad, mapping.nativeButton);
},
})
.on_pressed([this, port, &mapping] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingButtonMapping = &mapping;
});
}
break;
}
case Page::Triggers: {
u32 axisCount = 0;
PADAxisMapping* axes = PADGetAxisMappings(port, &axisCount);
u32 buttonCount = 0;
PADButtonMapping* buttons = PADGetButtonMappings(port, &buttonCount);
if (axes == nullptr && buttons == nullptr) {
pane.add_text("No controller selected");
break;
}
SDL_Gamepad* gamepad = gamepad_for_port(port);
pane.add_section("Analog");
constexpr std::array<PADAxis, 2> kTriggerAxes = {PAD_AXIS_TRIGGER_L, PAD_AXIS_TRIGGER_R};
if (axes != nullptr) {
for (PADAxis axis : kTriggerAxes) {
if (axis >= axisCount) {
continue;
}
PADAxisMapping& mapping = axes[axis];
pane.add_select_button({
.key = PADGetAxisName(mapping.padAxis),
.getValue =
[this, &mapping, gamepad] {
if (mPendingAxisMapping == &mapping) {
return pending_axis_label();
}
return native_axis_name(mapping, gamepad);
},
})
.on_pressed([this, port, &mapping] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingAxisMapping = &mapping;
});
}
}
pane.add_section("Digital");
if (buttons != nullptr) {
for (u32 i = 0; i < buttonCount; ++i) {
PADButtonMapping& mapping = buttons[i];
if (mapping.padButton != PAD_TRIGGER_L && mapping.padButton != PAD_TRIGGER_R) {
continue;
}
pane.add_select_button({
.key = PADGetButtonName(mapping.padButton),
.getValue =
[this, &mapping, gamepad] {
if (mPendingButtonMapping == &mapping) {
return pending_button_label();
}
return native_button_name(
gamepad, mapping.nativeButton);
},
})
.on_pressed([this, port, &mapping] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingButtonMapping = &mapping;
});
}
}
break;
}
case Page::Sticks: {
u32 axisCount = 0;
PADAxisMapping* axes = PADGetAxisMappings(port, &axisCount);
if (axes == nullptr) {
pane.add_text("No controller selected");
break;
}
SDL_Gamepad* gamepad = gamepad_for_port(port);
auto addAxis = [&](PADAxis axis) {
if (axis >= axisCount) {
return;
}
PADAxisMapping& mapping = axes[axis];
pane.add_select_button({
.key = PADGetAxisDirectionLabel(mapping.padAxis),
.getValue =
[this, &mapping, gamepad] {
if (mPendingAxisMapping == &mapping) {
return pending_axis_label();
}
return native_axis_name(mapping, gamepad);
},
})
.on_pressed([this, port, &mapping] {
cancel_pending_binding();
mPendingPort = port;
mPendingBindingArmed = false;
mPendingAxisMapping = &mapping;
});
};
pane.add_section("Control Stick");
addAxis(PAD_AXIS_LEFT_Y_POS);
addAxis(PAD_AXIS_LEFT_Y_NEG);
addAxis(PAD_AXIS_LEFT_X_NEG);
addAxis(PAD_AXIS_LEFT_X_POS);
pane.add_section("C Stick");
addAxis(PAD_AXIS_RIGHT_Y_POS);
addAxis(PAD_AXIS_RIGHT_Y_NEG);
addAxis(PAD_AXIS_RIGHT_X_NEG);
addAxis(PAD_AXIS_RIGHT_X_POS);
break;
}
}
}
void ControllerConfigWindow::refresh_controller_page() {
if (!visible() || mPage != Page::Controller || mRightPane == nullptr) {
return;
}
render_page(*mRightPane, mActivePort, Page::Controller);
}
void ControllerConfigWindow::poll_pending_binding() {
if (mSuppressNavigationUntilNeutral && input_neutral(mSuppressNavigationPort)) {
mSuppressNavigationUntilNeutral = false;
mSuppressNavigationPort = -1;
}
if (!capture_active()) {
return;
}
if (!mPendingBindingArmed) {
if (pending_input_neutral()) {
mPendingBindingArmed = true;
}
return;
}
if (mPendingButtonMapping != nullptr) {
const s32 nativeButton = PADGetNativeButtonPressed(mPendingPort);
if (nativeButton != -1) {
const int completedPort = mPendingPort;
mPendingButtonMapping->nativeButton = static_cast<u32>(nativeButton);
mPendingButtonMapping = nullptr;
mPendingPort = -1;
mPendingBindingArmed = false;
mSuppressNavigationUntilNeutral = true;
mSuppressNavigationPort = completedPort;
PADSerializeMappings();
}
return;
}
if (mPendingAxisMapping != nullptr) {
const PADSignedNativeAxis nativeAxis = PADGetNativeAxisPulled(mPendingPort);
if (nativeAxis.nativeAxis != -1) {
const int completedPort = mPendingPort;
mPendingAxisMapping->nativeAxis = nativeAxis;
mPendingAxisMapping->nativeButton = -1;
mPendingAxisMapping = nullptr;
mPendingPort = -1;
mPendingBindingArmed = false;
mSuppressNavigationUntilNeutral = true;
mSuppressNavigationPort = completedPort;
PADSerializeMappings();
return;
}
const s32 nativeButton = PADGetNativeButtonPressed(mPendingPort);
if (nativeButton != -1) {
const int completedPort = mPendingPort;
mPendingAxisMapping->nativeAxis = {-1, AXIS_SIGN_POSITIVE};
mPendingAxisMapping->nativeButton = nativeButton;
mPendingAxisMapping = nullptr;
mPendingPort = -1;
mPendingBindingArmed = false;
mSuppressNavigationUntilNeutral = true;
mSuppressNavigationPort = completedPort;
PADSerializeMappings();
}
}
}
bool ControllerConfigWindow::capture_active() const {
return mPendingButtonMapping != nullptr || mPendingAxisMapping != nullptr;
}
bool ControllerConfigWindow::pending_input_neutral() const {
return input_neutral(mPendingPort);
}
Rml::String ControllerConfigWindow::pending_button_label() const {
return mPendingBindingArmed ? "Press a button..." : "Waiting...";
}
Rml::String ControllerConfigWindow::pending_axis_label() const {
return mPendingBindingArmed ? "Move axis or press a button..." : "Waiting...";
}
void ControllerConfigWindow::cancel_pending_binding() {
if (mPendingButtonMapping == nullptr && mPendingAxisMapping == nullptr &&
!mSuppressNavigationUntilNeutral)
{
return;
}
mPendingButtonMapping = nullptr;
mPendingAxisMapping = nullptr;
mPendingPort = -1;
mPendingBindingArmed = false;
mSuppressNavigationUntilNeutral = false;
mSuppressNavigationPort = -1;
}
} // namespace dusk::ui
+45
View File
@@ -0,0 +1,45 @@
#pragma once
#include "window.hpp"
#include <pad.h>
namespace dusk::ui {
class ControllerConfigWindow : public Window {
public:
ControllerConfigWindow();
void update() override;
void hide(bool close) override;
private:
enum class Page {
Controller,
Buttons,
Triggers,
Sticks,
};
void build_port_tab(Rml::Element* content, int port);
void render_page(class Pane& pane, int port, Page page);
void refresh_controller_page();
void poll_pending_binding();
bool capture_active() const;
bool pending_input_neutral() const;
Rml::String pending_button_label() const;
Rml::String pending_axis_label() const;
void cancel_pending_binding();
Page mPage = Page::Controller;
Pane* mRightPane = nullptr;
int mActivePort = 0;
int mPendingPort = -1;
bool mPendingBindingArmed = false;
bool mSuppressNavigationUntilNeutral = false;
int mSuppressNavigationPort = -1;
PADButtonMapping* mPendingButtonMapping = nullptr;
PADAxisMapping* mPendingAxisMapping = nullptr;
};
} // namespace dusk::ui
+12 -1
View File
@@ -10,9 +10,20 @@ ScopedEventListener::ScopedEventListener(
mElement->AddEventListener(mEvent, this, mCapture);
}
ScopedEventListener::ScopedEventListener(
Rml::Element* element, Rml::String event, Callback callback, bool capture)
: mElement(element), mEventName(std::move(event)), mCapture(capture),
mCallback(std::move(callback)) {
mElement->AddEventListener(mEventName, this, mCapture);
}
ScopedEventListener::~ScopedEventListener() {
if (mElement != nullptr) {
mElement->RemoveEventListener(mEvent, this, mCapture);
if (!mEventName.empty()) {
mElement->RemoveEventListener(mEventName, this, mCapture);
} else {
mElement->RemoveEventListener(mEvent, this, mCapture);
}
mElement = nullptr;
}
}
+3
View File
@@ -12,6 +12,8 @@ public:
ScopedEventListener(
Rml::Element* element, Rml::EventId event, Callback callback, bool capture = false);
ScopedEventListener(
Rml::Element* element, Rml::String event, Callback callback, bool capture = false);
~ScopedEventListener() override;
ScopedEventListener(const ScopedEventListener&) = delete;
@@ -25,6 +27,7 @@ public:
private:
Rml::Element* mElement = nullptr;
Rml::EventId mEvent = Rml::EventId::Invalid;
Rml::String mEventName;
bool mCapture = false;
Callback mCallback;
};
+38 -1
View File
@@ -64,6 +64,40 @@ bool should_block_pad_for_menu_chord() noexcept {
return false;
}
const char* controller_change_type(Uint32 eventType) noexcept {
switch (eventType) {
case SDL_EVENT_GAMEPAD_ADDED:
return "added";
case SDL_EVENT_GAMEPAD_REMOVED:
return "removed";
case SDL_EVENT_GAMEPAD_REMAPPED:
return "remapped";
default:
return nullptr;
}
}
void dispatch_controller_change_event(const SDL_Event& event) noexcept {
const char* type = controller_change_type(event.type);
if (type == nullptr) {
return;
}
auto* context = aurora::rmlui::get_context();
if (context == nullptr) {
return;
}
auto* root = context->GetRootElement();
if (root == nullptr) {
return;
}
Rml::Dictionary parameters;
parameters["type"] = Rml::String(type);
parameters["which"] = static_cast<int>(event.gdevice.which);
root->DispatchEvent("controllerchange", parameters);
}
PADButton pad_button_from_axis(PADAxis axis) noexcept {
switch (axis) {
case PAD_AXIS_TRIGGER_R:
@@ -502,8 +536,11 @@ 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_REMOVED) {
return;
}
}
dispatch_controller_change_event(event);
if (event.type != SDL_EVENT_GAMEPAD_BUTTON_DOWN && event.type != SDL_EVENT_GAMEPAD_BUTTON_UP &&
event.type != SDL_EVENT_GAMEPAD_AXIS_MOTION)
{
+12
View File
@@ -51,6 +51,18 @@ void SelectButton::set_value_label(const Rml::String& value) {
}
}
SelectButton& SelectButton::on_pressed(SelectButtonCallback callback) {
if (!callback) {
return *this;
}
listen(Rml::EventId::Submit, [this, callback = std::move(callback)](Rml::Event& event) {
if (!disabled() && event.GetTargetElement() == mRoot) {
callback();
}
});
return *this;
}
void SelectButton::update_props(Props props) {
if (mProps.key != props.key) {
mKeyElem->SetInnerRML(escape(props.key));
+6
View File
@@ -3,8 +3,13 @@
#include "component.hpp"
#include "ui.hpp"
#include <functional>
#include <utility>
namespace dusk::ui {
using SelectButtonCallback = std::function<void()>;
class SelectButton : public FluentComponent<SelectButton> {
public:
struct Props {
@@ -18,6 +23,7 @@ public:
virtual bool modified() const;
void set_modified(bool value);
void set_value_label(const Rml::String& value);
SelectButton& on_pressed(SelectButtonCallback callback);
protected:
void update_props(Props props);
+1 -14
View File
@@ -1,9 +1,8 @@
#include "settings.hpp"
#include <fmt/format.h>
#include "aurora/gfx.h"
#include "bool_button.hpp"
#include "controller_config.hpp"
#include "dusk/audio/DuskAudioSystem.h"
#include "dusk/audio/DuskDsp.hpp"
#include "dusk/config.hpp"
@@ -127,18 +126,6 @@ SelectButton& config_percent_select(Pane& leftPane, Pane& rightPane, ConfigVar<f
});
}
class ControllerConfigWindow : public Window {
public:
ControllerConfigWindow() {
for (int i = 0; i < 4; ++i) {
add_tab(fmt::format("Port {}", i + 1), [this](Rml::Element* content) {
auto& pane = add_child<Pane>(content, Pane::Type::Controlled);
pane.add_section("Coming soon");
});
}
}
};
} // namespace
SettingsWindow::SettingsWindow() {