1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 14:28:12 +00:00
serenity/Userland/Services/WindowServer/WindowSwitcher.cpp
Tom 584b144953 WindowServer: Add basic virtual desktop support
This creates a 2-dimensional array of WindowStack instances, one for
each virtual desktop. The main desktop 0,0 is the main desktop, which
is the desktop used for all stationary windows (e.g. taskbar, desktop).
When adding windows to a desktop, stationary windows are always added
to the main desktop.

When composing the desktop, there are usually two WindowStacks
involved. For stationary windows, the main desktop will be traversed,
and for everything else the current virtual desktop will be iterated.
Iteration is interweaved to preserve the correct order. During the
transition animation, two WindowStacks will be iterated at the same
time.
2021-07-03 12:27:23 +02:00

240 lines
7.6 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibGfx/Bitmap.h>
#include <LibGfx/Font.h>
#include <LibGfx/StylePainter.h>
#include <WindowServer/Compositor.h>
#include <WindowServer/Event.h>
#include <WindowServer/Screen.h>
#include <WindowServer/WindowManager.h>
#include <WindowServer/WindowSwitcher.h>
namespace WindowServer {
static WindowSwitcher* s_the;
WindowSwitcher& WindowSwitcher::the()
{
VERIFY(s_the);
return *s_the;
}
WindowSwitcher::WindowSwitcher()
{
s_the = this;
}
WindowSwitcher::~WindowSwitcher()
{
}
void WindowSwitcher::set_visible(bool visible)
{
if (m_visible == visible)
return;
m_visible = visible;
Compositor::the().invalidate_occlusions();
if (m_switcher_window)
m_switcher_window->set_visible(visible);
if (!m_visible)
return;
refresh();
}
Window* WindowSwitcher::selected_window()
{
if (m_selected_index < 0 || m_selected_index >= static_cast<int>(m_windows.size()))
return nullptr;
return m_windows[m_selected_index].ptr();
}
void WindowSwitcher::event(Core::Event& event)
{
if (!static_cast<Event&>(event).is_mouse_event())
return;
auto& mouse_event = static_cast<MouseEvent&>(event);
int new_hovered_index = -1;
for (size_t i = 0; i < m_windows.size(); ++i) {
auto item_rect = this->item_rect(i);
if (item_rect.contains(mouse_event.position())) {
new_hovered_index = i;
break;
}
}
if (mouse_event.type() == Event::MouseMove) {
if (m_hovered_index != new_hovered_index) {
m_hovered_index = new_hovered_index;
redraw();
}
}
if (new_hovered_index == -1)
return;
if (mouse_event.type() == Event::MouseDown)
select_window_at_index(new_hovered_index);
event.accept();
}
void WindowSwitcher::on_key_event(const KeyEvent& event)
{
if (event.type() == Event::KeyUp) {
if (event.key() == Key_Super) {
if (auto* window = selected_window()) {
window->set_minimized(false);
WindowManager::the().move_to_front_and_make_active(*window);
}
WindowManager::the().set_highlight_window(nullptr);
hide();
}
return;
}
if (event.key() == Key_LeftShift || event.key() == Key_RightShift)
return;
if (event.key() != Key_Tab) {
WindowManager::the().set_highlight_window(nullptr);
hide();
return;
}
VERIFY(!m_windows.is_empty());
int new_selected_index;
if (!event.shift()) {
new_selected_index = (m_selected_index + 1) % static_cast<int>(m_windows.size());
} else {
new_selected_index = (m_selected_index - 1) % static_cast<int>(m_windows.size());
if (new_selected_index < 0)
new_selected_index = static_cast<int>(m_windows.size()) - 1;
}
VERIFY(new_selected_index < static_cast<int>(m_windows.size()));
select_window_at_index(new_selected_index);
}
void WindowSwitcher::select_window(Window& window)
{
for (size_t i = 0; i < m_windows.size(); ++i) {
if (m_windows.at(i) == &window) {
select_window_at_index(i);
return;
}
}
}
void WindowSwitcher::select_window_at_index(int index)
{
m_selected_index = index;
auto* highlight_window = m_windows.at(index).ptr();
VERIFY(highlight_window);
WindowManager::the().set_highlight_window(highlight_window);
redraw();
}
void WindowSwitcher::redraw()
{
draw();
Compositor::the().invalidate_screen(m_rect);
}
Gfx::IntRect WindowSwitcher::item_rect(int index) const
{
return {
padding(),
padding() + index * item_height(),
m_rect.width() - padding() * 2,
item_height()
};
}
void WindowSwitcher::draw()
{
auto palette = WindowManager::the().palette();
Gfx::Painter painter(*m_switcher_window->backing_store());
painter.fill_rect({ {}, m_rect.size() }, palette.window());
painter.draw_rect({ {}, m_rect.size() }, palette.threed_shadow2());
for (size_t index = 0; index < m_windows.size(); ++index) {
auto& window = *m_windows.at(index);
auto item_rect = this->item_rect(index);
Color text_color;
Color rect_text_color;
if (static_cast<int>(index) == m_selected_index) {
painter.fill_rect(item_rect, palette.selection());
text_color = palette.selection_text();
rect_text_color = palette.threed_shadow1();
} else {
if (static_cast<int>(index) == m_hovered_index)
Gfx::StylePainter::paint_button(painter, item_rect, palette, Gfx::ButtonStyle::Coolbar, false, true);
text_color = palette.window_text();
rect_text_color = palette.threed_shadow2();
}
item_rect.shrink(item_padding(), 0);
Gfx::IntRect thumbnail_rect = { item_rect.location().translated(0, 5), { thumbnail_width(), thumbnail_height() } };
if (window.backing_store()) {
painter.draw_scaled_bitmap(thumbnail_rect, *window.backing_store(), window.backing_store()->rect());
Gfx::StylePainter::paint_frame(painter, thumbnail_rect.inflated(4, 4), palette, Gfx::FrameShape::Container, Gfx::FrameShadow::Sunken, 2);
}
Gfx::IntRect icon_rect = { thumbnail_rect.bottom_right().translated(-window.icon().width(), -window.icon().height()), { window.icon().width(), window.icon().height() } };
painter.fill_rect(icon_rect, palette.window());
painter.blit(icon_rect.location(), window.icon(), window.icon().rect());
painter.draw_text(item_rect.translated(thumbnail_width() + 12, 0), window.computed_title(), WindowManager::the().window_title_font(), Gfx::TextAlignment::CenterLeft, text_color);
painter.draw_text(item_rect, window.rect().to_string(), Gfx::TextAlignment::CenterRight, rect_text_color);
}
}
void WindowSwitcher::refresh()
{
auto& wm = WindowManager::the();
const Window* selected_window = nullptr;
if (m_selected_index > 0 && m_windows[m_selected_index])
selected_window = m_windows[m_selected_index].ptr();
if (!selected_window)
selected_window = wm.highlight_window() ? wm.highlight_window() : wm.active_window();
m_windows.clear();
m_selected_index = 0;
int window_count = 0;
int longest_title_width = 0;
wm.for_each_window_stack([&](auto& window_stack) {
window_stack.for_each_window_of_type_from_front_to_back(
WindowType::Normal, [&](Window& window) {
if (window.is_frameless())
return IterationDecision::Continue;
++window_count;
longest_title_width = max(longest_title_width, wm.font().width(window.computed_title()));
if (selected_window == &window)
m_selected_index = m_windows.size();
m_windows.append(window);
return IterationDecision::Continue;
},
true);
return IterationDecision::Continue;
});
if (m_windows.is_empty()) {
hide();
return;
}
int space_for_window_rect = 180;
m_rect.set_width(thumbnail_width() + longest_title_width + space_for_window_rect + padding() * 2 + item_padding() * 2);
m_rect.set_height(window_count * item_height() + padding() * 2);
m_rect.center_within(Screen::main().rect());
if (!m_switcher_window)
m_switcher_window = Window::construct(*this, WindowType::WindowSwitcher);
m_switcher_window->set_rect(m_rect);
redraw();
}
void WindowSwitcher::refresh_if_needed()
{
if (m_visible)
refresh();
}
}