diff --git a/Base/res/icons/changing-filled-radio-circle.png b/Base/res/icons/changing-filled-radio-circle.png new file mode 100644 index 00000000000..fbaf7c8d807 Binary files /dev/null and b/Base/res/icons/changing-filled-radio-circle.png differ diff --git a/Base/res/icons/changing-unfilled-radio-circle.png b/Base/res/icons/changing-unfilled-radio-circle.png new file mode 100644 index 00000000000..6a4f99986b5 Binary files /dev/null and b/Base/res/icons/changing-unfilled-radio-circle.png differ diff --git a/Base/res/icons/filled-radio-circle.png b/Base/res/icons/filled-radio-circle.png new file mode 100644 index 00000000000..6af146b41d1 Binary files /dev/null and b/Base/res/icons/filled-radio-circle.png differ diff --git a/Base/res/icons/unfilled-radio-circle.png b/Base/res/icons/unfilled-radio-circle.png new file mode 100644 index 00000000000..edf16b3d7a2 Binary files /dev/null and b/Base/res/icons/unfilled-radio-circle.png differ diff --git a/LibGUI/GRadioButton.cpp b/LibGUI/GRadioButton.cpp new file mode 100644 index 00000000000..494d9c00d5b --- /dev/null +++ b/LibGUI/GRadioButton.cpp @@ -0,0 +1,122 @@ +#include +#include +#include + +static RetainPtr s_unfilled_circle_bitmap; +static RetainPtr s_filled_circle_bitmap; +static RetainPtr s_changing_filled_circle_bitmap; +static RetainPtr s_changing_unfilled_circle_bitmap; + +GRadioButton::GRadioButton(const String& label, GWidget* parent) + : GWidget(parent) + , m_label(label) +{ + if (!s_unfilled_circle_bitmap) { + s_unfilled_circle_bitmap = GraphicsBitmap::load_from_file("/res/icons/unfilled-radio-circle.png"); + s_filled_circle_bitmap = GraphicsBitmap::load_from_file("/res/icons/filled-radio-circle.png"); + s_changing_filled_circle_bitmap = GraphicsBitmap::load_from_file("/res/icons/changing-filled-radio-circle.png"); + s_changing_unfilled_circle_bitmap = GraphicsBitmap::load_from_file("/res/icons/changing-unfilled-radio-circle.png"); + } +} + +GRadioButton::~GRadioButton() +{ +} + +Size GRadioButton::circle_size() +{ + return s_unfilled_circle_bitmap->size(); +} + +static const GraphicsBitmap& circle_bitmap(bool checked, bool changing) +{ + if (changing) + return checked ? *s_changing_filled_circle_bitmap : *s_changing_unfilled_circle_bitmap; + return checked ? *s_filled_circle_bitmap : *s_unfilled_circle_bitmap; +} + +void GRadioButton::paint_event(GPaintEvent& event) +{ + GPainter painter(*this); + painter.add_clip_rect(event.rect()); + + Rect circle_rect { { 2, 0 }, circle_size() }; + circle_rect.center_vertically_within(rect()); + + auto& bitmap = circle_bitmap(m_checked, m_changing); + painter.blit(circle_rect.location(), bitmap, bitmap.rect()); + + if (!m_label.is_empty()) { + Rect text_rect { circle_rect.right() + 4, 0, font().width(m_label), font().glyph_height() }; + text_rect.center_vertically_within(rect()); + painter.draw_text(text_rect, m_label, TextAlignment::CenterLeft, foreground_color()); + } +} + +template +void GRadioButton::for_each_in_group(Callback callback) +{ + if (!parent()) + return; + for (auto& object : parent()->children()) { + if (!object->is_widget()) + continue; + if (!static_cast(object)->is_radio_button()) + continue; + callback(*static_cast(object)); + } +} + +void GRadioButton::mousedown_event(GMouseEvent& event) +{ + if (event.button() != GMouseButton::Left) + return; + + m_changing = rect().contains(event.position()); + m_tracking = true; + update(); +} + +void GRadioButton::mousemove_event(GMouseEvent& event) +{ + if (m_tracking) { + bool old_changing = m_changing; + m_changing = rect().contains(event.position()); + if (old_changing != m_changing) + update(); + } +} + +void GRadioButton::mouseup_event(GMouseEvent& event) +{ + if (event.button() != GMouseButton::Left) + return; + + if (rect().contains(event.position())) { + for_each_in_group([this] (auto& button) { + if (&button != this) + button.set_checked(false); + }); + set_checked(true); + } + + m_changing = false; + m_tracking = false; + update(); +} + +void GRadioButton::set_label(const String& label) +{ + if (m_label == label) + return; + m_label = label; + update(); +} + +void GRadioButton::set_checked(bool checked) +{ + if (m_checked == checked) + return; + m_checked = checked; + update(); +} diff --git a/LibGUI/GRadioButton.h b/LibGUI/GRadioButton.h new file mode 100644 index 00000000000..1e4e389cb64 --- /dev/null +++ b/LibGUI/GRadioButton.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +class GRadioButton : public GWidget { +public: + GRadioButton(const String& label, GWidget* parent); + virtual ~GRadioButton() override; + + void set_label(const String&); + String label() const { return m_label; } + + bool is_checked() const { return m_checked; } + void set_checked(bool); + +protected: + virtual void paint_event(GPaintEvent&) override; + virtual void mousedown_event(GMouseEvent&) override; + virtual void mousemove_event(GMouseEvent&) override; + virtual void mouseup_event(GMouseEvent&) override; + +private: + virtual bool is_radio_button() const final { return true; } + + template void for_each_in_group(Callback); + static Size circle_size(); + + String m_label; + bool m_checked { false }; + bool m_changing { false }; + bool m_tracking { false }; +}; diff --git a/LibGUI/GWidget.h b/LibGUI/GWidget.h index a0096ff3ce6..1d61cf28dfa 100644 --- a/LibGUI/GWidget.h +++ b/LibGUI/GWidget.h @@ -184,6 +184,8 @@ public: void register_local_shortcut_action(Badge, GAction&); void unregister_local_shortcut_action(Badge, GAction&); + virtual bool is_radio_button() const { return false; } + private: virtual bool is_widget() const final { return true; } diff --git a/LibGUI/Makefile b/LibGUI/Makefile index 1039ff00a04..ccffdbb62f6 100644 --- a/LibGUI/Makefile +++ b/LibGUI/Makefile @@ -57,6 +57,7 @@ LIBGUI_OBJS = \ GSlider.o \ GResizeCorner.o \ GTabWidget.o \ + GRadioButton.o \ GWindow.o OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS)