mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-26 14:28:49 +00:00
When a Button has a menu, the AbstractButton behaviour will now not be used in the mousemove_event. This was already the case for mousedown_event. Why only sometimes? Normally the presence of the menu prevents mousemove_events from being delivered to the button. But the menu doesn't spawn immediately. So sometimes mousemove events got through to the AbstractButton after the menu was told to spawn but before it appeared. This caused the m_being_pressed field of AbstractButton to be set to true. But there was never a mouseup_event because the menu got those instead.
185 lines
4.9 KiB
C++
185 lines
4.9 KiB
C++
/*
|
|
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/StringBuilder.h>
|
|
#include <LibGUI/Action.h>
|
|
#include <LibGUI/ActionGroup.h>
|
|
#include <LibGUI/Button.h>
|
|
#include <LibGUI/Menu.h>
|
|
#include <LibGUI/Painter.h>
|
|
#include <LibGfx/Font.h>
|
|
#include <LibGfx/FontDatabase.h>
|
|
#include <LibGfx/Palette.h>
|
|
#include <LibGfx/StylePainter.h>
|
|
|
|
REGISTER_WIDGET(GUI, Button)
|
|
|
|
namespace GUI {
|
|
|
|
Button::Button(String text)
|
|
: AbstractButton(move(text))
|
|
{
|
|
set_min_width(32);
|
|
set_fixed_height(22);
|
|
set_focus_policy(GUI::FocusPolicy::StrongFocus);
|
|
|
|
REGISTER_ENUM_PROPERTY(
|
|
"button_style", button_style, set_button_style, Gfx::ButtonStyle,
|
|
{ Gfx::ButtonStyle::Normal, "Normal" },
|
|
{ Gfx::ButtonStyle::Coolbar, "Coolbar" });
|
|
}
|
|
|
|
Button::~Button()
|
|
{
|
|
if (m_action)
|
|
m_action->unregister_button({}, *this);
|
|
}
|
|
|
|
void Button::paint_event(PaintEvent& event)
|
|
{
|
|
Painter painter(*this);
|
|
painter.add_clip_rect(event.rect());
|
|
|
|
bool paint_pressed = is_being_pressed() || (m_menu && m_menu->is_visible());
|
|
|
|
Gfx::StylePainter::paint_button(painter, rect(), palette(), m_button_style, paint_pressed, is_hovered(), is_checked(), is_enabled(), is_focused());
|
|
|
|
if (text().is_empty() && !m_icon)
|
|
return;
|
|
|
|
auto content_rect = rect().shrunken(8, 2);
|
|
auto icon_location = m_icon ? content_rect.center().translated(-(m_icon->width() / 2), -(m_icon->height() / 2)) : Gfx::IntPoint();
|
|
if (m_icon && !text().is_empty())
|
|
icon_location.set_x(content_rect.x());
|
|
|
|
if (paint_pressed || is_checked()) {
|
|
painter.translate(1, 1);
|
|
} else if (m_icon && is_enabled() && is_hovered() && button_style() == Gfx::ButtonStyle::Coolbar) {
|
|
auto shadow_color = palette().button().darkened(0.7f);
|
|
painter.blit_filtered(icon_location.translated(1, 1), *m_icon, m_icon->rect(), [&shadow_color](auto) {
|
|
return shadow_color;
|
|
});
|
|
icon_location.move_by(-1, -1);
|
|
}
|
|
|
|
if (m_icon) {
|
|
if (is_enabled()) {
|
|
if (is_hovered())
|
|
painter.blit_brightened(icon_location, *m_icon, m_icon->rect());
|
|
else
|
|
painter.blit(icon_location, *m_icon, m_icon->rect());
|
|
} else {
|
|
painter.blit_disabled(icon_location, *m_icon, m_icon->rect(), palette());
|
|
}
|
|
}
|
|
auto& font = is_checked() ? Gfx::FontDatabase::default_bold_font() : this->font();
|
|
if (m_icon && !text().is_empty()) {
|
|
content_rect.move_by(m_icon->width() + icon_spacing(), 0);
|
|
content_rect.set_width(content_rect.width() - m_icon->width() - icon_spacing());
|
|
}
|
|
|
|
Gfx::IntRect text_rect { 0, 0, font.width(text()), font.glyph_height() };
|
|
if (text_rect.width() > content_rect.width())
|
|
text_rect.set_width(content_rect.width());
|
|
text_rect.align_within(content_rect, text_alignment());
|
|
paint_text(painter, text_rect, font, text_alignment());
|
|
|
|
if (is_focused()) {
|
|
Gfx::IntRect focus_rect;
|
|
if (m_icon && !text().is_empty())
|
|
focus_rect = text_rect.inflated(6, 6);
|
|
else
|
|
focus_rect = rect().shrunken(8, 8);
|
|
painter.draw_focus_rect(focus_rect, palette().focus_outline());
|
|
}
|
|
}
|
|
|
|
void Button::click(unsigned modifiers)
|
|
{
|
|
if (!is_enabled())
|
|
return;
|
|
|
|
NonnullRefPtr protector = *this;
|
|
|
|
if (is_checkable()) {
|
|
if (is_checked() && !is_uncheckable())
|
|
return;
|
|
set_checked(!is_checked());
|
|
}
|
|
if (on_click)
|
|
on_click(modifiers);
|
|
if (m_action)
|
|
m_action->activate(this);
|
|
}
|
|
|
|
void Button::context_menu_event(ContextMenuEvent& context_menu_event)
|
|
{
|
|
if (!is_enabled())
|
|
return;
|
|
if (on_context_menu_request)
|
|
on_context_menu_request(context_menu_event);
|
|
}
|
|
|
|
void Button::set_action(Action& action)
|
|
{
|
|
m_action = action;
|
|
action.register_button({}, *this);
|
|
set_enabled(action.is_enabled());
|
|
set_checkable(action.is_checkable());
|
|
if (action.is_checkable())
|
|
set_checked(action.is_checked());
|
|
}
|
|
|
|
void Button::set_icon(RefPtr<Gfx::Bitmap>&& icon)
|
|
{
|
|
if (m_icon == icon)
|
|
return;
|
|
m_icon = move(icon);
|
|
update();
|
|
}
|
|
|
|
bool Button::is_uncheckable() const
|
|
{
|
|
if (!m_action)
|
|
return true;
|
|
if (!m_action->group())
|
|
return true;
|
|
return m_action->group()->is_unchecking_allowed();
|
|
}
|
|
|
|
void Button::set_menu(RefPtr<GUI::Menu> menu)
|
|
{
|
|
if (m_menu == menu)
|
|
return;
|
|
if (m_menu)
|
|
m_menu->on_visibility_change = nullptr;
|
|
m_menu = menu;
|
|
if (m_menu) {
|
|
m_menu->on_visibility_change = [&](bool) {
|
|
update();
|
|
};
|
|
}
|
|
}
|
|
|
|
void Button::mousedown_event(MouseEvent& event)
|
|
{
|
|
if (m_menu) {
|
|
m_menu->popup(screen_relative_rect().top_left());
|
|
update();
|
|
return;
|
|
}
|
|
AbstractButton::mousedown_event(event);
|
|
}
|
|
|
|
void Button::mousemove_event(MouseEvent& event)
|
|
{
|
|
if (m_menu) {
|
|
return;
|
|
}
|
|
AbstractButton::mousemove_event(event);
|
|
}
|
|
|
|
}
|