LibGUI: Add a GUI::Tray widget for the FilePicker common locations

The FilePicker has implemented its common locations tray as a composite
widget built from a GUI::Frame with a bunch of GUI::Button inside it.
The problem with that is that it creates a long and annoying chain of
keyboard-focusable widgets.

This patch adds GUI::Tray, which is a dedicated single widget that
implements the same UI element, but without child widgets.
This commit is contained in:
Andreas Kling 2021-10-21 19:28:46 +02:00
parent 25475f7003
commit ae2579d8b5
Notes: sideshowbarker 2024-07-18 02:04:55 +09:00
3 changed files with 271 additions and 0 deletions

View file

@ -98,6 +98,7 @@ set(SOURCES
TextEditor.cpp
Toolbar.cpp
ToolbarContainer.cpp
Tray.cpp
TreeView.cpp
UndoStack.cpp
ValueSlider.cpp

View file

@ -0,0 +1,214 @@
/*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGUI/Painter.h>
#include <LibGUI/Tray.h>
#include <LibGfx/Font.h>
#include <LibGfx/Palette.h>
#include <LibGfx/StylePainter.h>
REGISTER_WIDGET(GUI, Tray);
namespace GUI {
Tray::Tray()
{
set_fill_with_background_color(true);
set_background_role(Gfx::ColorRole::Tray);
set_focus_policy(GUI::FocusPolicy::TabFocus);
}
Tray::~Tray()
{
}
Gfx::IntRect Tray::Item::rect(Tray const& tray) const
{
static constexpr int item_height = 22;
return Gfx::IntRect {
tray.frame_thickness(),
tray.frame_thickness() + static_cast<int>(index) * item_height,
tray.frame_inner_rect().width(),
item_height,
};
}
size_t Tray::add_item(String text, RefPtr<Gfx::Bitmap> bitmap, String custom_data)
{
auto new_index = m_items.size();
m_items.append(Item {
.text = move(text),
.bitmap = move(bitmap),
.custom_data = move(custom_data),
.index = new_index,
});
update();
return new_index;
}
void Tray::set_item_checked(size_t index, bool checked)
{
if (checked) {
m_checked_item_index = index;
} else {
if (m_checked_item_index == index)
m_checked_item_index = {};
}
update();
}
void Tray::paint_event(GUI::PaintEvent& event)
{
GUI::Frame::paint_event(event);
GUI::Painter painter(*this);
painter.add_clip_rect(event.rect());
for (auto& item : m_items) {
auto rect = item.rect(*this);
bool is_pressed = item.index == m_pressed_item_index;
bool is_hovered = item.index == m_hovered_item_index;
bool is_checked = item.index == m_checked_item_index;
Gfx::StylePainter::paint_button(painter, rect, palette(), Gfx::ButtonStyle::Tray, is_pressed && is_hovered, is_hovered, is_checked, is_enabled());
Gfx::IntRect icon_rect {
rect.x() + 4,
0,
16,
16,
};
icon_rect.center_vertically_within(rect);
Gfx::IntRect text_rect {
icon_rect.right() + 5,
rect.y(),
rect.width(),
rect.height(),
};
text_rect.intersect(rect);
if (is_pressed && is_hovered) {
icon_rect.translate_by(1, 1);
text_rect.translate_by(1, 1);
}
if (item.bitmap)
painter.blit(icon_rect.location(), *item.bitmap, item.bitmap->rect());
auto const& font = is_checked ? this->font().bold_variant() : this->font();
painter.draw_text(text_rect, item.text, font, Gfx::TextAlignment::CenterLeft, palette().color(Gfx::ColorRole::TrayText));
}
}
void Tray::mousemove_event(GUI::MouseEvent& event)
{
auto* hovered_item = item_at(event.position());
if (!hovered_item) {
if (m_hovered_item_index.has_value())
update();
m_hovered_item_index = {};
return;
}
if (m_hovered_item_index != hovered_item->index) {
m_hovered_item_index = hovered_item->index;
update();
}
}
void Tray::mousedown_event(GUI::MouseEvent& event)
{
if (event.button() != GUI::MouseButton::Left)
return;
auto* pressed_item = item_at(event.position());
if (!pressed_item)
return;
if (m_pressed_item_index != pressed_item->index) {
m_pressed_item_index = pressed_item->index;
update();
}
}
void Tray::mouseup_event(GUI::MouseEvent& event)
{
if (event.button() != GUI::MouseButton::Left)
return;
if (auto* pressed_item = item_at(event.position()); pressed_item && m_pressed_item_index == pressed_item->index) {
on_item_activation(pressed_item->custom_data);
}
m_pressed_item_index = {};
update();
}
void Tray::leave_event(Core::Event&)
{
m_hovered_item_index = {};
update();
}
Tray::Item* Tray::item_at(Gfx::IntPoint const& position)
{
for (auto& item : m_items) {
if (item.rect(*this).contains(position))
return &item;
}
return nullptr;
}
void Tray::focusin_event(GUI::FocusEvent&)
{
if (m_items.is_empty())
return;
m_hovered_item_index = 0;
update();
}
void Tray::focusout_event(GUI::FocusEvent&)
{
if (m_items.is_empty())
return;
m_hovered_item_index = {};
update();
}
void Tray::keydown_event(GUI::KeyEvent& event)
{
if (m_items.is_empty() || event.modifiers())
return Frame::keydown_event(event);
if (event.key() == KeyCode::Key_Down) {
if (!m_hovered_item_index.has_value())
m_hovered_item_index = 0;
else
m_hovered_item_index = (*m_hovered_item_index + 1) % m_items.size();
update();
return;
}
if (event.key() == KeyCode::Key_Up) {
if (!m_hovered_item_index.has_value() || m_hovered_item_index == 0u)
m_hovered_item_index = m_items.size() - 1;
else
m_hovered_item_index = *m_hovered_item_index - 1;
update();
return;
}
if (event.key() == KeyCode::Key_Return) {
if (m_hovered_item_index.has_value())
on_item_activation(m_items[*m_hovered_item_index].custom_data);
return;
}
Frame::keydown_event(event);
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGUI/Frame.h>
#include <LibGfx/Bitmap.h>
namespace GUI {
class Tray : public GUI::Frame {
C_OBJECT(Tray);
public:
virtual ~Tray() override;
size_t add_item(String text, RefPtr<Gfx::Bitmap>, String custom_data);
void set_item_checked(size_t index, bool);
Function<void(String const&)> on_item_activation;
protected:
virtual void paint_event(GUI::PaintEvent&) override;
virtual void mousemove_event(GUI::MouseEvent&) override;
virtual void mousedown_event(GUI::MouseEvent&) override;
virtual void mouseup_event(GUI::MouseEvent&) override;
virtual void leave_event(Core::Event&) override;
virtual void focusin_event(GUI::FocusEvent&) override;
virtual void focusout_event(GUI::FocusEvent&) override;
virtual void keydown_event(GUI::KeyEvent&) override;
private:
Tray();
struct Item {
String text;
RefPtr<Gfx::Bitmap> bitmap;
String custom_data;
size_t index { 0 };
Gfx::IntRect rect(Tray const&) const;
};
Item* item_at(Gfx::IntPoint const&);
Vector<Item> m_items;
Optional<size_t> m_pressed_item_index;
Optional<size_t> m_hovered_item_index;
Optional<size_t> m_checked_item_index;
};
}