mirror of
https://github.com/RGBCube/serenity
synced 2025-05-21 19:55:07 +00:00

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.
350 lines
11 KiB
C++
350 lines
11 KiB
C++
/*
|
|
* Copyright (c) 2021, the SerenityOS developers.
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include "Overlays.h"
|
|
#include "Compositor.h"
|
|
#include "WindowManager.h"
|
|
|
|
namespace WindowServer {
|
|
|
|
Overlay::~Overlay()
|
|
{
|
|
Compositor::the().remove_overlay(*this);
|
|
}
|
|
|
|
bool Overlay::invalidate()
|
|
{
|
|
if (m_invalidated)
|
|
return false;
|
|
m_invalidated = true;
|
|
// m_current_rect should only get updated by recompute_overlay_rects()
|
|
if (!m_current_rect.is_empty())
|
|
Compositor::the().invalidate_screen(m_current_rect);
|
|
return true;
|
|
}
|
|
|
|
void Overlay::set_enabled(bool enable)
|
|
{
|
|
if (is_enabled() == enable)
|
|
return;
|
|
|
|
if (enable)
|
|
Compositor::the().add_overlay(*this);
|
|
else
|
|
Compositor::the().remove_overlay(*this);
|
|
}
|
|
|
|
void Overlay::set_rect(Gfx::IntRect const& rect)
|
|
{
|
|
if (m_rect == rect)
|
|
return;
|
|
auto previous_rect = m_rect;
|
|
m_rect = rect;
|
|
invalidate();
|
|
if (is_enabled())
|
|
Compositor::the().overlay_rects_changed();
|
|
rect_changed(previous_rect);
|
|
}
|
|
|
|
BitmapOverlay::BitmapOverlay()
|
|
{
|
|
clear_bitmaps();
|
|
}
|
|
|
|
void BitmapOverlay::rect_changed(Gfx::IntRect const& previous_rect)
|
|
{
|
|
if (rect().size() != previous_rect.size())
|
|
clear_bitmaps();
|
|
Overlay::rect_changed(previous_rect);
|
|
}
|
|
|
|
void BitmapOverlay::clear_bitmaps()
|
|
{
|
|
m_bitmaps = MultiScaleBitmaps::create_empty();
|
|
}
|
|
|
|
void BitmapOverlay::render(Gfx::Painter& painter, Screen const& screen)
|
|
{
|
|
auto scale_factor = screen.scale_factor();
|
|
auto* bitmap = m_bitmaps->find_bitmap(scale_factor);
|
|
if (!bitmap) {
|
|
auto new_bitmap = create_bitmap(scale_factor);
|
|
if (!new_bitmap)
|
|
return;
|
|
bitmap = new_bitmap.ptr();
|
|
m_bitmaps->add_bitmap(scale_factor, new_bitmap.release_nonnull());
|
|
}
|
|
|
|
painter.blit({}, *bitmap, bitmap->rect());
|
|
}
|
|
|
|
RectangularOverlay::RectangularOverlay()
|
|
{
|
|
clear_bitmaps();
|
|
}
|
|
|
|
void RectangularOverlay::rect_changed(Gfx::IntRect const& previous_rect)
|
|
{
|
|
if (rect().size() != previous_rect.size())
|
|
clear_bitmaps();
|
|
}
|
|
|
|
void RectangularOverlay::clear_bitmaps()
|
|
{
|
|
m_rendered_bitmaps = MultiScaleBitmaps::create_empty();
|
|
}
|
|
|
|
void RectangularOverlay::render(Gfx::Painter& painter, Screen const& screen)
|
|
{
|
|
if (m_content_invalidated) {
|
|
clear_bitmaps();
|
|
m_content_invalidated = false;
|
|
}
|
|
auto scale_factor = screen.scale_factor();
|
|
auto* bitmap = m_rendered_bitmaps->find_bitmap(scale_factor);
|
|
if (!bitmap) {
|
|
auto new_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, rect().size(), scale_factor);
|
|
if (!new_bitmap)
|
|
return;
|
|
bitmap = new_bitmap.ptr();
|
|
|
|
Gfx::Painter bitmap_painter(*new_bitmap);
|
|
if (auto* shadow_bitmap = WindowManager::the().overlay_rect_shadow()) {
|
|
WindowFrame::paint_simple_rect_shadow(bitmap_painter, new_bitmap->rect(), shadow_bitmap->bitmap(scale_factor), true, true);
|
|
} else {
|
|
bitmap_painter.fill_rect(new_bitmap->rect(), Color(Color::Black).with_alpha(0xcc));
|
|
}
|
|
render_overlay_bitmap(bitmap_painter);
|
|
m_rendered_bitmaps->add_bitmap(scale_factor, new_bitmap.release_nonnull());
|
|
}
|
|
|
|
painter.blit({}, *bitmap, bitmap->rect());
|
|
}
|
|
|
|
Gfx::IntRect RectangularOverlay::calculate_frame_rect(Gfx::IntRect const& rect)
|
|
{
|
|
if (auto* shadow_bitmap = WindowManager::the().overlay_rect_shadow()) {
|
|
Gfx::IntSize size;
|
|
int base_size = shadow_bitmap->default_bitmap().height() / 2;
|
|
size = { base_size, base_size };
|
|
return rect.inflated(2 * base_size, 2 * base_size);
|
|
}
|
|
return rect.inflated(2 * default_frame_thickness, 2 * default_frame_thickness);
|
|
}
|
|
|
|
void RectangularOverlay::set_content_rect(Gfx::IntRect const& rect)
|
|
{
|
|
set_rect(calculate_frame_rect(rect));
|
|
}
|
|
|
|
void RectangularOverlay::invalidate_content()
|
|
{
|
|
m_content_invalidated = true;
|
|
invalidate();
|
|
}
|
|
|
|
Gfx::Font const* ScreenNumberOverlay::s_font { nullptr };
|
|
|
|
ScreenNumberOverlay::ScreenNumberOverlay(Screen& screen)
|
|
: m_screen(screen)
|
|
{
|
|
if (!s_font)
|
|
pick_font();
|
|
|
|
Gfx::IntRect rect {
|
|
default_offset,
|
|
default_offset,
|
|
default_size,
|
|
default_size
|
|
};
|
|
rect.translate_by(screen.rect().location());
|
|
set_rect(rect);
|
|
}
|
|
|
|
void ScreenNumberOverlay::pick_font()
|
|
{
|
|
auto screen_number_content_rect_size = calculate_content_rect_for_screen(Screen::main()).size();
|
|
auto& font_database = Gfx::FontDatabase::the();
|
|
auto& default_font = WindowManager::the().font();
|
|
String best_font_name;
|
|
int best_font_size = -1;
|
|
font_database.for_each_font([&](Gfx::Font const& font) {
|
|
// TODO: instead of picking *any* font we should probably compare font.name()
|
|
// with default_font.name(). But the default font currently does not provide larger sizes
|
|
auto size = font.glyph_height();
|
|
if (size * 2 <= screen_number_content_rect_size.height() && size > best_font_size) {
|
|
best_font_name = font.qualified_name();
|
|
best_font_size = size;
|
|
}
|
|
});
|
|
|
|
if (auto best_font = font_database.get_by_name(best_font_name)) {
|
|
s_font = best_font.ptr();
|
|
} else {
|
|
s_font = &default_font;
|
|
}
|
|
|
|
Compositor::the().for_each_overlay([&](auto& overlay) {
|
|
if (overlay.zorder() == ZOrder::ScreenNumber)
|
|
overlay.invalidate();
|
|
return IterationDecision::Continue;
|
|
});
|
|
}
|
|
|
|
Gfx::Font const& ScreenNumberOverlay::font()
|
|
{
|
|
if (!s_font) {
|
|
pick_font();
|
|
VERIFY(s_font);
|
|
}
|
|
return *s_font;
|
|
}
|
|
|
|
void ScreenNumberOverlay::render_overlay_bitmap(Gfx::Painter& painter)
|
|
{
|
|
painter.draw_text({ {}, rect().size() }, String::formatted("{}", m_screen.index() + 1), font(), Gfx::TextAlignment::Center, Color::White);
|
|
}
|
|
|
|
Gfx::IntRect ScreenNumberOverlay::calculate_content_rect_for_screen(Screen& screen)
|
|
{
|
|
Gfx::IntRect content_rect {
|
|
screen.rect().location().translated(default_offset, default_offset),
|
|
{ default_size, default_size }
|
|
};
|
|
|
|
return calculate_frame_rect(content_rect);
|
|
}
|
|
|
|
WindowGeometryOverlay::WindowGeometryOverlay(Window& window)
|
|
: m_window(window)
|
|
{
|
|
update_rect();
|
|
}
|
|
|
|
void WindowGeometryOverlay::update_rect()
|
|
{
|
|
if (auto* window = m_window.ptr()) {
|
|
auto& wm = WindowManager::the();
|
|
if (!window->size_increment().is_null()) {
|
|
int width_steps = (window->width() - window->base_size().width()) / window->size_increment().width();
|
|
int height_steps = (window->height() - window->base_size().height()) / window->size_increment().height();
|
|
m_label = String::formatted("{} ({}x{})", window->rect(), width_steps, height_steps);
|
|
} else {
|
|
m_label = window->rect().to_string();
|
|
}
|
|
m_label_rect = Gfx::IntRect { 0, 0, wm.font().width(m_label) + 16, wm.font().glyph_height() + 10 };
|
|
|
|
auto rect = calculate_frame_rect(m_label_rect);
|
|
rect.center_within(window->frame().rect());
|
|
auto desktop_rect = wm.desktop_rect(ScreenInput::the().cursor_location_screen());
|
|
if (rect.left() < desktop_rect.left())
|
|
rect.set_left(desktop_rect.left());
|
|
if (rect.top() < desktop_rect.top())
|
|
rect.set_top(desktop_rect.top());
|
|
if (rect.right() > desktop_rect.right())
|
|
rect.set_right_without_resize(desktop_rect.right());
|
|
if (rect.bottom() > desktop_rect.bottom())
|
|
rect.set_bottom_without_resize(desktop_rect.bottom());
|
|
|
|
set_rect(rect);
|
|
} else {
|
|
set_enabled(false);
|
|
}
|
|
}
|
|
|
|
void WindowGeometryOverlay::render_overlay_bitmap(Gfx::Painter& painter)
|
|
{
|
|
painter.draw_text({ {}, rect().size() }, m_label, WindowManager::the().font(), Gfx::TextAlignment::Center, Color::White);
|
|
}
|
|
|
|
void WindowGeometryOverlay::window_rect_changed()
|
|
{
|
|
update_rect();
|
|
invalidate_content();
|
|
}
|
|
|
|
DndOverlay::DndOverlay(String const& text, Gfx::Bitmap const* bitmap)
|
|
: m_bitmap(bitmap)
|
|
, m_text(text)
|
|
{
|
|
update_rect();
|
|
}
|
|
|
|
Gfx::Font const& DndOverlay::font()
|
|
{
|
|
return WindowManager::the().font();
|
|
}
|
|
|
|
void DndOverlay::update_rect()
|
|
{
|
|
int bitmap_width = m_bitmap ? m_bitmap->width() : 0;
|
|
int bitmap_height = m_bitmap ? m_bitmap->height() : 0;
|
|
auto& font = this->font();
|
|
int width = font.width(m_text) + bitmap_width;
|
|
int height = max((int)font.glyph_height(), bitmap_height);
|
|
auto location = Compositor::the().current_cursor_rect().center().translated(8, 8);
|
|
set_rect(Gfx::IntRect(location, { width, height }).inflated(16, 8));
|
|
}
|
|
|
|
RefPtr<Gfx::Bitmap> DndOverlay::create_bitmap(int scale_factor)
|
|
{
|
|
auto new_bitmap = Gfx::Bitmap::create(Gfx::BitmapFormat::BGRA8888, rect().size(), scale_factor);
|
|
if (!new_bitmap)
|
|
return {};
|
|
|
|
auto& wm = WindowManager::the();
|
|
Gfx::Painter bitmap_painter(*new_bitmap);
|
|
auto bitmap_rect = new_bitmap->rect();
|
|
bitmap_painter.fill_rect(bitmap_rect, wm.palette().selection().with_alpha(200));
|
|
bitmap_painter.draw_rect(bitmap_rect, wm.palette().selection());
|
|
if (!m_text.is_empty()) {
|
|
auto text_rect = bitmap_rect;
|
|
if (m_bitmap)
|
|
text_rect.translate_by(m_bitmap->width() + 8, 0);
|
|
bitmap_painter.draw_text(text_rect, m_text, Gfx::TextAlignment::CenterLeft, wm.palette().selection_text());
|
|
}
|
|
if (m_bitmap)
|
|
bitmap_painter.blit(bitmap_rect.top_left().translated(4, 4), *m_bitmap, m_bitmap->rect());
|
|
return new_bitmap;
|
|
}
|
|
|
|
void WindowStackSwitchOverlay::render_overlay_bitmap(Gfx::Painter& painter)
|
|
{
|
|
// We should come up with a more elegant way to get the content rectangle
|
|
Gfx::IntRect content_rect({}, m_content_size);
|
|
content_rect.center_within({ {}, rect().size() });
|
|
auto active_color = WindowManager::the().palette().active_window_border1();
|
|
auto inactive_color = WindowManager::the().palette().inactive_window_border1();
|
|
for (int y = 0; y < m_rows; y++) {
|
|
for (int x = 0; x < m_columns; x++) {
|
|
Gfx::IntRect rect {
|
|
content_rect.left() + x * (default_screen_rect_width + default_screen_rect_padding),
|
|
content_rect.top() + y * (default_screen_rect_height + default_screen_rect_padding),
|
|
default_screen_rect_width,
|
|
default_screen_rect_height
|
|
};
|
|
bool is_target = y == m_target_row && x == m_target_column;
|
|
painter.fill_rect(rect, is_target ? active_color : inactive_color);
|
|
}
|
|
}
|
|
}
|
|
|
|
WindowStackSwitchOverlay::WindowStackSwitchOverlay(Screen& screen, WindowStack& target_window_stack)
|
|
: m_rows((int)WindowManager::the().window_stack_rows())
|
|
, m_columns((int)WindowManager::the().window_stack_columns())
|
|
, m_target_row((int)target_window_stack.row())
|
|
, m_target_column((int)target_window_stack.column())
|
|
{
|
|
m_content_size = {
|
|
m_columns * (default_screen_rect_width + default_screen_rect_padding) - default_screen_rect_padding,
|
|
m_rows * (default_screen_rect_height + default_screen_rect_padding) - default_screen_rect_padding,
|
|
};
|
|
auto rect = calculate_frame_rect(Gfx::IntRect({}, m_content_size).inflated(2 * default_screen_rect_margin, 2 * default_screen_rect_margin));
|
|
rect.center_within(screen.rect());
|
|
set_rect(rect);
|
|
}
|
|
|
|
}
|