ladybird/Userland/Libraries/LibGUI/Button.cpp
Timothy Flynn 62cbfc68b9 Revert "LibGUI: Update buttons' text/tooltips in Action::set_text"
This reverts commit e20756f9f7.

Some buttons, e.g. GUI::ToolbarButton, set text to be used only as a
tooltip instead of text on the button itself. This commit forced those
buttons to have text on them when their action became set. For most
toolbars, this was an invisible side effect; the button icons covered
the whole button rect. But the toolbar for EmojiInputDialog has slightly
smaller icons, causing an ellipsis to be displayed next to the icon.
2022-11-26 09:02:25 +01:00

291 lines
8 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 <LibGUI/Window.h>
#include <LibGfx/Font/Font.h>
#include <LibGfx/Palette.h>
#include <LibGfx/StylePainter.h>
REGISTER_WIDGET(GUI, Button)
REGISTER_WIDGET(GUI, DialogButton)
namespace GUI {
Button::Button(String text)
: AbstractButton(move(text))
{
set_min_size({ 40, 22 });
set_preferred_size({ SpecialDimension::OpportunisticGrow, 22 });
set_focus_policy(GUI::FocusPolicy::StrongFocus);
on_focus_change = [this](bool has_focus, auto) {
if (!is_default())
return;
if (!has_focus && is<Button>(window()->focused_widget()))
m_another_button_has_focus = true;
else
m_another_button_has_focus = false;
update();
};
REGISTER_ENUM_PROPERTY(
"button_style", button_style, set_button_style, Gfx::ButtonStyle,
{ Gfx::ButtonStyle::Normal, "Normal" },
{ Gfx::ButtonStyle::Coolbar, "Coolbar" });
REGISTER_STRING_PROPERTY("icon", icon, set_icon_from_path);
REGISTER_BOOL_PROPERTY("default", is_default, set_default);
}
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() || is_mimic_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(), is_default() && !another_button_has_focus());
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.translate_by(-1, -1);
}
if (m_icon) {
auto solid_color = m_icon->solid_color(60);
bool should_invert_icon = false;
if (solid_color.has_value()) {
auto contrast_ratio = palette().button().contrast_ratio(*solid_color);
// Note: 4.5 is the minimum recommended contrast ratio for text on the web:
// (https://developer.mozilla.org/en-US/docs/Web/Accessibility/Understanding_WCAG/Perceivable/Color_contrast)
// Reusing that threshold here as it seems to work reasonably well.
should_invert_icon = contrast_ratio < 4.5f && contrast_ratio < palette().button().contrast_ratio(solid_color->inverted());
}
if (should_invert_icon)
m_icon->invert();
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());
}
if (should_invert_icon)
m_icon->invert();
}
auto& font = is_checked() ? this->font().bold_variant() : this->font();
if (m_icon && !text().is_empty()) {
content_rect.translate_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(4, 4);
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::middle_mouse_click(unsigned int modifiers)
{
if (!is_enabled())
return;
NonnullRefPtr protector = *this;
if (on_middle_mouse_click)
on_middle_mouse_click(modifiers);
}
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();
}
void Button::set_icon_from_path(String const& path)
{
auto maybe_bitmap = Gfx::Bitmap::try_load_from_file(path);
if (maybe_bitmap.is_error()) {
dbgln("Unable to load bitmap `{}` for button icon", path);
return;
}
set_icon(maybe_bitmap.release_value());
}
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().bottom_left(), {}, rect());
update();
return;
}
AbstractButton::mousedown_event(event);
}
void Button::mousemove_event(MouseEvent& event)
{
if (m_menu) {
return;
}
AbstractButton::mousemove_event(event);
}
bool Button::is_default() const
{
if (!window())
return false;
return this == window()->default_return_key_widget();
}
void Button::set_default(bool default_button)
{
deferred_invoke([this, default_button] {
VERIFY(window());
window()->set_default_return_key_widget(default_button ? this : nullptr);
});
}
void Button::set_mimic_pressed(bool mimic_pressed)
{
if (!is_being_pressed()) {
m_mimic_pressed = mimic_pressed;
stop_timer();
start_timer(80, Core::TimerShouldFireWhenNotVisible::Yes);
update();
}
}
void Button::timer_event(Core::TimerEvent&)
{
if (is_mimic_pressed()) {
m_mimic_pressed = false;
update();
}
}
Optional<UISize> Button::calculated_min_size() const
{
int horizontal = 0, vertical = 0;
if (!text().is_empty()) {
auto& font = this->font();
horizontal = font.width(text()) + 2;
vertical = font.glyph_height() + 4; // FIXME: Use actual maximum total height
}
if (m_icon) {
vertical = max(vertical, m_icon->height());
horizontal += m_icon->width() + icon_spacing();
}
horizontal += 8;
vertical += 4;
return UISize(horizontal, vertical);
}
}