wip ui_select recompui component

This commit is contained in:
thecozies
2025-07-22 15:08:31 -05:00
parent b2395b5d3a
commit 61c842cbc8
5 changed files with 353 additions and 2 deletions
+1
View File
@@ -199,6 +199,7 @@ set (SOURCES
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_pill_button.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_radio.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_scroll_container.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_select.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_slider.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_span.cpp
${CMAKE_SOURCE_DIR}/src/ui/elements/ui_style.cpp
+29 -1
View File
@@ -513,4 +513,32 @@ void Element::register_callback(ContextId context, PTR(void) callback, PTR(void)
callbacks.emplace_back(UICallback{.context = context, .callback = callback, .userdata = userdata});
}
}
Element *Element::select_add_option(std::string_view text, std::string_view value) {
if (base->GetTagName() != "select") {
return nullptr;
}
Rml::ElementFormControlSelect* select = (Rml::ElementFormControlSelect *)(base);
if (!select) {
return nullptr;
}
ContextId context = get_current_context();
Element *option_element = context.create_element<Element>(this, 0, "option", true);
option_element->set_text(text);
option_element->set_input_text(value);
return option_element;
}
Element Element::get_element_with_tag_name(std::string_view tag_name) {
for (int i = 0; i < base->GetNumChildren(true); i++) {
Rml::Element* child = base->GetChild(i);
if (child->GetTagName() == tag_name) {
return Element(child);
}
}
throw std::runtime_error("Select element has no child with the specified tag name");
}
} // namespace recompui
+4 -1
View File
@@ -71,6 +71,7 @@ public:
bool remove_child(Element *child) { return remove_child(child->get_resource_id()); }
void add_style(Style *style, std::string_view style_name);
void add_style(Style *style, const std::initializer_list<std::string_view> &style_names);
Element get_element_with_tag_name(std::string_view tag_name);
void set_enabled(bool enabled);
bool is_enabled() const;
void set_text(std::string_view text);
@@ -99,8 +100,10 @@ public:
void set_input_value_float(float val) { set_input_value(val); }
void set_input_value_double(double val) { set_input_value(val); }
const std::string& get_id() { return id; }
Element *select_add_option(std::string_view text, std::string_view value);
};
void queue_ui_callback(recompui::ResourceId resource, const Event& e, const UICallback& callback);
} // namespace recompui
} // namespace recompui
+252
View File
@@ -0,0 +1,252 @@
#include "ui_select.h"
#include "ui_svg.h"
#include <cassert>
namespace recompui {
Option::Option(Element *parent, const SelectOption &option) :
Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "option", true),
option(option)
{
set_text(option.text);
set_input_text(option.value);
set_color(theme::color::Primary);
set_padding(12.0f);
set_height_auto();
set_width_auto();
set_overflow(Overflow::Hidden);
set_min_width(128.0f);
set_font_size(18.0f);
set_letter_spacing(2.52f);
set_line_height(18.0f);
set_font_weight(400);
set_background_color(theme::color::Transparent);
set_cursor(Cursor::Pointer);
hover_style.set_color(theme::color::TextActive);
hover_style.set_background_color(theme::color::White, 77);
focus_style.set_color(theme::color::TextActive);
focus_style.set_background_color(theme::color::White, 77);
disabled_style.set_color(theme::color::TextDim, 128);
disabled_style.set_background_color(theme::color::Transparent);
disabled_style.set_cursor(Cursor::None);
add_style(&hover_style, hover_state);
add_style(&focus_style, focus_state);
add_style(&disabled_style, disabled_state);
}
void Option::process_event(const Event &e) {
switch (e.type) {
case EventType::Click:
break;
case EventType::Hover:
printf("Option hovered: %s\n", option.text.c_str());
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && is_enabled());
break;
case EventType::Enable:
{
bool enable_active = std::get<EventEnable>(e.variant).active;
set_style_enabled(disabled_state, !enable_active);
if (enable_active) {
set_cursor(Cursor::Pointer);
set_focusable(true);
}
else {
set_cursor(Cursor::None);
set_focusable(false);
}
}
break;
case EventType::Focus:
set_style_enabled(focus_state, std::get<EventFocus>(e.variant).active);
break;
case EventType::Update:
break;
default:
assert(false && "Unknown event type.");
break;
}
}
constexpr std::string_view select_element_selectbox = "selectbox";
constexpr std::string_view select_element_selectarrow = "selectarrow";
constexpr std::string_view select_element_selectvalue = "selectvalue";
Select::Select(
Element *parent, std::vector<SelectOption> options, const std::string &label, const std::string &default_text
) :
Element(parent, Events(EventType::Text, EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "select", false),
label(label),
default_text(default_text)
{
this->options = options;
set_display(Display::Flex);
set_flex_direction(FlexDirection::Row);
set_align_items(AlignItems::Center);
set_justify_content(JustifyContent::SpaceBetween);
set_text_align(TextAlign::Left);
set_position(Position::Relative);
set_padding(16.0f);
set_height(76.0f);
set_width_auto();
set_overflow(Overflow::Hidden);
set_min_width(128.0f + 64.0f);
set_border_width(theme::border::width);
set_border_radius(theme::border::radius_md);
set_border_color(theme::color::Border, 204);
set_font_size(28.0f);
set_letter_spacing(3.08f);
set_line_height(28.0f);
set_font_style(FontStyle::Normal);
set_font_weight(700);
set_focusable(true);
set_color(theme::color::Text);
set_background_color(theme::color::Transparent);
set_cursor(Cursor::Pointer);
set_nav_auto(NavDirection::Up);
set_nav_auto(NavDirection::Right);
set_nav_auto(NavDirection::Down);
set_nav_auto(NavDirection::Left);
hover_style.set_color(theme::color::TextActive);
hover_style.set_background_color(theme::color::White, 77);
hover_style.set_border_color(theme::color::Border, 255);
focus_style.set_color(theme::color::TextActive);
focus_style.set_background_color(theme::color::White, 77);
focus_style.set_border_color(theme::color::Border, 255);
disabled_style.set_color(theme::color::TextDim, 128);
disabled_style.set_background_color(theme::color::Transparent);
disabled_style.set_border_color(theme::color::Border, 77);
disabled_style.set_cursor(Cursor::None);
hover_disabled_style.set_color(theme::color::TextDim, 128);
hover_disabled_style.set_background_color(theme::color::Transparent);
hover_disabled_style.set_border_color(theme::color::Border, 77);
hover_disabled_style.set_cursor(Cursor::None);
Element selectbox_element = get_element_with_tag_name(select_element_selectbox);
selectbox_element.set_margin_top(4.0f);
selectbox_element.set_width_auto();
selectbox_element.set_padding_top(8.0f);
selectbox_element.set_padding_bottom(8.0f);
selectbox_element.set_background_color(theme::color::Background3);
selectbox_element.set_border_width(theme::border::width);
selectbox_element.set_border_radius(theme::border::radius_sm);
selectbox_element.set_border_color(theme::color::BorderSoft);
Element selectvalue_element = get_element_with_tag_name(select_element_selectvalue);
selectvalue_element.set_display(Display::Block);
selectvalue_element.set_height_auto();
selectvalue_element.set_width_auto();
selectvalue_element.set_flex(1, 1);
selectvalue_element.set_flex_basis(100, Unit::Percent);
Element selectarrow_element = get_element_with_tag_name(select_element_selectarrow);
selectarrow_element.set_display(Display::Block);
selectarrow_element.set_height(24.0f);
selectarrow_element.set_width(24.0f);
ContextId context = get_current_context();
Svg *arrow = context.create_element<Svg>(
&selectarrow_element,
"assets/icons/Arrow.svg"
);
arrow->set_width(24.0f);
arrow->set_height(24.0f);
arrow->set_color(theme::color::TextDim);
add_option_elements();
add_style(&hover_style, hover_state);
add_style(&focus_style, focus_state);
add_style(&disabled_style, disabled_state);
add_style(&hover_disabled_style, { hover_state, disabled_state });
}
void Select::process_event(const Event &e) {
switch (e.type) {
case EventType::Click:
break;
case EventType::Hover:
set_style_enabled(hover_state, std::get<EventHover>(e.variant).active && is_enabled());
break;
case EventType::Enable:
{
bool enable_active = std::get<EventEnable>(e.variant).active;
set_style_enabled(disabled_state, !enable_active);
if (enable_active) {
set_cursor(Cursor::Pointer);
set_focusable(true);
}
else {
set_cursor(Cursor::None);
set_focusable(false);
}
}
break;
case EventType::Focus:
set_style_enabled(focus_state, std::get<EventFocus>(e.variant).active);
break;
case EventType::Text:
{
const std::string& opt_value = std::get<EventText>(e.variant).text;
int index = -1;
for (int i = 0; i < options.size(); i++) {
if (options[i].value == opt_value) {
index = i;
break;
}
}
if (index == -1) {
break;
}
selected_option_index = index;
for (const auto &callback : change_callbacks) {
callback(options[index], selected_option_index);
}
}
break;
case EventType::Update:
break;
default:
assert(false && "Unknown event type.");
break;
}
}
void Select::add_change_callback(std::function<void(SelectOption& option, int option_index)> callback) {
change_callbacks.emplace_back(callback);
}
void Select::add_option_elements() {
clear_children();
option_elements.clear();
ContextId context = get_current_context();
for (int i = 0; i < options.size(); i++) {
auto &option = options[i];
Option *option_element = context.create_element<Option>(this, option);
option_elements.push_back(option_element);
}
}
};
+67
View File
@@ -0,0 +1,67 @@
#pragma once
#include "ui_element.h"
namespace recompui {
enum class SelectOptionStyle {
Primary,
Secondary,
Tertiary,
Success,
Warning,
Danger,
Default = Tertiary,
};
struct SelectOption {
std::string text;
std::string value;
SelectOptionStyle style = SelectOptionStyle::Default;
SelectOption(const std::string &text, const std::string &value, SelectOptionStyle style = SelectOptionStyle::Default)
: text(text), value(value), style(style) {}
};
class Option : public Element {
protected:
SelectOption option;
Style hover_style;
Style focus_style;
Style disabled_style;
// Element overrides.
virtual void process_event(const Event &e) override;
std::string_view get_type_name() override { return "Option"; }
public:
Option(Element *parent, const SelectOption &option);
};
class Select : public Element {
protected:
int selected_option_index = -1;
std::string label;
std::string default_text;
std::vector<SelectOption> options;
std::vector<Option*> option_elements;
Style hover_style;
Style focus_style;
Style disabled_style;
Style hover_disabled_style;
std::list<std::function<void(SelectOption& option, int option_index)>> change_callbacks;
// Element overrides.
virtual void process_event(const Event &e) override;
std::string_view get_type_name() override { return "Select"; }
public:
Select(Element *parent, std::vector<SelectOption> options = {}, const std::string &label = "", const std::string &default_text = "");
void add_change_callback(std::function<void(SelectOption& option, int option_index)> callback);
Style* get_hover_style() { return &hover_style; }
Style* get_focus_style() { return &focus_style; }
Style* get_disabled_style() { return &disabled_style; }
Style* get_hover_disabled_style() { return &hover_disabled_style; }
private:
void add_option_elements();
};
} // namespace recompui