From 3aa54ca17067afd6ec2c3fb01a6a37b79430ec13 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sat, 22 Aug 2026 23:16:32 -0600 Subject: [PATCH] UiService: Add is_selected for buttons (#2337) --- sdk/include/mods/svc/ui.h | 8 +++++--- src/dusk/mods/svc/ui.cpp | 6 +++++- src/dusk/ui/group_button.cpp | 7 ++++++- src/dusk/ui/group_button.hpp | 3 +++ src/dusk/ui/mod_window.cpp | 2 ++ src/dusk/ui/mod_window.hpp | 1 + 6 files changed, 22 insertions(+), 5 deletions(-) diff --git a/sdk/include/mods/svc/ui.h b/sdk/include/mods/svc/ui.h index 38d67e3192..e9cbc3fa65 100644 --- a/sdk/include/mods/svc/ui.h +++ b/sdk/include/mods/svc/ui.h @@ -9,7 +9,7 @@ #define UI_SERVICE_ID "dev.twilitrealm.dusklight.ui" #define UI_SERVICE_MAJOR 1u -#define UI_SERVICE_MINOR 4u +#define UI_SERVICE_MINOR 5u /* * UI primitives: a panel inside the host Mods window, mod-owned windows, dialogs, toasts, @@ -102,7 +102,7 @@ typedef struct UiControlDesc { /* Optional override for the modified indicator. CONFIG_VAR controls derive it from value != * default when this is NULL. */ UiPredicateFn is_modified; - /* Passed to every callback above. */ + /* Passed to every callback. */ void* user_data; /* NUMBER: inclusive clamp range and step. min == max means the defaults (0 .. INT32_MAX); step * < 1 means 1. */ @@ -121,11 +121,13 @@ typedef struct UiControlDesc { const char* const* color_presets; size_t color_preset_count; bool color_alpha; /* COLOR: use RRGGBBAA values instead of RRGGBB */ + /* Optional selected state for BUTTON/GROUP. Added in UiService minor version 5. */ + UiPredicateFn is_selected; } UiControlDesc; #define UI_CONTROL_DESC_INIT \ {sizeof(UiControlDesc), UI_CONTROL_BUTTON, NULL, NULL, UI_BINDING_CALLBACKS, 0u, NULL, NULL, \ - NULL, NULL, NULL, NULL, 0, 0, 1, NULL, NULL, NULL, 0u, 0, NULL, 0u, false} + NULL, NULL, NULL, NULL, 0, 0, 1, NULL, NULL, NULL, 0u, 0, NULL, 0u, false, NULL} /* Build pane contents. A non-MOD_OK result fails the mod. */ typedef ModResult (*UiPaneBuildFn)( diff --git a/src/dusk/mods/svc/ui.cpp b/src/dusk/mods/svc/ui.cpp index 7296909b67..3ac625ab77 100644 --- a/src/dusk/mods/svc/ui.cpp +++ b/src/dusk/mods/svc/ui.cpp @@ -570,6 +570,9 @@ ModResult ui_pane_add_control( case UI_CONTROL_GROUP: spec.kind = desc.kind == UI_CONTROL_BUTTON ? ui::ModControlSpec::Kind::Button : ui::ModControlSpec::Kind::Group; + if (desc.struct_size >= sizeof(UiControlDesc)) { + spec.isSelected = wrap_predicate(mod, desc.is_selected, desc.user_data, pane); + } spec.onPressed = [modPtr = &mod, fn = desc.on_pressed, userData = desc.user_data, guardHandle = pane] { if (!slot_live(guardHandle)) { @@ -1147,6 +1150,7 @@ bool valid_color_preset(const char* value, bool alpha) { bool valid_control_desc(const UiControlDesc& desc) { constexpr size_t kLegacyDescSize = offsetof(UiControlDesc, color_presets); + constexpr size_t kColorDescSize = offsetof(UiControlDesc, is_selected); if (desc.struct_size < kLegacyDescSize || desc.label == nullptr) { return false; } @@ -1160,7 +1164,7 @@ bool valid_control_desc(const UiControlDesc& desc) { case UI_CONTROL_SELECT: break; case UI_CONTROL_COLOR: - if (desc.struct_size < sizeof(UiControlDesc)) { + if (desc.struct_size < kColorDescSize) { return false; } break; diff --git a/src/dusk/ui/group_button.cpp b/src/dusk/ui/group_button.cpp index 79ae8a4d32..2f9a430d68 100644 --- a/src/dusk/ui/group_button.cpp +++ b/src/dusk/ui/group_button.cpp @@ -4,15 +4,20 @@ namespace dusk::ui { GroupButton::GroupButton(Rml::Element* parent, Props props) : SelectButton{parent, {.key = std::move(props.text)}}, - mIsDisabled{std::move(props.isDisabled)} { + mIsSelected{std::move(props.isSelected)}, mIsDisabled{std::move(props.isDisabled)} { mRoot->SetClass("group-button", true); } void GroupButton::update() { + set_selected(selected()); set_disabled(disabled()); SelectButton::update(); } +bool GroupButton::selected() const { + return mIsSelected ? mIsSelected() : SelectButton::selected(); +} + bool GroupButton::disabled() const { return mIsDisabled ? mIsDisabled() : SelectButton::disabled(); } diff --git a/src/dusk/ui/group_button.hpp b/src/dusk/ui/group_button.hpp index 2287bb9004..8b207318d4 100644 --- a/src/dusk/ui/group_button.hpp +++ b/src/dusk/ui/group_button.hpp @@ -8,15 +8,18 @@ class GroupButton : public SelectButton { public: struct Props { Rml::String text; + std::function isSelected; std::function isDisabled; }; GroupButton(Rml::Element* parent, Props props); void update() override; + bool selected() const override; bool disabled() const override; private: + std::function mIsSelected; std::function mIsDisabled; }; diff --git a/src/dusk/ui/mod_window.cpp b/src/dusk/ui/mod_window.cpp index eebc1e086b..213ac29b7d 100644 --- a/src/dusk/ui/mod_window.cpp +++ b/src/dusk/ui/mod_window.cpp @@ -17,6 +17,7 @@ Component* build_mod_control(Pane& pane, Pane* helpPane, ModControlSpec spec) { case ModControlSpec::Kind::Button: control = &pane.add_button(ControlledButton::Props{ .text = s.label, + .isSelected = s.isSelected, .isDisabled = s.isDisabled, }) .on_pressed([shared] { @@ -28,6 +29,7 @@ Component* build_mod_control(Pane& pane, Pane* helpPane, ModControlSpec spec) { case ModControlSpec::Kind::Group: control = &pane.add_group_button(GroupButton::Props{ .text = s.label, + .isSelected = s.isSelected, .isDisabled = s.isDisabled, }) .on_pressed([shared] { diff --git a/src/dusk/ui/mod_window.hpp b/src/dusk/ui/mod_window.hpp index f8eade44d3..88e59dd68c 100644 --- a/src/dusk/ui/mod_window.hpp +++ b/src/dusk/ui/mod_window.hpp @@ -29,6 +29,7 @@ struct ModControlSpec { std::function setInt; std::function getString; std::function setString; + std::function isSelected; std::function isDisabled; std::function isModified; int min = 0;