From 61c842cbc895cd338a792c3d74ff03877d767536 Mon Sep 17 00:00:00 2001 From: thecozies <79979276+thecozies@users.noreply.github.com> Date: Tue, 22 Jul 2025 15:08:31 -0500 Subject: [PATCH] wip ui_select recompui component --- CMakeLists.txt | 1 + src/ui/elements/ui_element.cpp | 30 +++- src/ui/elements/ui_element.h | 5 +- src/ui/elements/ui_select.cpp | 252 +++++++++++++++++++++++++++++++++ src/ui/elements/ui_select.h | 67 +++++++++ 5 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 src/ui/elements/ui_select.cpp create mode 100644 src/ui/elements/ui_select.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fea9b8..0c834c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/ui/elements/ui_element.cpp b/src/ui/elements/ui_element.cpp index 9bdaa73..27dddf9 100644 --- a/src/ui/elements/ui_element.cpp +++ b/src/ui/elements/ui_element.cpp @@ -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}); } -} \ No newline at end of file +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(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 diff --git a/src/ui/elements/ui_element.h b/src/ui/elements/ui_element.h index 6e2ce16..35fdff3 100644 --- a/src/ui/elements/ui_element.h +++ b/src/ui/elements/ui_element.h @@ -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 &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 \ No newline at end of file +} // namespace recompui diff --git a/src/ui/elements/ui_select.cpp b/src/ui/elements/ui_select.cpp new file mode 100644 index 0000000..ce8ccf0 --- /dev/null +++ b/src/ui/elements/ui_select.cpp @@ -0,0 +1,252 @@ +#include "ui_select.h" +#include "ui_svg.h" + +#include + +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(e.variant).active && is_enabled()); + break; + case EventType::Enable: + { + bool enable_active = std::get(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(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 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( + &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(e.variant).active && is_enabled()); + break; + case EventType::Enable: + { + bool enable_active = std::get(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(e.variant).active); + break; + case EventType::Text: + { + const std::string& opt_value = std::get(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 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