From 36d8b9e89bec8e3f15c2c5220d9820b066f6a25f Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Thu, 23 May 2019 22:37:21 +0200 Subject: [PATCH] LibGUI: Add a GRadioButton widget. Radio buttons are automagically exclusive with other radio button children of the same parent. :^) --- .../icons/changing-filled-radio-circle.png | Bin 0 -> 202 bytes .../icons/changing-unfilled-radio-circle.png | Bin 0 -> 195 bytes Base/res/icons/filled-radio-circle.png | Bin 0 -> 189 bytes Base/res/icons/unfilled-radio-circle.png | Bin 0 -> 177 bytes LibGUI/GRadioButton.cpp | 122 ++++++++++++++++++ LibGUI/GRadioButton.h | 32 +++++ LibGUI/GWidget.h | 2 + LibGUI/Makefile | 1 + 8 files changed, 157 insertions(+) create mode 100644 Base/res/icons/changing-filled-radio-circle.png create mode 100644 Base/res/icons/changing-unfilled-radio-circle.png create mode 100644 Base/res/icons/filled-radio-circle.png create mode 100644 Base/res/icons/unfilled-radio-circle.png create mode 100644 LibGUI/GRadioButton.cpp create mode 100644 LibGUI/GRadioButton.h 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 0000000000000000000000000000000000000000..fbaf7c8d807bf7ddc82a9f9e06bdbb0eb95867e0 GIT binary patch literal 202 zcmeAS@N?(olHy`uVBq!ia0y~yVBi5^4mJh`hOp9@=L`%C6FprVLp08}2099|DDo^e zf4zcjLm;yPgR}yJN#n9NkGENi?AWenTzN&Vh zOOL~Q#)gg;v*cZmES+*~%DL)%V`Jl@a{*80Y~T5vkKxCfyrokfM{eZ&mneKj)~~ki zo5j=m>%ThscAjl})*sw%*rsUkdAf&-+Xk-q>Fdy(SnEd{+m+OR8tK^^WKd?EK>GHBD?Q51xkGwu(eIu%v(aK#5B=2$9L!Pwbl*iV&WP+4>)k0>Defstnh8c5zBO=7kkwoZavZc;+^M(C}Bat zNh}T8c441p-Tgi{KP=R(gG2tZ_&dKze?r_Dj#niL|2eGoBX-O6=x<-;m;b)ZW_&u+ sW`%LfR~x~QQq~5Q6^AB-JgQ-AZmByOdPRYqfq{X+)78&qol`;+06f)6tpET3 literal 0 HcmV?d00001 diff --git a/Base/res/icons/unfilled-radio-circle.png b/Base/res/icons/unfilled-radio-circle.png new file mode 100644 index 0000000000000000000000000000000000000000..edf16b3d7a2f445b7670d56d4db7a562a54c241f GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0y~yVBi5^4mJh`hOp9@=L`%C6`n4RAsXkmPCO`jM1jL) zX6;Lk>`SvYsIAHTwn5Q}>wm33ytw+vzs#nYPp?~qR$o8v>ckrIm7U3> hEo+aEzrh#QS4ZpiuUwJY%fP_E;OXk;vd$@?2>|R$M~eUe literal 0 HcmV?d00001 diff --git a/LibGUI/GRadioButton.cpp b/LibGUI/GRadioButton.cpp new file mode 100644 index 0000000000..494d9c00d5 --- /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 0000000000..1e4e389cb6 --- /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 a0096ff3ce..1d61cf28df 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 1039ff00a0..ccffdbb62f 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)