Serenity Operating System
at master 215 lines 5.5 kB view raw
1/* 2 * Copyright (c) 2021-2023, Andreas Kling <kling@serenityos.org> 3 * Copyright (c) 2022, the SerenityOS developers. 4 * 5 * SPDX-License-Identifier: BSD-2-Clause 6 */ 7 8#include <LibGUI/Painter.h> 9#include <LibGUI/Tray.h> 10#include <LibGfx/Font/Font.h> 11#include <LibGfx/Palette.h> 12#include <LibGfx/StylePainter.h> 13 14REGISTER_WIDGET(GUI, Tray); 15 16namespace GUI { 17 18Tray::Tray() 19{ 20 set_fill_with_background_color(true); 21 set_background_role(Gfx::ColorRole::Tray); 22 set_focus_policy(GUI::FocusPolicy::TabFocus); 23} 24 25Gfx::IntRect Tray::Item::rect(Tray const& tray) const 26{ 27 int item_height = tray.font().pixel_size_rounded_up() + 12; 28 return Gfx::IntRect { 29 tray.frame_thickness(), 30 tray.frame_thickness() + static_cast<int>(index) * item_height, 31 tray.frame_inner_rect().width(), 32 item_height, 33 }; 34} 35 36size_t Tray::add_item(DeprecatedString text, RefPtr<Gfx::Bitmap const> bitmap, DeprecatedString custom_data) 37{ 38 auto new_index = m_items.size(); 39 40 m_items.append(Item { 41 .text = move(text), 42 .bitmap = move(bitmap), 43 .custom_data = move(custom_data), 44 .index = new_index, 45 }); 46 update(); 47 48 return new_index; 49} 50 51void Tray::set_item_checked(size_t index, bool checked) 52{ 53 if (checked) { 54 m_checked_item_index = index; 55 } else { 56 if (m_checked_item_index == index) 57 m_checked_item_index = {}; 58 } 59 update(); 60} 61 62void Tray::paint_event(GUI::PaintEvent& event) 63{ 64 GUI::Frame::paint_event(event); 65 66 GUI::Painter painter(*this); 67 painter.add_clip_rect(event.rect()); 68 69 for (auto& item : m_items) { 70 auto rect = item.rect(*this); 71 bool is_pressed = item.index == m_pressed_item_index; 72 bool is_hovered = item.index == m_hovered_item_index; 73 bool is_checked = item.index == m_checked_item_index; 74 Gfx::StylePainter::paint_button(painter, rect, palette(), Gfx::ButtonStyle::Tray, is_pressed && is_hovered, is_hovered, is_checked, is_enabled()); 75 76 Gfx::IntRect icon_rect { 77 rect.x() + 4, 78 0, 79 16, 80 16, 81 }; 82 icon_rect.center_vertically_within(rect); 83 84 Gfx::IntRect text_rect { 85 icon_rect.right() + 5, 86 rect.y(), 87 rect.width(), 88 rect.height(), 89 }; 90 text_rect.intersect(rect); 91 92 if (is_pressed && is_hovered) { 93 icon_rect.translate_by(1, 1); 94 text_rect.translate_by(1, 1); 95 } 96 97 if (item.bitmap) { 98 if (is_hovered) 99 painter.blit_brightened(icon_rect.location(), *item.bitmap, item.bitmap->rect()); 100 else 101 painter.blit(icon_rect.location(), *item.bitmap, item.bitmap->rect()); 102 } 103 104 auto const& font = is_checked ? this->font().bold_variant() : this->font(); 105 painter.draw_text(text_rect, item.text, font, Gfx::TextAlignment::CenterLeft, palette().tray_text()); 106 } 107} 108 109void Tray::mousemove_event(GUI::MouseEvent& event) 110{ 111 auto* hovered_item = item_at(event.position()); 112 if (!hovered_item) { 113 if (m_hovered_item_index.has_value()) 114 update(); 115 m_hovered_item_index = {}; 116 return; 117 } 118 if (m_hovered_item_index != hovered_item->index) { 119 m_hovered_item_index = hovered_item->index; 120 update(); 121 } 122} 123 124void Tray::mousedown_event(GUI::MouseEvent& event) 125{ 126 if (event.button() != GUI::MouseButton::Primary) 127 return; 128 129 auto* pressed_item = item_at(event.position()); 130 if (!pressed_item) 131 return; 132 133 if (m_pressed_item_index != pressed_item->index) { 134 m_pressed_item_index = pressed_item->index; 135 update(); 136 } 137} 138 139void Tray::mouseup_event(GUI::MouseEvent& event) 140{ 141 if (event.button() != GUI::MouseButton::Primary) 142 return; 143 144 if (auto* pressed_item = item_at(event.position()); pressed_item && m_pressed_item_index == pressed_item->index) { 145 on_item_activation(pressed_item->custom_data); 146 } 147 148 m_pressed_item_index = {}; 149 update(); 150} 151 152void Tray::leave_event(Core::Event&) 153{ 154 m_hovered_item_index = {}; 155 update(); 156} 157 158Tray::Item* Tray::item_at(Gfx::IntPoint position) 159{ 160 for (auto& item : m_items) { 161 if (item.rect(*this).contains(position)) 162 return &item; 163 } 164 return nullptr; 165} 166 167void Tray::focusin_event(GUI::FocusEvent&) 168{ 169 if (m_items.is_empty()) 170 return; 171 m_hovered_item_index = 0; 172 update(); 173} 174 175void Tray::focusout_event(GUI::FocusEvent&) 176{ 177 if (m_items.is_empty()) 178 return; 179 m_hovered_item_index = {}; 180 update(); 181} 182 183void Tray::keydown_event(GUI::KeyEvent& event) 184{ 185 if (m_items.is_empty() || event.modifiers()) 186 return Frame::keydown_event(event); 187 188 if (event.key() == KeyCode::Key_Down) { 189 if (!m_hovered_item_index.has_value()) 190 m_hovered_item_index = 0; 191 else 192 m_hovered_item_index = (*m_hovered_item_index + 1) % m_items.size(); 193 update(); 194 return; 195 } 196 197 if (event.key() == KeyCode::Key_Up) { 198 if (!m_hovered_item_index.has_value() || m_hovered_item_index == 0u) 199 m_hovered_item_index = m_items.size() - 1; 200 else 201 m_hovered_item_index = *m_hovered_item_index - 1; 202 update(); 203 return; 204 } 205 206 if (event.key() == KeyCode::Key_Return) { 207 if (m_hovered_item_index.has_value()) 208 on_item_activation(m_items[*m_hovered_item_index].custom_data); 209 return; 210 } 211 212 Frame::keydown_event(event); 213} 214 215}