Serenity Operating System
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}