Added recompui::IconButton

This commit is contained in:
thecozies
2025-07-10 11:02:01 -05:00
parent 71dfde0270
commit 9247fc6b43
10 changed files with 262 additions and 9 deletions
+2
View File
@@ -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
+8 -4
View File
@@ -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);
+2 -1
View File
@@ -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
+1 -1
View File
@@ -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);
}
}
+177
View File
@@ -0,0 +1,177 @@
#include "ui_icon_button.h"
#include <cassert>
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<float>(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<Svg>(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<EventHover>(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<EventEnable>(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<EventFocus>(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<void()> 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 });
}
};
+43
View File
@@ -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<std::function<void()>> 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<void()> 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
+11
View File
@@ -0,0 +1,11 @@
#include "ui_svg.h"
#include <cassert>
namespace recompui {
Svg::Svg(Element *parent, std::string_view src) : Element(parent, 0, "svg") {
set_src(src);
}
};
+14
View File
@@ -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
+2 -2
View File
@@ -688,11 +688,11 @@ ModMenu::ModMenu(Element *parent) : Element(parent) {
Element* footer_spacer = context.create_element<Element>(footer_container);
footer_spacer->set_flex(1.0f, 0.0f);
refresh_button = context.create_element<Button>(footer_container, "Refresh", recompui::ButtonStyle::Tertiary);
refresh_button = context.create_element<IconButton>(footer_container, "icons/Reset.svg", recompui::ButtonStyle::Secondary, recompui::IconButtonSize::XLarge);
refresh_button->add_pressed_callback([this](){ refresh_mods(true); });
refresh_button->set_nav_manual(NavDirection::Up, mod_tab_id);
mods_folder_button = context.create_element<Button>(footer_container, "Open Mods Folder", recompui::ButtonStyle::Primary);
mods_folder_button = context.create_element<Button>(footer_container, "Open Mods Folder", recompui::ButtonStyle::Tertiary);
mods_folder_button->add_pressed_callback([this](){ open_mods_folder(); });
mods_folder_button->set_nav(NavDirection::Up, configure_button);
mods_folder_button->set_nav_manual(NavDirection::Up, mod_tab_id);
+2 -1
View File
@@ -3,6 +3,7 @@
#include "librecomp/mods.hpp"
#include "elements/ui_scroll_container.h"
#include "elements/ui_icon_button.h"
#include "ui_config_sub_menu.h"
#include "ui_mod_details_panel.h"
@@ -101,7 +102,7 @@ private:
Container *body_empty_container = nullptr;
Container *footer_container = nullptr;
Button *install_mods_button = nullptr;
Button *refresh_button = nullptr;
IconButton *refresh_button = nullptr;
Button *mods_folder_button = nullptr;
int32_t active_mod_index = -1;
std::vector<ModEntryButton *> mod_entry_buttons;