From 9247fc6b434af13324c97d9b8db4a5d247a77f06 Mon Sep 17 00:00:00 2001 From: thecozies <79979276+thecozies@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:02:01 -0500 Subject: [PATCH] Added recompui::IconButton --- CMakeLists.txt | 2 + src/ui/elements/ui_button.cpp | 12 +- src/ui/elements/ui_button.h | 3 +- src/ui/elements/ui_element.cpp | 2 +- src/ui/elements/ui_icon_button.cpp | 177 +++++++++++++++++++++++++++++ src/ui/elements/ui_icon_button.h | 43 +++++++ src/ui/elements/ui_svg.cpp | 11 ++ src/ui/elements/ui_svg.h | 14 +++ src/ui/ui_mod_menu.cpp | 4 +- src/ui/ui_mod_menu.h | 3 +- 10 files changed, 262 insertions(+), 9 deletions(-) create mode 100644 src/ui/elements/ui_icon_button.cpp create mode 100644 src/ui/elements/ui_icon_button.h create mode 100644 src/ui/elements/ui_svg.cpp create mode 100644 src/ui/elements/ui_svg.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e0ab362..d7e18be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -183,6 +183,7 @@ set (SOURCES ${CMAKE_SOURCE_DIR}/src/ui/util/hsv.cpp ${CMAKE_SOURCE_DIR}/src/ui/core/ui_context.cpp ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_button.cpp + ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_icon_button.cpp ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_clickable.cpp ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_container.cpp ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_element.cpp @@ -193,6 +194,7 @@ set (SOURCES ${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 + ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_svg.cpp ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_text_input.cpp ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_theme.cpp ${CMAKE_SOURCE_DIR}/src/ui/elements/ui_toggle.cpp diff --git a/src/ui/elements/ui_button.cpp b/src/ui/elements/ui_button.cpp index c3e51a3..8d8ec7e 100644 --- a/src/ui/elements/ui_button.cpp +++ b/src/ui/elements/ui_button.cpp @@ -100,17 +100,21 @@ namespace recompui { apply_theme_style(ThemeColor::Danger); break; } + case ButtonStyle::Basic: { + apply_theme_style(ThemeColor::Text, true); + break; + } default: assert(false && "Unknown button style."); break; } } - void Button::apply_theme_style(recompui::ThemeColor color) { - const uint8_t border_opacity = 204; - const uint8_t background_opacity = 13; - const uint8_t border_hover_opacity = 255; + void Button::apply_theme_style(recompui::ThemeColor color, bool is_basic) { + const uint8_t border_opacity = is_basic ? 0 : 204; + const uint8_t background_opacity = is_basic ? 0 : 13; const uint8_t background_hover_opacity = 77; + const uint8_t border_hover_opacity = is_basic ? background_hover_opacity : 255; set_border_color(color, border_opacity); set_background_color(color, background_opacity); diff --git a/src/ui/elements/ui_button.h b/src/ui/elements/ui_button.h index 0781936..d305e61 100644 --- a/src/ui/elements/ui_button.h +++ b/src/ui/elements/ui_button.h @@ -11,6 +11,7 @@ namespace recompui { Success, Warning, Danger, + Basic, // No border, only shows background on hover or focus. }; class Button : public Element { @@ -34,7 +35,7 @@ namespace recompui { Style* get_hover_disabled_style() { return &hover_disabled_style; } void apply_button_style(ButtonStyle new_style); private: - void apply_theme_style(recompui::ThemeColor color); + void apply_theme_style(recompui::ThemeColor color, bool is_basic = false); }; } // namespace recompui diff --git a/src/ui/elements/ui_element.cpp b/src/ui/elements/ui_element.cpp index 01a849a..9bdaa73 100644 --- a/src/ui/elements/ui_element.cpp +++ b/src/ui/elements/ui_element.cpp @@ -110,7 +110,7 @@ void Element::apply_style(Style *style) { // Skip redundant SetProperty calls to prevent dirtying unnecessary state. // This avoids expensive layout operations when a simple color-only style is applied. const Rml::Property* cur_value = base->GetLocalProperty(it.first); - if (*cur_value != it.second) { + if (cur_value == nullptr || *cur_value != it.second) { base->SetProperty(it.first, it.second); } } diff --git a/src/ui/elements/ui_icon_button.cpp b/src/ui/elements/ui_icon_button.cpp new file mode 100644 index 0000000..2cf1ba9 --- /dev/null +++ b/src/ui/elements/ui_icon_button.cpp @@ -0,0 +1,177 @@ +#include "ui_icon_button.h" + +#include + +namespace recompui { + // Borders add width to the button, so this subtracts from the base size to bring it back to the expected size. + static const float border_width_thickness = 1.1f; + + IconButton::IconButton(Element *parent, const std::string &svg_src, ButtonStyle style, IconButtonSize size) : Element(parent, Events(EventType::Click, EventType::Hover, EventType::Enable, EventType::Focus), "button") { + this->style = style; + this->size = size; + float float_size_internal = static_cast(size) - (border_width_thickness * 2.0f); + + enable_focus(); + + set_display(Display::Flex); + set_align_items(AlignItems::Center); + set_justify_content(JustifyContent::Center); + set_width(float_size_internal); + set_min_width(float_size_internal); + set_max_width(float_size_internal); + set_height(float_size_internal); + set_min_height(float_size_internal); + set_max_height(float_size_internal); + set_border_width(border_width_thickness); + set_border_radius(float_size_internal * 0.5f); + set_border_color(ThemeColor::Transparent); + + set_cursor(Cursor::Pointer); + set_color(ThemeColor::TextDim); + set_tab_index(TabIndex::Auto); + + hover_style.set_color(ThemeColor::Text); + focus_style.set_color(ThemeColor::Text); + disabled_style.set_color(ThemeColor::TextDim); + disabled_style.set_cursor(Cursor::None); + disabled_style.set_opacity(0.5f); + hover_disabled_style.set_color(ThemeColor::TextDim); + + float icon_size = 0; + switch (size) { + case IconButtonSize::Mini: + icon_size = 16.0f; + break; + case IconButtonSize::Small: + icon_size = 24.0f; + break; + case IconButtonSize::Medium: + icon_size = 32.0f; + break; + case IconButtonSize::Large: + default: + icon_size = 32.0f; + break; + case IconButtonSize::XLarge: + icon_size = 40.0f; + break; + } + + ContextId context = get_current_context(); + + svg = context.create_element(this, svg_src); + svg->set_width(icon_size); + svg->set_color(ThemeColor::TextDim); + + apply_button_style(style); + + // transition: color 0.05s linear-in-out, background-color 0.05s linear-in-out; + } + + void IconButton::process_event(const Event &e) { + switch (e.type) { + case EventType::Click: + if (is_enabled()) { + for (const auto &function : pressed_callbacks) { + function(); + } + } + break; + case EventType::Hover: + { + bool hover_active = std::get(e.variant).active && is_enabled(); + set_style_enabled(hover_state, hover_active); + svg->set_color(hover_active ? ThemeColor::Text : ThemeColor::TextDim); + } + 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); + svg->set_color(ThemeColor::TextDim); + } + else { + set_cursor(Cursor::None); + set_focusable(false); + svg->set_color(ThemeColor::TextDim); + } + } + break; + case EventType::Focus: + { + bool focus_active = std::get(e.variant).active; + set_style_enabled(focus_state, focus_active); + svg->set_color(focus_active ? ThemeColor::Text : ThemeColor::TextDim); + } + break; + case EventType::Update: + break; + default: + assert(false && "Unknown event type."); + break; + } + } + + void IconButton::add_pressed_callback(std::function callback) { + pressed_callbacks.emplace_back(callback); + } + + void IconButton::apply_button_style(ButtonStyle new_style) { + style = new_style; + switch (style) { + case ButtonStyle::Primary: { + apply_theme_style(ThemeColor::Primary); + break; + } + case ButtonStyle::Secondary: { + apply_theme_style(ThemeColor::Secondary); + break; + } + case ButtonStyle::Tertiary: { + apply_theme_style(ThemeColor::Text); + break; + } + case ButtonStyle::Success: { + apply_theme_style(ThemeColor::Success); + break; + } + case ButtonStyle::Warning: { + apply_theme_style(ThemeColor::Warning); + break; + } + case ButtonStyle::Danger: { + apply_theme_style(ThemeColor::Danger); + break; + } + case ButtonStyle::Basic: { + apply_theme_style(ThemeColor::Text, true); + break; + } + default: + assert(false && "Unknown button style."); + break; + } + } + + void IconButton::apply_theme_style(recompui::ThemeColor color, bool is_basic) { + const uint8_t background_opacity = is_basic ? 0 : 13; + const uint8_t border_opacity = is_basic ? 0 : 204; + const uint8_t background_hover_opacity = 77; + const uint8_t border_hover_opacity = is_basic ? background_hover_opacity : 255; + + set_border_color(color, border_opacity); + set_background_color(color, background_opacity); + hover_style.set_border_color(color, border_hover_opacity); + hover_style.set_background_color(color, background_hover_opacity); + focus_style.set_border_color(color, border_hover_opacity); + focus_style.set_background_color(color, background_hover_opacity); + + 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 }); + } +}; diff --git a/src/ui/elements/ui_icon_button.h b/src/ui/elements/ui_icon_button.h new file mode 100644 index 0000000..f8cead5 --- /dev/null +++ b/src/ui/elements/ui_icon_button.h @@ -0,0 +1,43 @@ +#pragma once + +#include "ui_element.h" +#include "ui_button.h" +#include "ui_svg.h" + +namespace recompui { + enum class IconButtonSize { + Mini = 20, // 20x20 (Inline with body text) + Small = 32, // 32x32 + Medium = 48, // 48x48 + Large = 56, // 56x56 + Default = IconButtonSize::Large, + XLarge = 72, // 72x72 + }; + + class IconButton : public Element { + protected: + ButtonStyle style = ButtonStyle::Primary; + IconButtonSize size = IconButtonSize::Default; + Style hover_style; + Style focus_style; + Style disabled_style; + Style hover_disabled_style; + std::list> pressed_callbacks; + Svg *svg; + + // Element overrides. + virtual void process_event(const Event &e) override; + std::string_view get_type_name() override { return "IconButton"; } + public: + IconButton(Element *parent, const std::string &svg_src, ButtonStyle style, IconButtonSize size = IconButtonSize::Default); + void add_pressed_callback(std::function 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; } + void apply_button_style(ButtonStyle new_style); + private: + void apply_theme_style(recompui::ThemeColor color, bool is_basic = false); + }; + +} // namespace recompui diff --git a/src/ui/elements/ui_svg.cpp b/src/ui/elements/ui_svg.cpp new file mode 100644 index 0000000..c000a0b --- /dev/null +++ b/src/ui/elements/ui_svg.cpp @@ -0,0 +1,11 @@ +#include "ui_svg.h" + +#include + +namespace recompui { + + Svg::Svg(Element *parent, std::string_view src) : Element(parent, 0, "svg") { + set_src(src); + } + +}; diff --git a/src/ui/elements/ui_svg.h b/src/ui/elements/ui_svg.h new file mode 100644 index 0000000..97bb672 --- /dev/null +++ b/src/ui/elements/ui_svg.h @@ -0,0 +1,14 @@ +#pragma once + +#include "ui_element.h" + +namespace recompui { + + class Svg : public Element { + protected: + std::string_view get_type_name() override { return "Svg"; } + public: + Svg(Element *parent, std::string_view src); + }; + +} // namespace recompui diff --git a/src/ui/ui_mod_menu.cpp b/src/ui/ui_mod_menu.cpp index ca18455..350680a 100644 --- a/src/ui/ui_mod_menu.cpp +++ b/src/ui/ui_mod_menu.cpp @@ -688,11 +688,11 @@ ModMenu::ModMenu(Element *parent) : Element(parent) { Element* footer_spacer = context.create_element(footer_container); footer_spacer->set_flex(1.0f, 0.0f); - refresh_button = context.create_element