Serenity Operating System
at portability 254 lines 8.8 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <LibGfx/Bitmap.h> 28#include <LibGfx/Font.h> 29#include <LibGfx/StylePainter.h> 30#include <WindowServer/Event.h> 31#include <WindowServer/Screen.h> 32#include <WindowServer/WindowManager.h> 33#include <WindowServer/WindowSwitcher.h> 34 35namespace WindowServer { 36 37static WindowSwitcher* s_the; 38 39WindowSwitcher& WindowSwitcher::the() 40{ 41 ASSERT(s_the); 42 return *s_the; 43} 44 45WindowSwitcher::WindowSwitcher() 46{ 47 s_the = this; 48} 49 50WindowSwitcher::~WindowSwitcher() 51{ 52} 53 54void WindowSwitcher::set_visible(bool visible) 55{ 56 if (m_visible == visible) 57 return; 58 m_visible = visible; 59 WindowManager::the().recompute_occlusions(); 60 if (m_switcher_window) 61 m_switcher_window->set_visible(visible); 62 if (!m_visible) 63 return; 64 refresh(); 65} 66 67Window* WindowSwitcher::selected_window() 68{ 69 if (m_selected_index < 0 || m_selected_index >= static_cast<int>(m_windows.size())) 70 return nullptr; 71 return m_windows[m_selected_index].ptr(); 72} 73 74void WindowSwitcher::event(Core::Event& event) 75{ 76 if (!static_cast<Event&>(event).is_mouse_event()) 77 return; 78 79 auto& mouse_event = static_cast<MouseEvent&>(event); 80 int new_hovered_index = -1; 81 for (size_t i = 0; i < m_windows.size(); ++i) { 82 auto item_rect = this->item_rect(i); 83 if (item_rect.contains(mouse_event.position())) { 84 new_hovered_index = i; 85 break; 86 } 87 } 88 89 if (mouse_event.type() == Event::MouseMove) { 90 if (m_hovered_index != new_hovered_index) { 91 m_hovered_index = new_hovered_index; 92 redraw(); 93 } 94 } 95 96 if (new_hovered_index == -1) 97 return; 98 99 if (mouse_event.type() == Event::MouseDown) 100 select_window_at_index(new_hovered_index); 101 102 event.accept(); 103} 104 105void WindowSwitcher::on_key_event(const KeyEvent& event) 106{ 107 if (event.type() == Event::KeyUp) { 108 if (event.key() == Key_Logo) { 109 if (auto* window = selected_window()) { 110 window->set_minimized(false); 111 WindowManager::the().move_to_front_and_make_active(*window); 112 } 113 WindowManager::the().set_highlight_window(nullptr); 114 hide(); 115 } 116 return; 117 } 118 119 if (event.key() == Key_LeftShift || event.key() == Key_RightShift) 120 return; 121 if (event.key() != Key_Tab) { 122 WindowManager::the().set_highlight_window(nullptr); 123 hide(); 124 return; 125 } 126 ASSERT(!m_windows.is_empty()); 127 128 int new_selected_index; 129 130 if (!event.shift()) { 131 new_selected_index = (m_selected_index + 1) % static_cast<int>(m_windows.size()); 132 } else { 133 new_selected_index = (m_selected_index - 1) % static_cast<int>(m_windows.size()); 134 if (new_selected_index < 0) 135 new_selected_index = static_cast<int>(m_windows.size()) - 1; 136 } 137 ASSERT(new_selected_index < static_cast<int>(m_windows.size())); 138 139 select_window_at_index(new_selected_index); 140} 141 142void WindowSwitcher::select_window(Window& window) 143{ 144 for (size_t i = 0; i < m_windows.size(); ++i) { 145 if (m_windows.at(i) == &window) { 146 select_window_at_index(i); 147 return; 148 } 149 } 150} 151 152void WindowSwitcher::select_window_at_index(int index) 153{ 154 m_selected_index = index; 155 auto* highlight_window = m_windows.at(index).ptr(); 156 ASSERT(highlight_window); 157 WindowManager::the().set_highlight_window(highlight_window); 158 redraw(); 159} 160 161void WindowSwitcher::redraw() 162{ 163 draw(); 164 WindowManager::the().invalidate(m_rect); 165} 166 167Gfx::Rect WindowSwitcher::item_rect(int index) const 168{ 169 return { 170 padding(), 171 padding() + index * item_height(), 172 m_rect.width() - padding() * 2, 173 item_height() 174 }; 175} 176 177void WindowSwitcher::draw() 178{ 179 auto palette = WindowManager::the().palette(); 180 Gfx::Painter painter(*m_switcher_window->backing_store()); 181 painter.fill_rect({ {}, m_rect.size() }, palette.window()); 182 painter.draw_rect({ {}, m_rect.size() }, palette.threed_shadow2()); 183 for (size_t index = 0; index < m_windows.size(); ++index) { 184 auto& window = *m_windows.at(index); 185 auto item_rect = this->item_rect(index); 186 Color text_color; 187 Color rect_text_color; 188 if (static_cast<int>(index) == m_selected_index) { 189 painter.fill_rect(item_rect, palette.selection()); 190 text_color = palette.selection_text(); 191 rect_text_color = palette.threed_shadow1(); 192 } else { 193 if (static_cast<int>(index) == m_hovered_index) 194 Gfx::StylePainter::paint_button(painter, item_rect, palette, Gfx::ButtonStyle::CoolBar, false, true); 195 text_color = palette.window_text(); 196 rect_text_color = palette.threed_shadow2(); 197 } 198 item_rect.shrink(item_padding(), 0); 199 Gfx::Rect thumbnail_rect = { item_rect.location().translated(0, 5), { thumbnail_width(), thumbnail_height() } }; 200 if (window.backing_store()) { 201 painter.draw_scaled_bitmap(thumbnail_rect, *window.backing_store(), window.backing_store()->rect()); 202 Gfx::StylePainter::paint_frame(painter, thumbnail_rect.inflated(4, 4), palette, Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2); 203 } 204 Gfx::Rect icon_rect = { thumbnail_rect.bottom_right().translated(-window.icon().width(), -window.icon().height()), { window.icon().width(), window.icon().height() } }; 205 painter.fill_rect(icon_rect, palette.window()); 206 painter.blit(icon_rect.location(), window.icon(), window.icon().rect()); 207 painter.draw_text(item_rect.translated(thumbnail_width() + 12, 0), window.title(), WindowManager::the().window_title_font(), Gfx::TextAlignment::CenterLeft, text_color); 208 painter.draw_text(item_rect, window.rect().to_string(), Gfx::TextAlignment::CenterRight, rect_text_color); 209 } 210} 211 212void WindowSwitcher::refresh() 213{ 214 auto& wm = WindowManager::the(); 215 Window* selected_window = nullptr; 216 if (m_selected_index > 0 && m_windows[m_selected_index]) 217 selected_window = m_windows[m_selected_index].ptr(); 218 if (!selected_window) 219 selected_window = wm.highlight_window() ? wm.highlight_window() : wm.active_window(); 220 m_windows.clear(); 221 m_selected_index = 0; 222 int window_count = 0; 223 int longest_title_width = 0; 224 wm.for_each_window_of_type_from_front_to_back( 225 WindowType::Normal, [&](Window& window) { 226 ++window_count; 227 longest_title_width = max(longest_title_width, wm.font().width(window.title())); 228 if (selected_window == &window) 229 m_selected_index = m_windows.size(); 230 m_windows.append(window.make_weak_ptr()); 231 return IterationDecision::Continue; 232 }, 233 true); 234 if (m_windows.is_empty()) { 235 hide(); 236 return; 237 } 238 int space_for_window_rect = 180; 239 m_rect.set_width(thumbnail_width() + longest_title_width + space_for_window_rect + padding() * 2 + item_padding() * 2); 240 m_rect.set_height(window_count * item_height() + padding() * 2); 241 m_rect.center_within(Screen::the().rect()); 242 if (!m_switcher_window) 243 m_switcher_window = Window::construct(*this, WindowType::WindowSwitcher); 244 m_switcher_window->set_rect(m_rect); 245 redraw(); 246} 247 248void WindowSwitcher::refresh_if_needed() 249{ 250 if (m_visible) 251 refresh(); 252} 253 254}