feat(controls): removed IconButton and added an optional icon to Button (less code to maintain)

This commit is contained in:
Lemmy
2026-04-04 18:49:25 -04:00
parent 388d153a4f
commit 79d1e4c1ef
8 changed files with 66 additions and 250 deletions
-1
View File
@@ -224,7 +224,6 @@ add_executable(noctalia
src/ui/controls/Chip.cpp
src/ui/controls/Dropdown.cpp
src/ui/controls/Icon.cpp
src/ui/controls/IconButton.cpp
src/ui/controls/Label.cpp
src/ui/controls/Slider.cpp
src/ui/controls/Toggle.cpp
+4 -1
View File
@@ -80,8 +80,11 @@ just build
just rebuild # debug
just rebuild release # release
# Run
# Run debug
just run
# Run release
just run release
```
<details>
+3 -4
View File
@@ -4,7 +4,6 @@
#include "ui/controls/Box.h"
#include "ui/controls/Button.h"
#include "ui/controls/Dropdown.h"
#include "ui/controls/IconButton.h"
#include "ui/controls/Label.h"
#include "ui/controls/Slider.h"
#include "ui/controls/Toggle.h"
@@ -51,16 +50,16 @@ void TestPanelContent::create(Renderer& renderer) {
container->addChild(std::move(row));
}
auto iconButton = std::make_unique<IconButton>();
auto iconButton = std::make_unique<Button>();
iconButton->setText("Settings");
iconButton->setIcon("settings");
iconButton->setVariant(ButtonVariant::Outline);
iconButton->setVariant(ButtonVariant::Default);
iconButton->setOnClick([]() {});
m_iconButton = iconButton.get();
{
auto row = makeRow();
auto rowLabel = std::make_unique<Label>();
rowLabel->setText("IconButton");
rowLabel->setText("Button w/ Icon");
rowLabel->setCaptionStyle();
row->addChild(std::move(rowLabel));
row->addChild(std::move(iconButton));
+1 -2
View File
@@ -5,7 +5,6 @@
class Box;
class Button;
class Dropdown;
class IconButton;
class Label;
class Slider;
class Toggle;
@@ -25,7 +24,7 @@ private:
Label* m_sliderValueLabel = nullptr;
Button* m_button = nullptr;
Dropdown* m_dropdown = nullptr;
IconButton* m_iconButton = nullptr;
Button* m_iconButton = nullptr;
Slider* m_slider = nullptr;
Toggle* m_toggle = nullptr;
};
+52 -5
View File
@@ -1,6 +1,7 @@
#include "ui/controls/Button.h"
#include "render/scene/InputArea.h"
#include "ui/controls/Icon.h"
#include "ui/controls/Label.h"
#include "ui/style/Palette.h"
#include "ui/style/Style.h"
@@ -19,7 +20,22 @@ Button::Button() {
void Button::setText(std::string_view text) { m_label->setText(text); }
void Button::setFontSize(float size) { m_label->setFontSize(size); }
void Button::setIcon(std::string_view name) {
ensureIcon();
m_icon->setIcon(name);
}
void Button::setFontSize(float size) {
m_label->setFontSize(size);
if (m_icon != nullptr) {
m_icon->setSize(size);
}
}
void Button::setIconSize(float size) {
ensureIcon();
m_icon->setSize(size);
}
void Button::setOnClick(std::function<void()> callback) {
m_onClick = std::move(callback);
@@ -134,22 +150,47 @@ void Button::applyVariant() {
applyVisualState();
}
void Button::ensureIcon() {
if (m_icon != nullptr) {
return;
}
// Insert icon before the label so it appears on the left
auto& kids = children();
std::size_t labelIndex = 0;
for (std::size_t i = 0; i < kids.size(); ++i) {
if (kids[i].get() == m_label) {
labelIndex = i;
break;
}
}
auto icon = std::make_unique<Icon>();
m_icon = static_cast<Icon*>(insertChildAt(labelIndex, std::move(icon)));
setDirection(BoxDirection::Horizontal);
setGap(Style::spaceXs);
}
void Button::applyVisualState() {
bool isHovered = hovered();
bool isPressed = pressed();
Color labelColor;
if (isPressed) {
setBackground(m_bgColorPressed);
setBorderColor(m_borderColorPressed);
m_label->setColor(m_labelColorPressed);
labelColor = m_labelColorPressed;
} else if (isHovered) {
setBackground(m_bgColorHover);
setBorderColor(m_borderColorHover);
m_label->setColor(m_labelColorHover);
labelColor = m_labelColorHover;
} else {
setBackground(m_bgColorNormal);
setBorderColor(m_borderColorNormal);
m_label->setColor(m_labelColorNormal);
labelColor = m_labelColorNormal;
}
m_label->setColor(labelColor);
if (m_icon != nullptr) {
m_icon->setColor(labelColor);
}
}
@@ -159,6 +200,9 @@ void Button::layout(Renderer& renderer) {
}
m_label->measure(renderer);
if (m_icon != nullptr) {
m_icon->measure(renderer);
}
if (m_inputArea != nullptr) {
m_inputArea->setVisible(false);
@@ -166,7 +210,10 @@ void Button::layout(Renderer& renderer) {
Box::layout(renderer);
m_label->setPosition((width() - m_label->width()) * 0.5f, (height() - m_label->height()) * 0.5f);
// Center label when there's no icon
if (m_icon == nullptr) {
m_label->setPosition((width() - m_label->width()) * 0.5f, (height() - m_label->height()) * 0.5f);
}
if (m_inputArea != nullptr) {
m_inputArea->setVisible(true);
+6
View File
@@ -7,6 +7,7 @@
#include <functional>
#include <string_view>
class Icon;
class InputArea;
class Label;
@@ -23,7 +24,9 @@ public:
Button();
void setText(std::string_view text);
void setIcon(std::string_view name);
void setFontSize(float size);
void setIconSize(float size);
void setVariant(ButtonVariant variant);
void setOnClick(std::function<void()> callback);
void setCursorShape(std::uint32_t shape);
@@ -33,13 +36,16 @@ public:
void updateInputArea();
[[nodiscard]] Label* label() const noexcept { return m_label; }
[[nodiscard]] Icon* icon() const noexcept { return m_icon; }
[[nodiscard]] bool hovered() const noexcept;
[[nodiscard]] bool pressed() const noexcept;
private:
void ensureIcon();
void applyVariant();
void applyVisualState();
Icon* m_icon = nullptr;
Label* m_label = nullptr;
InputArea* m_inputArea = nullptr;
std::function<void()> m_onClick;
-185
View File
@@ -1,185 +0,0 @@
#include "ui/controls/IconButton.h"
#include "render/scene/InputArea.h"
#include "ui/controls/Icon.h"
#include "ui/controls/Label.h"
#include "ui/style/Palette.h"
#include "ui/style/Style.h"
#include <memory>
IconButton::IconButton() {
setDirection(BoxDirection::Horizontal);
setAlign(BoxAlign::Center);
setGap(Style::spaceXs);
setPadding(Style::paddingV, Style::paddingH, Style::paddingV, Style::paddingH);
setRadius(Style::radiusMd);
auto icon = std::make_unique<Icon>();
m_icon = static_cast<Icon*>(addChild(std::move(icon)));
auto label = std::make_unique<Label>();
m_label = static_cast<Label*>(addChild(std::move(label)));
applyVariant();
}
void IconButton::setText(std::string_view text) { m_label->setText(text); }
void IconButton::setIcon(std::string_view name) { m_icon->setIcon(name); }
void IconButton::setFontSize(float size) {
m_label->setFontSize(size);
m_icon->setSize(size);
}
void IconButton::setIconSize(float size) { m_icon->setSize(size); }
void IconButton::setOnClick(std::function<void()> callback) {
m_onClick = std::move(callback);
if (m_inputArea == nullptr) {
auto area = std::make_unique<InputArea>();
area->setOnEnter([this](const InputArea::PointerData& /*data*/) { applyVisualState(); });
area->setOnLeave([this]() { applyVisualState(); });
area->setOnPress([this](const InputArea::PointerData& /*data*/) { applyVisualState(); });
area->setOnClick([this](const InputArea::PointerData& /*data*/) {
if (m_onClick) {
m_onClick();
}
applyVisualState();
});
m_inputArea = static_cast<InputArea*>(addChild(std::move(area)));
m_inputArea->setPosition(0.0f, 0.0f);
m_inputArea->setSize(width(), height());
}
}
void IconButton::setCursorShape(std::uint32_t shape) {
if (m_inputArea != nullptr) {
m_inputArea->setCursorShape(shape);
}
}
bool IconButton::hovered() const noexcept { return m_inputArea != nullptr && m_inputArea->hovered(); }
bool IconButton::pressed() const noexcept { return m_inputArea != nullptr && m_inputArea->pressed(); }
void IconButton::setVariant(ButtonVariant variant) {
if (m_variant == variant) {
return;
}
m_variant = variant;
applyVariant();
}
void IconButton::applyVariant() {
setPadding(Style::paddingV, Style::paddingH, Style::paddingV, Style::paddingH);
setRadius(Style::radiusMd);
setBorderWidth(Style::borderWidth);
switch (m_variant) {
case ButtonVariant::Default:
m_bgColorNormal = palette.surfaceVariant;
m_bgColorHover = brighten(m_bgColorNormal, 1.14f);
m_bgColorPressed = palette.primary;
m_labelColorNormal = palette.onSurfaceVariant;
m_labelColorHover = palette.onSurfaceVariant;
m_labelColorPressed = palette.onPrimary;
m_borderColorNormal = palette.outline;
m_borderColorHover = brighten(m_borderColorNormal, 1.14f);
m_borderColorPressed = palette.primary;
break;
case ButtonVariant::Secondary:
m_bgColorNormal = palette.secondary;
m_bgColorHover = brighten(m_bgColorNormal, 1.12f);
m_bgColorPressed = palette.primary;
m_labelColorNormal = palette.onSecondary;
m_labelColorHover = palette.onSecondary;
m_labelColorPressed = palette.onPrimary;
m_borderColorNormal = palette.outline;
m_borderColorHover = brighten(m_borderColorNormal, 1.14f);
m_borderColorPressed = palette.primary;
break;
case ButtonVariant::Destructive:
m_bgColorNormal = palette.error;
m_bgColorHover = brighten(m_bgColorNormal, 1.1f);
m_bgColorPressed = palette.error;
m_labelColorNormal = palette.onError;
m_labelColorHover = palette.onError;
m_labelColorPressed = palette.onError;
m_borderColorNormal = palette.outline;
m_borderColorHover = brighten(m_borderColorNormal, 1.14f);
m_borderColorPressed = palette.error;
break;
case ButtonVariant::Outline:
m_bgColorNormal = palette.surface;
m_bgColorHover = brighten(m_bgColorNormal, 1.12f);
m_bgColorPressed = palette.primary;
m_labelColorNormal = palette.onSurface;
m_labelColorHover = palette.onSurface;
m_labelColorPressed = palette.onPrimary;
m_borderColorNormal = palette.outline;
m_borderColorHover = brighten(m_borderColorNormal, 1.14f);
m_borderColorPressed = palette.primary;
break;
case ButtonVariant::Ghost:
m_bgColorNormal = palette.surface;
m_bgColorHover = brighten(m_bgColorNormal, 1.12f);
m_bgColorPressed = palette.primary;
m_labelColorNormal = palette.onSurface;
m_labelColorHover = palette.onSurface;
m_labelColorPressed = palette.onPrimary;
m_borderColorNormal = palette.outline;
m_borderColorHover = brighten(m_borderColorNormal, 1.14f);
m_borderColorPressed = palette.primary;
break;
}
applyVisualState();
}
void IconButton::applyVisualState() {
const bool isHovered = hovered();
const bool isPressed = pressed();
if (isPressed) {
setBackground(m_bgColorPressed);
setBorderColor(m_borderColorPressed);
m_label->setColor(m_labelColorPressed);
m_icon->setColor(m_labelColorPressed);
} else if (isHovered) {
setBackground(m_bgColorHover);
setBorderColor(m_borderColorHover);
m_label->setColor(m_labelColorHover);
m_icon->setColor(m_labelColorHover);
} else {
setBackground(m_bgColorNormal);
setBorderColor(m_borderColorNormal);
m_label->setColor(m_labelColorNormal);
m_icon->setColor(m_labelColorNormal);
}
}
void IconButton::layout(Renderer& renderer) {
if (m_label == nullptr || m_icon == nullptr) {
return;
}
m_icon->measure(renderer);
m_label->measure(renderer);
if (m_inputArea != nullptr) {
m_inputArea->setVisible(false);
}
Box::layout(renderer);
if (m_inputArea != nullptr) {
m_inputArea->setVisible(true);
m_inputArea->setPosition(0.0f, 0.0f);
m_inputArea->setSize(width(), height());
}
applyVisualState();
}
-52
View File
@@ -1,52 +0,0 @@
#pragma once
#include "ui/controls/Box.h"
#include "ui/controls/Button.h"
#include "render/core/Color.h"
#include <functional>
#include <string_view>
class Icon;
class InputArea;
class Label;
class IconButton : public Box {
public:
IconButton();
void setText(std::string_view text);
void setIcon(std::string_view name);
void setFontSize(float size);
void setIconSize(float size);
void setVariant(ButtonVariant variant);
void setOnClick(std::function<void()> callback);
void setCursorShape(std::uint32_t shape);
void layout(Renderer& renderer) override;
[[nodiscard]] Label* label() const noexcept { return m_label; }
[[nodiscard]] Icon* icon() const noexcept { return m_icon; }
[[nodiscard]] bool hovered() const noexcept;
[[nodiscard]] bool pressed() const noexcept;
private:
void applyVariant();
void applyVisualState();
Icon* m_icon = nullptr;
Label* m_label = nullptr;
InputArea* m_inputArea = nullptr;
std::function<void()> m_onClick;
ButtonVariant m_variant = ButtonVariant::Default;
Color m_bgColorNormal{};
Color m_bgColorHover{};
Color m_bgColorPressed{};
Color m_borderColorNormal{};
Color m_borderColorHover{};
Color m_borderColorPressed{};
Color m_labelColorNormal{};
Color m_labelColorHover{};
Color m_labelColorPressed{};
};