Improve radio navigation and setup mod config submenu navigation setup

This commit is contained in:
Mr-Wiseguy 2025-04-26 22:58:47 -04:00
parent f92edb590c
commit d93ac4b49a
9 changed files with 144 additions and 7 deletions

View file

@ -4,7 +4,7 @@
namespace recompui {
Container::Container(Element *parent, FlexDirection direction, JustifyContent justify_content) : Element(parent) {
Container::Container(Element *parent, FlexDirection direction, JustifyContent justify_content, uint32_t events_enabled) : Element(parent, events_enabled) {
set_display(Display::Flex);
set_flex_direction(direction);
set_justify_content(justify_content);

View file

@ -8,7 +8,7 @@ namespace recompui {
protected:
std::string_view get_type_name() override { return "Container"; }
public:
Container(Element* parent, FlexDirection direction, JustifyContent justify_content);
Container(Element* parent, FlexDirection direction, JustifyContent justify_content, uint32_t events_enabled = 0);
};
} // namespace recompui

View file

@ -456,6 +456,10 @@ void Element::focus() {
base->Focus();
}
void Element::blur() {
base->Blur();
}
void Element::queue_update() {
ContextId cur_context = get_current_context();

View file

@ -89,6 +89,7 @@ public:
float get_client_height();
void enable_focus();
void focus();
void blur();
void queue_update();
void register_callback(ContextId context, PTR(void) callback, PTR(void) userdata);
uint32_t get_input_value_u32();

View file

@ -67,6 +67,10 @@ namespace recompui {
apply_styles();
queue_update();
}
if (focus_queued) {
focus_queued = false;
focus();
}
break;
default:
break;
@ -102,9 +106,23 @@ namespace recompui {
}, val);
}
Radio::Radio(Element *parent) : Container(parent, FlexDirection::Row, JustifyContent::FlexStart) {
Radio::Radio(Element *parent) : Container(parent, FlexDirection::Row, JustifyContent::FlexStart, Events(EventType::Focus)) {
set_gap(24.0f);
set_align_items(AlignItems::FlexStart);
enable_focus();
}
void Radio::process_event(const Event &e) {
switch (e.type) {
case EventType::Focus:
if (!options.empty()) {
if (std::get<EventFocus>(e.variant).active) {
blur();
options[index]->queue_focus();
}
}
break;
}
}
Radio::~Radio() {
@ -120,6 +138,11 @@ namespace recompui {
if (options.size() == 1) {
set_index_internal(0, true, false);
}
// At least one other option already existed, so set up navigation.
else {
options[options.size() - 2]->set_nav(NavDirection::Right, options[options.size() - 1]);
options[options.size() - 1]->set_nav(NavDirection::Left, options[options.size() - 2]);
}
}
void Radio::set_index(uint32_t index) {
@ -134,4 +157,84 @@ namespace recompui {
index_changed_callbacks.emplace_back(callback);
}
void Radio::set_nav_auto(NavDirection dir) {
Element::set_nav_auto(dir);
if (!options.empty()) {
switch (dir) {
case NavDirection::Up:
case NavDirection::Down:
for (Element* e : options) {
e->set_nav_auto(dir);
}
break;
case NavDirection::Left:
options.front()->set_nav_auto(dir);
break;
case NavDirection::Right:
options.back()->set_nav_auto(dir);
break;
}
}
}
void Radio::set_nav_none(NavDirection dir) {
Element::set_nav_none(dir);
if (!options.empty()) {
switch (dir) {
case NavDirection::Up:
case NavDirection::Down:
for (Element* e : options) {
e->set_nav_none(dir);
}
break;
case NavDirection::Left:
options.front()->set_nav_none(dir);
break;
case NavDirection::Right:
options.back()->set_nav_none(dir);
break;
}
}
}
void Radio::set_nav(NavDirection dir, Element* element) {
Element::set_nav(dir, element);
if (!options.empty()) {
switch (dir) {
case NavDirection::Up:
case NavDirection::Down:
for (Element* e : options) {
e->set_nav(dir, element);
}
break;
case NavDirection::Left:
options.front()->set_nav(dir, element);
break;
case NavDirection::Right:
options.back()->set_nav(dir, element);
break;
}
}
}
void Radio::set_nav_manual(NavDirection dir, const std::string& target) {
Element::set_nav_manual(dir, target);
if (!options.empty()) {
switch (dir) {
case NavDirection::Up:
case NavDirection::Down:
for (Element* e : options) {
e->set_nav_manual(dir, target);
}
break;
case NavDirection::Left:
options.front()->set_nav_manual(dir, target);
break;
case NavDirection::Right:
options.back()->set_nav_manual(dir, target);
break;
}
}
}
};

View file

@ -11,6 +11,7 @@ namespace recompui {
Style pulsing_style;
std::function<void(uint32_t)> pressed_callback = nullptr;
uint32_t index = 0;
bool focus_queued = false;
protected:
virtual void process_event(const Event &e) override;
std::string_view get_type_name() override { return "LabelRadioOption"; }
@ -18,6 +19,7 @@ namespace recompui {
RadioOption(Element *parent, std::string_view name, uint32_t index);
void set_pressed_callback(std::function<void(uint32_t)> callback);
void set_selected_state(bool enable);
void queue_focus() { focus_queued = true; queue_update(); }
};
class Radio : public Container {
@ -31,6 +33,7 @@ namespace recompui {
void set_input_value(const ElementValue& val) override;
ElementValue get_element_value() override { return get_index(); }
protected:
virtual void process_event(const Event &e) override;
std::string_view get_type_name() override { return "LabelRadio"; }
public:
Radio(Element *parent);
@ -39,6 +42,13 @@ namespace recompui {
void set_index(uint32_t index);
uint32_t get_index() const;
void add_index_changed_callback(std::function<void(uint32_t)> callback);
size_t num_options() const { return options.size(); }
RadioOption* get_option_element(size_t option_index) { return options[option_index]; }
RadioOption* get_current_option_element() { return options.empty() ? nullptr : options[index]; }
void set_nav_auto(NavDirection dir) override;
void set_nav_none(NavDirection dir) override;
void set_nav(NavDirection dir, Element* element) override;
void set_nav_manual(NavDirection dir, const std::string& target) override;
};
} // namespace recompui

View file

@ -94,10 +94,10 @@ namespace recompui {
void set_drag(Drag drag);
void set_tab_index(TabIndex focus);
void set_font_family(std::string_view family);
void set_nav_auto(NavDirection dir);
void set_nav_none(NavDirection dir);
void set_nav(NavDirection dir, Element* element);
void set_nav_manual(NavDirection dir, const std::string& target);
virtual void set_nav_auto(NavDirection dir);
virtual void set_nav_none(NavDirection dir);
virtual void set_nav(NavDirection dir, Element* element);
virtual void set_nav_manual(NavDirection dir, const std::string& target);
void set_tab_index_auto();
void set_tab_index_none();
void set_focusable(bool focusable);

View file

@ -172,6 +172,8 @@ ConfigSubMenu::ConfigSubMenu(Element *parent) : Element(parent) {
description_label = context.create_element<Label>(body_container, "Description", LabelStyle::Small);
description_label->set_min_width(800.0f);
}
recompui::get_current_context().set_autofocus_element(back_button);
}
ConfigSubMenu::~ConfigSubMenu() {
@ -193,6 +195,15 @@ void ConfigSubMenu::add_option(ConfigOptionElement *option, std::string_view id,
option->set_name(name);
option->set_description(description);
option->set_hover_callback([this](ConfigOptionElement *option, bool active){ option_hovered(option, active); });
if (config_option_elements.empty()) {
back_button->set_nav(NavDirection::Down, option->get_focus_element());
option->set_nav(NavDirection::Up, back_button);
}
else {
config_option_elements.back()->set_nav(NavDirection::Down, option->get_focus_element());
option->set_nav(NavDirection::Up, config_option_elements.back()->get_focus_element());
}
config_option_elements.emplace_back(option);
}

View file

@ -31,6 +31,11 @@ public:
void set_description(std::string_view description);
void set_hover_callback(std::function<void(ConfigOptionElement *, bool)> callback);
const std::string &get_description() const;
void set_nav_auto(NavDirection dir) override { get_focus_element()->set_nav_auto(dir); }
void set_nav_none(NavDirection dir) override { get_focus_element()->set_nav_none(dir); }
void set_nav(NavDirection dir, Element* element) override { get_focus_element()->set_nav(dir, element); }
void set_nav_manual(NavDirection dir, const std::string& target) override { get_focus_element()->set_nav_manual(dir, target); }
virtual Element* get_focus_element() { return this; }
};
class ConfigOptionSlider : public ConfigOptionElement {
@ -42,6 +47,7 @@ protected:
std::string_view get_type_name() override { return "ConfigOptionSlider"; }
public:
ConfigOptionSlider(Element *parent, double value, double min_value, double max_value, double step_value, bool percent, std::function<void(const std::string &, double)> callback);
Element* get_focus_element() override { return slider; }
};
class ConfigOptionTextInput : public ConfigOptionElement {
@ -53,6 +59,7 @@ protected:
std::string_view get_type_name() override { return "ConfigOptionTextInput"; }
public:
ConfigOptionTextInput(Element *parent, std::string_view value, std::function<void(const std::string &, const std::string &)> callback);
Element* get_focus_element() override { return text_input; }
};
class ConfigOptionRadio : public ConfigOptionElement {
@ -64,6 +71,7 @@ protected:
std::string_view get_type_name() override { return "ConfigOptionRadio"; }
public:
ConfigOptionRadio(Element *parent, uint32_t value, const std::vector<std::string> &options, std::function<void(const std::string &, uint32_t)> callback);
Element* get_focus_element() override { return radio; }
};
class ConfigSubMenu : public Element {