LibGUI: Add a GAbstractButton base class for button widgets.

This patch moves GButton and GRadioButton to inherit from it. This allows
them to share code for mouse event handling, etc.
This commit is contained in:
Andreas Kling 2019-05-24 16:32:20 +02:00
parent abbcdba72e
commit 21c56477b0
Notes: sideshowbarker 2024-07-19 13:58:24 +09:00
18 changed files with 202 additions and 211 deletions

View file

@ -46,7 +46,7 @@ int main(int argc, char** argv)
version_label->set_preferred_size({ 0, 11 });
auto* quit_button = new GButton(widget);
quit_button->set_caption("Okay");
quit_button->set_text("Okay");
quit_button->set_size_policy(SizePolicy::Fixed, SizePolicy::Fixed);
quit_button->set_preferred_size({ 100, 20 });
quit_button->on_click = [] (GButton&) {

View file

@ -50,7 +50,7 @@ FontEditorWidget::FontEditorWidget(const String& path, RetainPtr<Font>&& edited_
};
auto* save_button = new GButton(this);
save_button->set_caption("Save");
save_button->set_text("Save");
save_button->set_relative_rect({ 5, 300, 105, 20 });
save_button->on_click = [this] (GButton&) {
dbgprintf("write to file: '%s'\n", m_path.characters());
@ -58,7 +58,7 @@ FontEditorWidget::FontEditorWidget(const String& path, RetainPtr<Font>&& edited_
};
auto* quit_button = new GButton(this);
quit_button->set_caption("Quit");
quit_button->set_text("Quit");
quit_button->set_relative_rect({ 110, 300, 105, 20 });
quit_button->on_click = [] (GButton&) {
exit(0);

View file

@ -125,10 +125,10 @@ void TaskbarWindow::wm_event(GWMEvent& event)
window.set_minimized(changed_event.is_minimized());
if (window.is_minimized()) {
window.button()->set_foreground_color(Color::DarkGray);
window.button()->set_caption(String::format("[%s]", changed_event.title().characters()));
window.button()->set_text(String::format("[%s]", changed_event.title().characters()));
} else {
window.button()->set_foreground_color(Color::Black);
window.button()->set_caption(changed_event.title());
window.button()->set_text(changed_event.title());
}
window.button()->set_checked(changed_event.is_active());
break;

View file

@ -123,7 +123,7 @@ void VBWidget::setup_properties()
}
if (m_type == VBWidgetType::GButton) {
VB_ADD_PROPERTY(GButton, "caption", caption, set_caption, string);
VB_ADD_PROPERTY(GButton, "text", text, set_text, string);
}
if (m_type == VBWidgetType::GGroupBox) {

View file

@ -44,7 +44,7 @@ static GWidget* build_gwidget(VBWidgetType type, GWidget* parent)
}
case VBWidgetType::GButton: {
auto* button = new GButton(parent);
button->set_caption("button_1");
button->set_text("button_1");
return button;
}
case VBWidgetType::GSpinBox: {

106
LibGUI/GAbstractButton.cpp Normal file
View file

@ -0,0 +1,106 @@
#include <LibGUI/GAbstractButton.h>
GAbstractButton::GAbstractButton(GWidget* parent)
: GWidget(parent)
{
}
GAbstractButton::GAbstractButton(const String& text, GWidget* parent)
: GWidget(parent)
, m_text(text)
{
}
GAbstractButton::~GAbstractButton()
{
}
void GAbstractButton::set_text(const String& text)
{
if (m_text == text)
return;
m_text = text;
update();
}
void GAbstractButton::set_checked(bool checked)
{
if (m_checked == checked)
return;
m_checked = checked;
update();
}
void GAbstractButton::set_checkable(bool checkable)
{
if (m_checkable == checkable)
return;
m_checkable = checkable;
update();
}
void GAbstractButton::mousemove_event(GMouseEvent& event)
{
bool is_over = rect().contains(event.position());
m_hovered = is_over;
if (event.buttons() & GMouseButton::Left) {
if (is_enabled()) {
bool being_pressed = is_over;
if (being_pressed != m_being_pressed) {
m_being_pressed = being_pressed;
update();
}
}
}
GWidget::mousemove_event(event);
}
void GAbstractButton::mousedown_event(GMouseEvent& event)
{
#ifdef GABSTRACTBUTTON_DEBUG
dbgprintf("GAbstractButton::mouse_down_event: x=%d, y=%d, button=%u\n", event.x(), event.y(), (unsigned)event.button());
#endif
if (event.button() == GMouseButton::Left) {
if (is_enabled()) {
m_being_pressed = true;
update();
}
}
GWidget::mousedown_event(event);
}
void GAbstractButton::mouseup_event(GMouseEvent& event)
{
#ifdef GABSTRACTBUTTON_DEBUG
dbgprintf("GAbstractButton::mouse_up_event: x=%d, y=%d, button=%u\n", event.x(), event.y(), (unsigned)event.button());
#endif
if (event.button() == GMouseButton::Left) {
if (is_enabled()) {
bool was_being_pressed = m_being_pressed;
m_being_pressed = false;
update();
if (was_being_pressed)
click();
}
}
GWidget::mouseup_event(event);
}
void GAbstractButton::enter_event(CEvent&)
{
m_hovered = true;
update();
}
void GAbstractButton::leave_event(CEvent&)
{
m_hovered = false;
update();
}
void GAbstractButton::keydown_event(GKeyEvent& event)
{
if (event.key() == KeyCode::Key_Return)
click();
GWidget::keydown_event(event);
}

42
LibGUI/GAbstractButton.h Normal file
View file

@ -0,0 +1,42 @@
#pragma once
#include <LibGUI/GWidget.h>
class GAbstractButton : public GWidget {
public:
virtual ~GAbstractButton() override;
void set_text(const String&);
const String& text() const { return m_text; }
bool is_checked() const { return m_checked; }
void set_checked(bool);
bool is_checkable() const { return m_checkable; }
void set_checkable(bool);
bool is_hovered() const { return m_hovered; }
bool is_being_pressed() const { return m_being_pressed; }
virtual void click() = 0;
virtual const char* class_name() const override { return "GAbstractButton"; }
protected:
explicit GAbstractButton(GWidget* parent);
GAbstractButton(const String&, GWidget* parent);
virtual void mousedown_event(GMouseEvent&) override;
virtual void mousemove_event(GMouseEvent&) override;
virtual void mouseup_event(GMouseEvent&) override;
virtual void keydown_event(GKeyEvent&) override;
virtual void enter_event(CEvent&) override;
virtual void leave_event(CEvent&) override;
private:
String m_text;
bool m_checked { false };
bool m_checkable { false };
bool m_hovered { false };
bool m_being_pressed { false };
};

View file

@ -5,10 +5,8 @@
#include <LibGUI/GAction.h>
#include <Kernel/KeyCode.h>
//#define GBUTTON_DEBUG
GButton::GButton(GWidget* parent)
: GWidget(parent)
: GAbstractButton(parent)
{
}
@ -18,37 +16,21 @@ GButton::~GButton()
m_action->unregister_button({ }, *this);
}
void GButton::set_caption(const String& caption)
{
if (caption == m_caption)
return;
m_caption = caption;
update();
}
void GButton::set_checked(bool checked)
{
if (m_checked == checked)
return;
m_checked = checked;
update();
}
void GButton::paint_event(GPaintEvent& event)
{
GPainter painter(*this);
painter.add_clip_rect(event.rect());
StylePainter::paint_button(painter, rect(), m_button_style, m_being_pressed, m_hovered, m_checkable && m_checked, is_enabled());
StylePainter::paint_button(painter, rect(), m_button_style, is_being_pressed(), is_hovered(), is_checkable() && is_checked(), is_enabled());
if (m_caption.is_empty() && !m_icon)
if (text().is_empty() && !m_icon)
return;
auto content_rect = rect().shrunken(10, 2);
auto icon_location = m_icon ? content_rect.center().translated(-(m_icon->width() / 2), -(m_icon->height() / 2)) : Point();
if (m_icon && !m_caption.is_empty())
if (m_icon && !text().is_empty())
icon_location.set_x(content_rect.x());
if (m_being_pressed)
if (is_being_pressed())
painter.translate(1, 1);
if (m_icon) {
if (is_enabled())
@ -56,57 +38,27 @@ void GButton::paint_event(GPaintEvent& event)
else
painter.blit_dimmed(icon_location, *m_icon, m_icon->rect());
}
auto& font = (m_checkable && m_checked) ? Font::default_bold_font() : this->font();
if (m_icon && !m_caption.is_empty()) {
auto& font = (is_checkable() && is_checked()) ? Font::default_bold_font() : this->font();
if (m_icon && !text().is_empty()) {
content_rect.move_by(m_icon->width() + 4, 0);
content_rect.set_width(content_rect.width() - m_icon->width() - 4);
}
if (is_enabled()) {
if (!m_caption.is_empty()) {
painter.draw_text(content_rect, m_caption, font, text_alignment(), foreground_color(), TextElision::Right);
if (!text().is_empty()) {
painter.draw_text(content_rect, text(), font, text_alignment(), foreground_color(), TextElision::Right);
if (is_focused()) {
Rect focus_rect = { 0, 0, font.width(m_caption), font.glyph_height() };
Rect focus_rect = { 0, 0, font.width(text()), font.glyph_height() };
focus_rect.inflate(6, 4);
focus_rect.center_within(content_rect);
painter.draw_rect(focus_rect, Color(140, 140, 140));
}
}
} else {
painter.draw_text(content_rect.translated(1, 1), m_caption, font, text_alignment(), Color::White, TextElision::Right);
painter.draw_text(content_rect, m_caption, font, text_alignment(), Color::from_rgb(0x808080), TextElision::Right);
painter.draw_text(content_rect.translated(1, 1), text(), font, text_alignment(), Color::White, TextElision::Right);
painter.draw_text(content_rect, text(), font, text_alignment(), Color::from_rgb(0x808080), TextElision::Right);
}
}
void GButton::mousemove_event(GMouseEvent& event)
{
bool is_over = rect().contains(event.position());
m_hovered = is_over;
if (event.buttons() & GMouseButton::Left) {
if (is_enabled()) {
bool being_pressed = is_over;
if (being_pressed != m_being_pressed) {
m_being_pressed = being_pressed;
update();
}
}
}
GWidget::mousemove_event(event);
}
void GButton::mousedown_event(GMouseEvent& event)
{
#ifdef GBUTTON_DEBUG
dbgprintf("GButton::mouse_down_event: x=%d, y=%d, button=%u\n", event.x(), event.y(), (unsigned)event.button());
#endif
if (event.button() == GMouseButton::Left) {
if (is_enabled()) {
m_being_pressed = true;
update();
}
}
GWidget::mousedown_event(event);
}
void GButton::click()
{
if (!is_enabled())
@ -115,35 +67,6 @@ void GButton::click()
on_click(*this);
}
void GButton::mouseup_event(GMouseEvent& event)
{
#ifdef GBUTTON_DEBUG
dbgprintf("GButton::mouse_up_event: x=%d, y=%d, button=%u\n", event.x(), event.y(), (unsigned)event.button());
#endif
if (event.button() == GMouseButton::Left) {
if (is_enabled()) {
bool was_being_pressed = m_being_pressed;
m_being_pressed = false;
update();
if (was_being_pressed)
click();
}
}
GWidget::mouseup_event(event);
}
void GButton::enter_event(CEvent&)
{
m_hovered = true;
update();
}
void GButton::leave_event(CEvent&)
{
m_hovered = false;
update();
}
void GButton::set_action(GAction& action)
{
m_action = action.make_weak_ptr();
@ -161,10 +84,3 @@ void GButton::set_icon(RetainPtr<GraphicsBitmap>&& icon)
m_icon = move(icon);
update();
}
void GButton::keydown_event(GKeyEvent& event)
{
if (event.key() == KeyCode::Key_Return)
click();
GWidget::keydown_event(event);
}

View file

@ -1,32 +1,23 @@
#pragma once
#include <LibGUI/GWidget.h>
#include <SharedGraphics/StylePainter.h>
#include <AK/AKString.h>
#include <AK/Function.h>
#include <LibGUI/GAbstractButton.h>
#include <SharedGraphics/GraphicsBitmap.h>
#include <SharedGraphics/StylePainter.h>
#include <SharedGraphics/TextAlignment.h>
class GAction;
class GButton : public GWidget {
class GButton : public GAbstractButton {
public:
explicit GButton(GWidget* parent);
virtual ~GButton() override;
String caption() const { return m_caption; }
void set_caption(const String&);
void set_icon(RetainPtr<GraphicsBitmap>&&);
const GraphicsBitmap* icon() const { return m_icon.ptr(); }
GraphicsBitmap* icon() { return m_icon.ptr(); }
bool is_checkable() const { return m_checkable; }
void set_checkable(bool checkable) { m_checkable = checkable; }
bool is_checked() const { return m_checked; }
void set_checked(bool);
void set_text_alignment(TextAlignment text_alignment) { m_text_alignment = text_alignment; }
TextAlignment text_alignment() const { return m_text_alignment; }
@ -35,7 +26,7 @@ public:
void set_button_style(ButtonStyle style) { m_button_style = style; }
ButtonStyle button_style() const { return m_button_style; }
void click();
virtual void click() override;
void set_action(GAction&);
@ -44,22 +35,11 @@ public:
protected:
virtual void paint_event(GPaintEvent&) override;
virtual void mousedown_event(GMouseEvent&) override;
virtual void mouseup_event(GMouseEvent&) override;
virtual void mousemove_event(GMouseEvent&) override;
virtual void enter_event(CEvent&) override;
virtual void leave_event(CEvent&) override;
virtual void keydown_event(GKeyEvent&) override;
private:
String m_caption;
RetainPtr<GraphicsBitmap> m_icon;
ButtonStyle m_button_style { ButtonStyle::Normal };
TextAlignment m_text_alignment { TextAlignment::Center };
WeakPtr<GAction> m_action;
bool m_being_pressed { false };
bool m_hovered { false };
bool m_checkable { false };
bool m_checked { false };
};

View file

@ -114,7 +114,7 @@ GFilePicker::GFilePicker(const String& path, CObject* parent)
auto* cancel_button = new GButton(button_container);
cancel_button->set_size_policy(SizePolicy::Fixed, SizePolicy::Fill);
cancel_button->set_preferred_size({ 80, 0 });
cancel_button->set_caption("Cancel");
cancel_button->set_text("Cancel");
cancel_button->on_click = [this] (auto&) {
done(ExecCancel);
};
@ -122,7 +122,7 @@ GFilePicker::GFilePicker(const String& path, CObject* parent)
auto* ok_button = new GButton(button_container);
ok_button->set_size_policy(SizePolicy::Fixed, SizePolicy::Fill);
ok_button->set_preferred_size({ 80, 0 });
ok_button->set_caption("OK");
ok_button->set_text("OK");
ok_button->on_click = [this, filename_textbox] (auto&) {
FileSystemPath path(String::format("%s/%s", m_model->path().characters(), filename_textbox->text().characters()));
m_selected_file = path.string();

View file

@ -54,7 +54,7 @@ void GInputBox::build()
m_cancel_button = new GButton(button_container_inner);
m_cancel_button->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed);
m_cancel_button->set_preferred_size({ 0, 20 });
m_cancel_button->set_caption("Cancel");
m_cancel_button->set_text("Cancel");
m_cancel_button->on_click = [this] (auto&) {
dbgprintf("GInputBox: Cancel button clicked\n");
done(ExecCancel);
@ -63,7 +63,7 @@ void GInputBox::build()
m_ok_button = new GButton(button_container_inner);
m_ok_button->set_size_policy(SizePolicy::Fill, SizePolicy::Fixed);
m_ok_button->set_preferred_size({ 0, 20 });
m_ok_button->set_caption("OK");
m_ok_button->set_text("OK");
m_ok_button->on_click = [this] (auto&) {
dbgprintf("GInputBox: OK button clicked\n");
m_text_value = m_text_editor->text();

View file

@ -72,7 +72,7 @@ void GMessageBox::build()
auto* button = new GButton(widget);
button->set_size_policy(SizePolicy::Fixed, SizePolicy::Fixed);
button->set_preferred_size({ 100, 20 });
button->set_caption("OK");
button->set_text("OK");
button->on_click = [this] (auto&) {
dbgprintf("GMessageBox: OK button clicked\n");
done(0);

View file

@ -7,9 +7,8 @@ static RetainPtr<GraphicsBitmap> s_filled_circle_bitmap;
static RetainPtr<GraphicsBitmap> s_changing_filled_circle_bitmap;
static RetainPtr<GraphicsBitmap> s_changing_unfilled_circle_bitmap;
GRadioButton::GRadioButton(const String& label, GWidget* parent)
: GWidget(parent)
, m_label(label)
GRadioButton::GRadioButton(const String& text, GWidget* parent)
: GAbstractButton(text, parent)
{
if (!s_unfilled_circle_bitmap) {
s_unfilled_circle_bitmap = GraphicsBitmap::load_from_file("/res/icons/unfilled-radio-circle.png");
@ -43,13 +42,13 @@ void GRadioButton::paint_event(GPaintEvent& event)
Rect circle_rect { { 2, 0 }, circle_size() };
circle_rect.center_vertically_within(rect());
auto& bitmap = circle_bitmap(m_checked, m_changing);
auto& bitmap = circle_bitmap(is_checked(), is_being_pressed());
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() };
if (!text().is_empty()) {
Rect text_rect { circle_rect.right() + 4, 0, font().width(text()), font().glyph_height() };
text_rect.center_vertically_within(rect());
painter.draw_text(text_rect, m_label, TextAlignment::CenterLeft, foreground_color());
painter.draw_text(text_rect, text(), TextAlignment::CenterLeft, foreground_color());
}
}
@ -67,56 +66,13 @@ void GRadioButton::for_each_in_group(Callback callback)
}
}
void GRadioButton::mousedown_event(GMouseEvent& event)
void GRadioButton::click()
{
if (event.button() != GMouseButton::Left)
if (!is_enabled())
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();
for_each_in_group([this] (auto& button) {
if (&button != this)
button.set_checked(false);
});
set_checked(true);
}

View file

@ -1,32 +1,22 @@
#pragma once
#include <LibGUI/GWidget.h>
#include <LibGUI/GAbstractButton.h>
class GRadioButton : public GWidget {
class GRadioButton : public GAbstractButton {
public:
GRadioButton(const String& label, GWidget* parent);
GRadioButton(const String& text, GWidget* parent);
virtual ~GRadioButton() override;
void set_label(const String&);
String label() const { return m_label; }
virtual const char* class_name() const override { return "GRadioButton"; }
bool is_checked() const { return m_checked; }
void set_checked(bool);
virtual void click() override;
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<typename Callback> 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 };
};

View file

@ -15,10 +15,10 @@ GSpinBox::GSpinBox(GWidget* parent)
m_editor->set_text(String::format("%d", m_value));
};
m_increment_button = new GButton(this);
m_increment_button->set_caption("\xf6");
m_increment_button->set_text("\xf6");
m_increment_button->on_click = [this] (GButton&) { set_value(m_value + 1); };
m_decrement_button = new GButton(this);
m_decrement_button->set_caption("\xf7");
m_decrement_button->set_text("\xf7");
m_decrement_button->on_click = [this] (GButton&) { set_value(m_value - 1); };
}

View file

@ -31,7 +31,7 @@ void GToolBar::add_action(Retained<GAction>&& action)
if (item->action->icon())
button->set_icon(item->action->icon());
else
button->set_caption(item->action->text());
button->set_text(item->action->text());
button->on_click = [raw_action_ptr] (const GButton&) {
raw_action_ptr->activate();
};

View file

@ -58,6 +58,7 @@ LIBGUI_OBJS = \
GResizeCorner.o \
GTabWidget.o \
GRadioButton.o \
GAbstractButton.o \
GWindow.o
OBJS = $(SHAREDGRAPHICS_OBJS) $(LIBGUI_OBJS)

View file

@ -67,7 +67,7 @@ GWindow* make_launcher_window()
auto* terminal_button = new GButton(widget);
terminal_button->set_relative_rect({ 5, 20, 90, 20 });
terminal_button->set_caption("Terminal");
terminal_button->set_text("Terminal");
terminal_button->on_click = [label] (GButton&) {
pid_t child_pid = fork();
@ -83,7 +83,7 @@ GWindow* make_launcher_window()
auto* guitest_button = new GButton(widget);
guitest_button->set_relative_rect({ 5, 50, 90, 20 });
guitest_button->set_caption("guitest");
guitest_button->set_text("guitest");
guitest_button->on_click = [label] (GButton&) {
pid_t child_pid = fork();
@ -99,7 +99,7 @@ GWindow* make_launcher_window()
auto* dummy_button = new GButton(widget);
dummy_button->set_relative_rect({ 5, 80, 90, 20 });
dummy_button->set_caption("Dummy");
dummy_button->set_text("Dummy");
auto* textbox = new GTextBox(widget);
textbox->set_relative_rect({ 5, 110, 90, 20 });
@ -119,7 +119,7 @@ GWindow* make_launcher_window()
auto* close_button = new GButton(widget);
close_button->set_relative_rect({ 5, 200, 90, 20 });
close_button->set_caption("Close");
close_button->set_text("Close");
close_button->on_click = [window] (GButton&) {
window->close();
};