feat(controls): add checkbox

This commit is contained in:
Lysec
2026-04-08 20:11:17 +02:00
parent f39bd07abb
commit b2d7fa37b3
6 changed files with 194 additions and 5 deletions
+1
View File
@@ -415,6 +415,7 @@ add_executable(noctalia
src/ui/controls/audio_spectrum.cpp
src/ui/controls/button.cpp
src/ui/controls/chip.cpp
src/ui/controls/checkbox.cpp
src/ui/controls/flex.cpp
src/ui/controls/glyph.cpp
src/ui/controls/glyph_registry.cpp
+2 -3
View File
@@ -202,7 +202,7 @@ gdbus call --session --dest dev.noctalia.Debug --object-path /dev/noctalia/Debug
### Controls (`src/ui/controls/`)
- [ ] Checkbox
- [x] Checkbox
- [ ] Tab bar
- [ ] Grid view
- [ ] Context menu
@@ -216,7 +216,6 @@ gdbus call --session --dest dev.noctalia.Debug --object-path /dev/noctalia/Debug
- [ ] Network
- [ ] Bluetooth
- [ ] Brightness
- [ ] Microphone <- unsure we need it
- [ ] Power profile
- [~] System monitor <- need graph fix
- [ ] Dock
@@ -227,7 +226,7 @@ gdbus call --session --dest dev.noctalia.Debug --object-path /dev/noctalia/Debug
- [ ] Keep awake (idle inhibitor)
- [ ] Audio visualizer
- [x] Launcher button
- [ ] Session menu button
- [x] Session menu button
- [ ] Wallpaper selector button
- [ ] Custom button (with user-defined IPC)
- [ ] Settings button
+26
View File
@@ -2,6 +2,7 @@
#include "ui/controls/box.h"
#include "ui/controls/button.h"
#include "ui/controls/checkbox.h"
#include "ui/controls/flex.h"
#include "ui/controls/glyph.h"
#include "ui/controls/input.h"
@@ -203,6 +204,31 @@ void TestPanel::create(Renderer& renderer) {
container->addChild(std::move(row));
}
// Checkbox
auto checkbox = std::make_unique<Checkbox>();
checkbox->setScale(scale);
checkbox->setChecked(true);
checkbox->setOnChange([this](bool checked) {
if (m_checkboxValueLabel != nullptr) {
m_checkboxValueLabel->setText(checked ? "true" : "false");
}
});
m_checkbox = checkbox.get();
auto checkboxValueLabel = std::make_unique<Label>();
checkboxValueLabel->setText("true");
checkboxValueLabel->setCaptionStyle();
checkboxValueLabel->setFontSize(Style::fontSizeCaption * scale);
m_checkboxValueLabel = checkboxValueLabel.get();
{
auto row = makeRow();
row->addChild(makeRowLabel("Checkbox", kRowLabelWidth));
row->addChild(std::move(checkbox));
row->addChild(std::move(checkboxValueLabel));
container->addChild(std::move(row));
}
// Radio
{
auto options = std::make_unique<Flex>();
+5 -2
View File
@@ -5,6 +5,7 @@
class Flex;
class Button;
class Box;
class Checkbox;
class Glyph;
class Input;
class RadioButton;
@@ -20,8 +21,8 @@ public:
void layout(Renderer& renderer, float width, float height) override;
void update(Renderer& renderer) override;
[[nodiscard]] float preferredWidth() const override { return scaled(640.0f); }
[[nodiscard]] float preferredHeight() const override { return scaled(480.0f); }
[[nodiscard]] float preferredWidth() const override { return scaled(860.0f); }
[[nodiscard]] float preferredHeight() const override { return scaled(620.0f); }
// [[nodiscard]] bool centeredHorizontally() const override { return true; }
// [[nodiscard]] bool centeredVertically() const override { return true; }
private:
@@ -29,6 +30,7 @@ private:
Label* m_headerLabel = nullptr;
Label* m_sliderValueLabel = nullptr;
Label* m_toggleValueLabel = nullptr;
Label* m_checkboxValueLabel = nullptr;
Button* m_button = nullptr;
Select* m_select = nullptr;
Button* m_glyphTextButton = nullptr;
@@ -37,6 +39,7 @@ private:
Glyph* m_glyph = nullptr;
Slider* m_slider = nullptr;
Toggle* m_toggle = nullptr;
Checkbox* m_checkbox = nullptr;
RadioButton* m_radioA = nullptr;
RadioButton* m_radioB = nullptr;
Spinner* m_spinner = nullptr;
+121
View File
@@ -0,0 +1,121 @@
#include "ui/controls/checkbox.h"
#include "render/core/renderer.h"
#include "render/scene/input_area.h"
#include "ui/controls/box.h"
#include "ui/controls/glyph.h"
#include "ui/palette.h"
#include "ui/style.h"
#include <algorithm>
#include <cmath>
#include <memory>
Checkbox::Checkbox() {
auto box = std::make_unique<Box>();
m_box = static_cast<Box*>(addChild(std::move(box)));
auto checkGlyph = std::make_unique<Glyph>();
checkGlyph->setGlyph("check");
m_checkGlyph = static_cast<Glyph*>(addChild(std::move(checkGlyph)));
auto area = std::make_unique<InputArea>();
area->setOnEnter([this](const InputArea::PointerData& /*data*/) { applyState(); });
area->setOnLeave([this]() { applyState(); });
area->setOnPress([this](const InputArea::PointerData& /*data*/) { applyState(); });
area->setOnClick([this](const InputArea::PointerData& /*data*/) {
if (!m_enabled) {
return;
}
const bool next = !m_checked;
m_checked = next;
applyState();
if (m_onChange) {
m_onChange(next);
}
});
m_inputArea = static_cast<InputArea*>(addChild(std::move(area)));
applyState();
}
void Checkbox::setChecked(bool checked) {
if (m_checked == checked) {
return;
}
m_checked = checked;
applyState();
}
void Checkbox::setEnabled(bool enabled) {
if (m_enabled == enabled) {
return;
}
m_enabled = enabled;
if (m_inputArea != nullptr) {
m_inputArea->setEnabled(enabled);
}
applyState();
}
void Checkbox::setOnChange(std::function<void(bool)> callback) { m_onChange = std::move(callback); }
void Checkbox::setScale(float scale) {
m_scale = std::max(0.1f, scale);
applyState();
markDirty();
}
bool Checkbox::hovered() const noexcept { return m_inputArea != nullptr && m_inputArea->hovered(); }
bool Checkbox::pressed() const noexcept { return m_inputArea != nullptr && m_inputArea->pressed(); }
void Checkbox::layout(Renderer& renderer) {
const float touchSize = Style::controlHeightSm * m_scale;
const float boxSize = (Style::fontSizeTitle + Style::spaceXs) * m_scale;
const float boxInset = (touchSize - boxSize) * 0.5f;
setSize(touchSize, touchSize);
if (m_box != nullptr) {
m_box->setPosition(boxInset, boxInset);
m_box->setSize(boxSize, boxSize);
m_box->setRadius(Style::radiusSm * m_scale);
m_box->setSoftness(1.0f);
}
if (m_checkGlyph != nullptr) {
m_checkGlyph->setGlyphSize((Style::fontSizeBody + Style::spaceXs * 0.5f) * m_scale);
m_checkGlyph->measure(renderer);
m_checkGlyph->setPosition(std::round(boxInset + (boxSize - m_checkGlyph->width()) * 0.5f),
std::round(boxInset + (boxSize - m_checkGlyph->height()) * 0.5f));
}
if (m_inputArea != nullptr) {
m_inputArea->setPosition(0.0f, 0.0f);
m_inputArea->setSize(width(), height());
}
}
void Checkbox::applyState() {
if (m_box == nullptr || m_checkGlyph == nullptr) {
return;
}
Color fill = palette.surface;
Color border = palette.outline;
if (m_checked) {
fill = palette.primary;
border = palette.primary;
} else if (pressed() || hovered()) {
border = palette.primary;
}
m_box->setFill(fill);
m_box->setBorder(border, Style::borderWidth * m_scale);
m_checkGlyph->setColor(palette.onPrimary);
m_checkGlyph->setVisible(m_checked);
setOpacity(m_enabled ? 1.0f : 0.55f);
}
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include "render/scene/node.h"
#include <functional>
class Box;
class Glyph;
class InputArea;
class Renderer;
class Checkbox : public Node {
public:
Checkbox();
void setChecked(bool checked);
void setEnabled(bool enabled);
void setOnChange(std::function<void(bool)> callback);
void setScale(float scale);
[[nodiscard]] bool checked() const noexcept { return m_checked; }
[[nodiscard]] bool enabled() const noexcept { return m_enabled; }
[[nodiscard]] bool hovered() const noexcept;
[[nodiscard]] bool pressed() const noexcept;
void layout(Renderer& renderer) override;
private:
void applyState();
Box* m_box = nullptr;
Glyph* m_checkGlyph = nullptr;
InputArea* m_inputArea = nullptr;
std::function<void(bool)> m_onChange;
bool m_checked = false;
bool m_enabled = true;
float m_scale = 1.0f;
};