summaryrefslogtreecommitdiff
path: root/Userland/Libraries/LibGUI
diff options
context:
space:
mode:
Diffstat (limited to 'Userland/Libraries/LibGUI')
-rw-r--r--Userland/Libraries/LibGUI/CMakeLists.txt1
-rw-r--r--Userland/Libraries/LibGUI/Tray.cpp214
-rw-r--r--Userland/Libraries/LibGUI/Tray.h56
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;
+};
+
+}