diff options
Diffstat (limited to 'Userland/Libraries/LibGUI')
-rw-r--r-- | Userland/Libraries/LibGUI/CMakeLists.txt | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibGUI/Tray.cpp | 214 | ||||
-rw-r--r-- | Userland/Libraries/LibGUI/Tray.h | 56 |
3 files changed, 271 insertions, 0 deletions
diff --git a/Userland/Libraries/LibGUI/CMakeLists.txt b/Userland/Libraries/LibGUI/CMakeLists.txt index abee81eac0..21f1c4c8f8 100644 --- a/Userland/Libraries/LibGUI/CMakeLists.txt +++ b/Userland/Libraries/LibGUI/CMakeLists.txt @@ -98,6 +98,7 @@ set(SOURCES TextEditor.cpp Toolbar.cpp ToolbarContainer.cpp + Tray.cpp TreeView.cpp UndoStack.cpp ValueSlider.cpp diff --git a/Userland/Libraries/LibGUI/Tray.cpp b/Userland/Libraries/LibGUI/Tray.cpp new file mode 100644 index 0000000000..856e81cd1c --- /dev/null +++ b/Userland/Libraries/LibGUI/Tray.cpp @@ -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); +} + +} diff --git a/Userland/Libraries/LibGUI/Tray.h b/Userland/Libraries/LibGUI/Tray.h new file mode 100644 index 0000000000..57e019fe01 --- /dev/null +++ b/Userland/Libraries/LibGUI/Tray.h @@ -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; +}; + +} |