ladybird/Userland/Libraries/LibGUI/Button.cpp
sin-ack e11940fd01 Userland: Move text wrapping/elision into the new TextLayout :^)
This class now contains all the fun bits about laying out text in a
rect. It will handle line wrapping at a certain width, cutting off lines
that don't fit the given rect, and handling text elision.
Painter::draw_text now internally uses this.

Future work here would be not laying out text twice (once actually
preparing the lines to be rendered and once to get the bounding box),
and possibly adding left elision if necessary.

Additionally, this commit makes the Utf32View versions of
Painter::draw_text convert to Utf8View internally. The intention is to
completely remove those versions, but they're kept at the moment to keep
the scope of this PR small.
2021-07-26 21:14:39 +04:30

185 lines
5 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.translate_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() ? 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(), Gfx::TextWrapping::DontWrap);
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);
}
}