2020-01-18 11:38:21 +03:00
|
|
|
/*
|
2021-06-27 17:47:52 +03:00
|
|
|
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
2020-01-18 11:38:21 +03:00
|
|
|
*
|
2021-04-22 11:24:48 +03:00
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-01-18 11:38:21 +03:00
|
|
|
*/
|
|
|
|
|
2020-02-06 22:03:37 +03:00
|
|
|
#include "Compositor.h"
|
2021-06-27 17:47:52 +03:00
|
|
|
#include "Animation.h"
|
2020-03-22 23:13:23 +03:00
|
|
|
#include "ClientConnection.h"
|
2020-02-06 22:03:37 +03:00
|
|
|
#include "Event.h"
|
|
|
|
#include "EventLoop.h"
|
2021-06-20 22:23:43 +03:00
|
|
|
#include "MultiScaleBitmaps.h"
|
2020-02-06 22:03:37 +03:00
|
|
|
#include "Screen.h"
|
|
|
|
#include "Window.h"
|
|
|
|
#include "WindowManager.h"
|
2021-01-17 20:39:00 +03:00
|
|
|
#include <AK/Debug.h>
|
2020-03-08 14:05:14 +03:00
|
|
|
#include <AK/Memory.h>
|
2020-08-18 07:45:10 +03:00
|
|
|
#include <AK/ScopeGuard.h>
|
2020-02-15 02:10:34 +03:00
|
|
|
#include <LibCore/Timer.h>
|
2020-02-06 14:04:00 +03:00
|
|
|
#include <LibGfx/Font.h>
|
|
|
|
#include <LibGfx/Painter.h>
|
2020-12-28 03:35:53 +03:00
|
|
|
#include <LibGfx/StylePainter.h>
|
2021-05-22 19:47:42 +03:00
|
|
|
#include <LibThreading/BackgroundAction.h>
|
2019-05-24 20:32:46 +03:00
|
|
|
|
2020-02-06 22:03:37 +03:00
|
|
|
namespace WindowServer {
|
|
|
|
|
|
|
|
Compositor& Compositor::the()
|
2019-05-24 20:32:46 +03:00
|
|
|
{
|
2020-02-06 22:03:37 +03:00
|
|
|
static Compositor s_the;
|
2019-05-24 20:32:46 +03:00
|
|
|
return s_the;
|
|
|
|
}
|
|
|
|
|
2020-08-11 01:03:04 +03:00
|
|
|
static WallpaperMode mode_to_enum(const String& name)
|
2019-05-26 20:14:03 +03:00
|
|
|
{
|
|
|
|
if (name == "tile")
|
|
|
|
return WallpaperMode::Tile;
|
2021-01-20 01:47:52 +03:00
|
|
|
if (name == "stretch")
|
|
|
|
return WallpaperMode::Stretch;
|
2021-05-20 18:49:20 +03:00
|
|
|
if (name == "center")
|
|
|
|
return WallpaperMode::Center;
|
|
|
|
return WallpaperMode::Center;
|
2019-05-26 20:14:03 +03:00
|
|
|
}
|
|
|
|
|
2020-02-06 22:03:37 +03:00
|
|
|
Compositor::Compositor()
|
2019-05-24 20:32:46 +03:00
|
|
|
{
|
2020-04-22 01:07:48 +03:00
|
|
|
m_display_link_notify_timer = add<Core::Timer>(
|
|
|
|
1000 / 60, [this] {
|
|
|
|
notify_display_links();
|
|
|
|
});
|
|
|
|
m_display_link_notify_timer->stop();
|
|
|
|
|
2020-04-07 23:15:22 +03:00
|
|
|
m_compose_timer = Core::Timer::create_single_shot(
|
|
|
|
1000 / 60,
|
|
|
|
[this] {
|
|
|
|
compose();
|
|
|
|
},
|
|
|
|
this);
|
2019-09-20 16:19:46 +03:00
|
|
|
|
2020-04-07 23:15:22 +03:00
|
|
|
m_immediate_compose_timer = Core::Timer::create_single_shot(
|
|
|
|
0,
|
|
|
|
[this] {
|
|
|
|
compose();
|
|
|
|
},
|
|
|
|
this);
|
2019-05-24 20:32:46 +03:00
|
|
|
|
2019-08-18 07:32:14 +03:00
|
|
|
init_bitmaps();
|
2019-05-24 20:32:46 +03:00
|
|
|
}
|
|
|
|
|
2021-06-19 06:42:25 +03:00
|
|
|
const Gfx::Bitmap* Compositor::cursor_bitmap_for_screenshot(Badge<ClientConnection>, Screen& screen) const
|
|
|
|
{
|
|
|
|
if (!m_current_cursor)
|
|
|
|
return nullptr;
|
|
|
|
return &m_current_cursor->bitmap(screen.scale_factor());
|
|
|
|
}
|
|
|
|
|
2021-06-13 15:16:06 +03:00
|
|
|
const Gfx::Bitmap& Compositor::front_bitmap_for_screenshot(Badge<ClientConnection>, Screen& screen) const
|
|
|
|
{
|
2021-07-18 19:24:41 +03:00
|
|
|
return *screen.compositor_screen_data().m_front_bitmap;
|
2021-06-13 15:16:06 +03:00
|
|
|
}
|
|
|
|
|
2021-07-18 19:24:41 +03:00
|
|
|
void CompositorScreenData::init_bitmaps(Compositor& compositor, Screen& screen)
|
2019-08-18 07:32:14 +03:00
|
|
|
{
|
2021-07-18 19:24:41 +03:00
|
|
|
// Recreate the screen-number overlay as the Screen instances may have changed, or get rid of it if we no longer need it
|
|
|
|
if (compositor.showing_screen_numbers()) {
|
|
|
|
m_screen_number_overlay = compositor.create_overlay<ScreenNumberOverlay>(screen);
|
|
|
|
m_screen_number_overlay->set_enabled(true);
|
|
|
|
} else {
|
|
|
|
m_screen_number_overlay = nullptr;
|
|
|
|
}
|
|
|
|
|
2021-07-03 21:43:35 +03:00
|
|
|
m_has_flipped = false;
|
|
|
|
m_have_flush_rects = false;
|
|
|
|
m_buffers_are_flipped = false;
|
|
|
|
m_screen_can_set_buffer = screen.can_set_buffer();
|
|
|
|
|
2021-07-18 19:24:41 +03:00
|
|
|
m_flush_rects.clear_with_capacity();
|
|
|
|
m_flush_transparent_rects.clear_with_capacity();
|
|
|
|
m_flush_special_rects.clear_with_capacity();
|
2019-08-18 07:32:14 +03:00
|
|
|
|
2021-07-18 19:24:41 +03:00
|
|
|
auto size = screen.size();
|
|
|
|
m_front_bitmap = nullptr;
|
2021-07-21 19:02:15 +03:00
|
|
|
m_front_bitmap = Gfx::Bitmap::try_create_wrapper(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor(), screen.pitch(), screen.scanline(0, 0));
|
2021-01-19 20:10:47 +03:00
|
|
|
m_front_painter = make<Gfx::Painter>(*m_front_bitmap);
|
2021-06-13 15:16:06 +03:00
|
|
|
m_front_painter->translate(-screen.rect().location());
|
2019-08-19 14:29:19 +03:00
|
|
|
|
2021-07-18 19:24:41 +03:00
|
|
|
m_back_bitmap = nullptr;
|
2021-07-03 21:43:35 +03:00
|
|
|
if (m_screen_can_set_buffer)
|
2021-07-21 19:02:15 +03:00
|
|
|
m_back_bitmap = Gfx::Bitmap::try_create_wrapper(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor(), screen.pitch(), screen.scanline(1, 0));
|
2020-04-07 23:16:55 +03:00
|
|
|
else
|
2021-07-21 19:02:15 +03:00
|
|
|
m_back_bitmap = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor());
|
2021-01-19 20:10:47 +03:00
|
|
|
m_back_painter = make<Gfx::Painter>(*m_back_bitmap);
|
2021-06-13 15:16:06 +03:00
|
|
|
m_back_painter->translate(-screen.rect().location());
|
WindowServer: Make HighDPI aware
Almost all logic stays in "logical" (unscaled coordinates), which
means the patch is small and things like DnD, window moving and
resizing, menu handling, menuapplets, etc all work without changes.
Screen knows about phyiscal coordinates and mouse handling internally is
in physical coordinates (so that two 1 pixel movements in succession can
translate to one 1 logical coordinate mouse movement -- only a single
event is sent in this case, on the 2nd moved pixel).
Compositor also knows about physical pixels for its backbuffers. This is
a temporary state -- in a follow-up, I'll try to let Bitmaps know about
their intrinsic scale, then Compositor won't have to know about pixels
any longer. Most of Compositor's logic stays in view units, just
blitting to and from back buffers and the cursor save buffer has to be
done in pixels. The back buffer Painter gets a scale applied which
transparently handles all drawing. (But since the backbuffer and cursor
save buffer are also HighDPI, they currently need to be drawn using a
hack temporary unscaled Painter object. This will also go away once
Bitmaps know about their intrinsic scale.)
With this, editing WindowServer.ini to say
Width=800
Height=600
ScaleFactor=2
and booting brings up a fully-functional HighDPI UI.
(Except for minimizing windows, which will crash the window server
until #4932 is merged. And I didn't test the window switcher since the
win-tab shortcut doesn't work on my system.) It's all pixel-scaled,
but it looks pretty decent :^)
2021-01-15 18:58:55 +03:00
|
|
|
|
2021-07-18 19:24:41 +03:00
|
|
|
m_temp_bitmap = nullptr;
|
2021-07-21 19:02:15 +03:00
|
|
|
m_temp_bitmap = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRx8888, size, screen.scale_factor());
|
2021-01-19 20:10:47 +03:00
|
|
|
m_temp_painter = make<Gfx::Painter>(*m_temp_bitmap);
|
2021-06-13 15:16:06 +03:00
|
|
|
m_temp_painter->translate(-screen.rect().location());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Compositor::init_bitmaps()
|
|
|
|
{
|
|
|
|
Screen::for_each([&](auto& screen) {
|
2021-07-18 19:24:41 +03:00
|
|
|
screen.compositor_screen_data().init_bitmaps(*this, screen);
|
2021-06-13 15:16:06 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
2019-08-18 07:32:14 +03:00
|
|
|
|
2020-08-18 07:45:10 +03:00
|
|
|
invalidate_screen();
|
2019-08-18 07:32:14 +03:00
|
|
|
}
|
|
|
|
|
2020-12-28 12:56:22 +03:00
|
|
|
void Compositor::did_construct_window_manager(Badge<WindowManager>)
|
|
|
|
{
|
|
|
|
auto& wm = WindowManager::the();
|
2021-06-30 04:51:26 +03:00
|
|
|
|
|
|
|
m_current_window_stack = &wm.current_window_stack();
|
|
|
|
|
2021-05-20 18:49:20 +03:00
|
|
|
m_wallpaper_mode = mode_to_enum(wm.config()->read_entry("Background", "Mode", "center"));
|
2020-12-28 12:56:22 +03:00
|
|
|
m_custom_background_color = Color::from_string(wm.config()->read_entry("Background", "Color", ""));
|
|
|
|
|
|
|
|
invalidate_screen();
|
|
|
|
invalidate_occlusions();
|
|
|
|
compose();
|
|
|
|
}
|
|
|
|
|
2021-06-30 04:51:26 +03:00
|
|
|
Gfx::IntPoint Compositor::window_transition_offset(Window& window)
|
|
|
|
{
|
|
|
|
if (WindowManager::is_stationary_window_type(window.type()))
|
|
|
|
return {};
|
|
|
|
|
|
|
|
if (window.is_moving_to_another_stack())
|
|
|
|
return {};
|
|
|
|
|
2021-07-02 22:52:23 +03:00
|
|
|
return window.window_stack().transition_offset();
|
2021-06-30 04:51:26 +03:00
|
|
|
}
|
|
|
|
|
2020-02-06 22:03:37 +03:00
|
|
|
void Compositor::compose()
|
2019-05-24 20:32:46 +03:00
|
|
|
{
|
2020-02-06 22:03:37 +03:00
|
|
|
auto& wm = WindowManager::the();
|
2019-05-24 20:32:46 +03:00
|
|
|
|
2020-12-17 08:15:14 +03:00
|
|
|
{
|
|
|
|
auto& current_cursor = wm.active_cursor();
|
2021-02-21 21:56:59 +03:00
|
|
|
if (m_current_cursor != ¤t_cursor) {
|
2020-12-17 08:15:14 +03:00
|
|
|
change_cursor(¤t_cursor);
|
2021-02-21 21:56:59 +03:00
|
|
|
m_invalidated_cursor = m_invalidated_any = true;
|
|
|
|
}
|
2020-12-17 08:15:14 +03:00
|
|
|
}
|
|
|
|
|
2020-08-18 07:45:10 +03:00
|
|
|
if (!m_invalidated_any) {
|
2019-05-26 18:35:33 +03:00
|
|
|
// nothing dirtied since the last compose pass.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-08-18 07:45:10 +03:00
|
|
|
if (m_occlusions_dirty) {
|
|
|
|
m_occlusions_dirty = false;
|
|
|
|
recompute_occlusions();
|
|
|
|
}
|
|
|
|
|
2021-06-20 22:23:43 +03:00
|
|
|
// We should have recomputed occlusions if any overlay rects were changed
|
|
|
|
VERIFY(!m_overlay_rects_changed);
|
|
|
|
|
2020-08-18 07:45:10 +03:00
|
|
|
auto dirty_screen_rects = move(m_dirty_screen_rects);
|
2019-05-24 20:32:46 +03:00
|
|
|
|
2021-06-30 04:51:26 +03:00
|
|
|
bool window_stack_transition_in_progress = m_transitioning_to_window_stack != nullptr;
|
|
|
|
|
2020-08-18 07:45:10 +03:00
|
|
|
// Mark window regions as dirty that need to be re-rendered
|
2021-06-30 04:51:26 +03:00
|
|
|
wm.for_each_visible_window_from_back_to_front([&](Window& window) {
|
|
|
|
auto transition_offset = window_transition_offset(window);
|
2021-02-09 03:27:51 +03:00
|
|
|
auto frame_rect = window.frame().render_rect();
|
2021-06-30 04:51:26 +03:00
|
|
|
auto frame_rect_on_screen = frame_rect.translated(transition_offset);
|
2020-08-18 07:45:10 +03:00
|
|
|
for (auto& dirty_rect : dirty_screen_rects.rects()) {
|
2021-06-30 04:51:26 +03:00
|
|
|
auto invalidate_rect = dirty_rect.intersected(frame_rect_on_screen);
|
2020-08-18 07:45:10 +03:00
|
|
|
if (!invalidate_rect.is_empty()) {
|
|
|
|
auto inner_rect_offset = window.rect().location() - frame_rect.location();
|
2021-06-30 04:51:26 +03:00
|
|
|
invalidate_rect.translate_by(-(frame_rect.location() + inner_rect_offset + transition_offset));
|
2020-08-18 07:45:10 +03:00
|
|
|
window.invalidate_no_notify(invalidate_rect);
|
|
|
|
m_invalidated_window = true;
|
|
|
|
}
|
2019-05-24 20:32:46 +03:00
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
window.prepare_dirty_rects();
|
2021-06-30 04:51:26 +03:00
|
|
|
if (window_stack_transition_in_progress)
|
|
|
|
window.dirty_rects().translate_by(transition_offset);
|
2020-08-18 07:45:10 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
|
2021-07-18 06:15:17 +03:00
|
|
|
// Any dirty rects in transparency areas may require windows above or below
|
|
|
|
// to also be marked dirty in these areas
|
2021-06-30 04:51:26 +03:00
|
|
|
wm.for_each_visible_window_from_back_to_front([&](Window& window) {
|
2021-07-18 06:15:17 +03:00
|
|
|
auto& dirty_rects = window.dirty_rects(); // dirty rects have already been adjusted for transition offset!
|
|
|
|
if (dirty_rects.is_empty())
|
2020-08-18 07:45:10 +03:00
|
|
|
return IterationDecision::Continue;
|
2021-07-18 06:15:17 +03:00
|
|
|
auto& affected_transparency_rects = window.affected_transparency_rects();
|
|
|
|
if (affected_transparency_rects.is_empty())
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
// If we have transparency rects that affect others, we better have transparency rects ourselves...
|
|
|
|
auto& transparency_rects = window.transparency_rects();
|
|
|
|
VERIFY(!transparency_rects.is_empty());
|
|
|
|
for (auto& it : affected_transparency_rects) {
|
|
|
|
auto& affected_window_dirty_rects = it.key->dirty_rects();
|
|
|
|
auto& affected_rects = it.value;
|
|
|
|
affected_rects.for_each_intersected(dirty_rects, [&](auto& dirty_rect) {
|
|
|
|
affected_window_dirty_rects.add(dirty_rect);
|
2020-08-18 07:45:10 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
2021-07-18 06:15:17 +03:00
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
2019-05-24 20:32:46 +03:00
|
|
|
|
2020-03-29 13:32:06 +03:00
|
|
|
Color background_color = wm.palette().desktop_background();
|
2020-12-28 12:56:22 +03:00
|
|
|
if (m_custom_background_color.has_value())
|
|
|
|
background_color = m_custom_background_color.value();
|
2020-03-29 13:32:06 +03:00
|
|
|
|
2021-01-24 01:59:27 +03:00
|
|
|
if constexpr (COMPOSE_DEBUG) {
|
2021-01-17 20:39:00 +03:00
|
|
|
dbgln("COMPOSE: invalidated: window: {} cursor: {}, any: {}", m_invalidated_window, m_invalidated_cursor, m_invalidated_any);
|
|
|
|
for (auto& r : dirty_screen_rects.rects())
|
|
|
|
dbgln("dirty screen: {}", r);
|
|
|
|
}
|
|
|
|
|
2021-06-13 15:16:06 +03:00
|
|
|
auto& cursor_screen = ScreenInput::the().cursor_location_screen();
|
2020-08-18 07:45:10 +03:00
|
|
|
|
2021-07-18 19:24:41 +03:00
|
|
|
Screen::for_each([&](auto& screen) {
|
|
|
|
auto& screen_data = screen.compositor_screen_data();
|
2021-07-03 21:43:35 +03:00
|
|
|
screen_data.m_have_flush_rects = false;
|
2021-06-13 15:16:06 +03:00
|
|
|
screen_data.m_flush_rects.clear_with_capacity();
|
|
|
|
screen_data.m_flush_transparent_rects.clear_with_capacity();
|
|
|
|
screen_data.m_flush_special_rects.clear_with_capacity();
|
2021-07-18 19:24:41 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
2021-02-09 03:27:51 +03:00
|
|
|
|
2021-06-13 15:16:06 +03:00
|
|
|
auto cursor_rect = current_cursor_rect();
|
|
|
|
|
|
|
|
bool need_to_draw_cursor = false;
|
|
|
|
Gfx::IntRect previous_cursor_rect;
|
|
|
|
Screen* previous_cursor_screen = nullptr;
|
|
|
|
auto check_restore_cursor_back = [&](Screen& screen, const Gfx::IntRect& rect) {
|
|
|
|
if (&screen == &cursor_screen && !previous_cursor_screen && !need_to_draw_cursor && rect.intersects(cursor_rect)) {
|
2020-08-18 07:45:10 +03:00
|
|
|
// Restore what's behind the cursor if anything touches the area of the cursor
|
|
|
|
need_to_draw_cursor = true;
|
2021-07-18 19:24:41 +03:00
|
|
|
if (cursor_screen.compositor_screen_data().restore_cursor_back(cursor_screen, previous_cursor_rect))
|
2021-06-13 15:16:06 +03:00
|
|
|
previous_cursor_screen = &screen;
|
2020-08-18 07:45:10 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-06-13 15:16:06 +03:00
|
|
|
if (&cursor_screen != m_current_cursor_screen) {
|
|
|
|
// Cursor moved to another screen, restore on the cursor's background on the previous screen
|
|
|
|
need_to_draw_cursor = true;
|
|
|
|
if (m_current_cursor_screen) {
|
2021-07-18 19:24:41 +03:00
|
|
|
if (m_current_cursor_screen->compositor_screen_data().restore_cursor_back(*m_current_cursor_screen, previous_cursor_rect))
|
2021-06-13 15:16:06 +03:00
|
|
|
previous_cursor_screen = m_current_cursor_screen;
|
|
|
|
}
|
|
|
|
m_current_cursor_screen = &cursor_screen;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto prepare_rect = [&](Screen& screen, const Gfx::IntRect& rect) {
|
2021-07-18 19:24:41 +03:00
|
|
|
auto& screen_data = screen.compositor_screen_data();
|
2021-02-07 15:03:24 +03:00
|
|
|
dbgln_if(COMPOSE_DEBUG, " -> flush opaque: {}", rect);
|
2021-06-13 15:16:06 +03:00
|
|
|
VERIFY(!screen_data.m_flush_rects.intersects(rect));
|
|
|
|
VERIFY(!screen_data.m_flush_transparent_rects.intersects(rect));
|
2021-07-03 21:43:35 +03:00
|
|
|
screen_data.m_have_flush_rects = true;
|
2021-06-13 15:16:06 +03:00
|
|
|
screen_data.m_flush_rects.add(rect);
|
|
|
|
check_restore_cursor_back(screen, rect);
|
2020-08-18 07:45:10 +03:00
|
|
|
};
|
|
|
|
|
2021-06-13 15:16:06 +03:00
|
|
|
auto prepare_transparency_rect = [&](Screen& screen, const Gfx::IntRect& rect) {
|
2021-07-18 19:24:41 +03:00
|
|
|
auto& screen_data = screen.compositor_screen_data();
|
2021-02-07 15:03:24 +03:00
|
|
|
dbgln_if(COMPOSE_DEBUG, " -> flush transparent: {}", rect);
|
2021-06-13 15:16:06 +03:00
|
|
|
VERIFY(!screen_data.m_flush_rects.intersects(rect));
|
|
|
|
for (auto& r : screen_data.m_flush_transparent_rects.rects()) {
|
2021-02-09 03:27:51 +03:00
|
|
|
if (r == rect)
|
|
|
|
return;
|
2020-08-18 07:45:10 +03:00
|
|
|
}
|
2020-09-18 10:49:51 +03:00
|
|
|
|
2021-07-03 21:43:35 +03:00
|
|
|
screen_data.m_have_flush_rects = true;
|
2021-06-13 15:16:06 +03:00
|
|
|
screen_data.m_flush_transparent_rects.add(rect);
|
|
|
|
check_restore_cursor_back(screen, rect);
|
2020-08-18 07:45:10 +03:00
|
|
|
};
|
|
|
|
|
2021-07-18 19:24:41 +03:00
|
|
|
if (!cursor_screen.compositor_screen_data().m_cursor_back_bitmap || m_invalidated_cursor)
|
2021-06-13 15:16:06 +03:00
|
|
|
check_restore_cursor_back(cursor_screen, cursor_rect);
|
2020-08-18 07:45:10 +03:00
|
|
|
|
2021-06-13 15:16:06 +03:00
|
|
|
auto paint_wallpaper = [&](Screen& screen, Gfx::Painter& painter, const Gfx::IntRect& rect, const Gfx::IntRect& screen_rect) {
|
2021-01-23 02:56:01 +03:00
|
|
|
// FIXME: If the wallpaper is opaque and covers the whole rect, no need to fill with color!
|
2020-08-18 07:45:10 +03:00
|
|
|
painter.fill_rect(rect, background_color);
|
2019-05-26 20:14:03 +03:00
|
|
|
if (m_wallpaper) {
|
2021-05-20 18:49:20 +03:00
|
|
|
if (m_wallpaper_mode == WallpaperMode::Center) {
|
2021-06-13 15:16:06 +03:00
|
|
|
Gfx::IntPoint offset { (screen.width() - m_wallpaper->width()) / 2, (screen.height() - m_wallpaper->height()) / 2 };
|
|
|
|
painter.blit_offset(rect.location(), *m_wallpaper, rect.translated(-screen_rect.location()), offset);
|
2019-05-26 20:14:03 +03:00
|
|
|
} else if (m_wallpaper_mode == WallpaperMode::Tile) {
|
2020-08-18 07:45:10 +03:00
|
|
|
painter.draw_tiled_bitmap(rect, *m_wallpaper);
|
2021-01-20 01:47:52 +03:00
|
|
|
} else if (m_wallpaper_mode == WallpaperMode::Stretch) {
|
2021-06-13 15:16:06 +03:00
|
|
|
float hscale = (float)m_wallpaper->width() / (float)screen.width();
|
|
|
|
float vscale = (float)m_wallpaper->height() / (float)screen.height();
|
2019-06-05 19:23:27 +03:00
|
|
|
|
2020-08-18 07:45:10 +03:00
|
|
|
// TODO: this may look ugly, we should scale to a backing bitmap and then blit
|
2021-06-13 15:16:06 +03:00
|
|
|
auto relative_rect = rect.translated(-screen_rect.location());
|
|
|
|
auto src_rect = Gfx::FloatRect { relative_rect.x() * hscale, relative_rect.y() * vscale, relative_rect.width() * hscale, relative_rect.height() * vscale };
|
LibGfx: Add a draw_scaled_bitmap() variant that takes a FloatRect as src_rect
Consider
draw_scaled_bitmap({0, 0, 10, 10}, source, {0, 0, 5, 5}).
Imagine wanting to split that up into two calls, like e.g. the
compositor when redrawing the background with damage rects. You really
want to be able to say
draw_scaled_bitmap({0, 0, 5, 10}, source, {0, 0, 2.5, 5})
but up to now you couldn't. Now you can.
This makes painting very low-res images (such as tile.png) in mode
"stretch" work much better.
2021-01-22 23:13:47 +03:00
|
|
|
painter.draw_scaled_bitmap(rect, *m_wallpaper, src_rect);
|
2019-11-09 12:49:20 +03:00
|
|
|
} else {
|
2021-02-23 22:42:32 +03:00
|
|
|
VERIFY_NOT_REACHED();
|
2019-05-26 20:14:03 +03:00
|
|
|
}
|
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
};
|
|
|
|
|
2021-07-20 01:26:26 +03:00
|
|
|
{
|
|
|
|
// Paint any desktop wallpaper rects that are not somehow underneath any window transparency
|
|
|
|
// rects and outside of any opaque window areas
|
|
|
|
auto paint_desktop_wallpaper = [&]<bool is_opaque>(const Gfx::IntRect& render_rect) {
|
|
|
|
Screen::for_each([&](auto& screen) {
|
|
|
|
auto screen_rect = screen.rect();
|
|
|
|
auto screen_render_rect = screen_rect.intersected(render_rect);
|
|
|
|
if (!screen_render_rect.is_empty()) {
|
|
|
|
if constexpr (is_opaque) {
|
|
|
|
dbgln_if(COMPOSE_DEBUG, " render wallpaper opaque: {} on screen #{}", screen_render_rect, screen.index());
|
|
|
|
prepare_rect(screen, render_rect);
|
|
|
|
auto& back_painter = *screen.compositor_screen_data().m_back_painter;
|
|
|
|
paint_wallpaper(screen, back_painter, render_rect, screen_rect);
|
|
|
|
} else {
|
|
|
|
dbgln_if(COMPOSE_DEBUG, " render wallpaper transparent: {} on screen #{}", screen_render_rect, screen.index());
|
|
|
|
prepare_transparency_rect(screen, render_rect);
|
|
|
|
auto& temp_painter = *screen.compositor_screen_data().m_temp_painter;
|
|
|
|
paint_wallpaper(screen, temp_painter, render_rect, screen_rect);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
2021-06-13 15:16:06 +03:00
|
|
|
return IterationDecision::Continue;
|
2021-07-20 01:26:26 +03:00
|
|
|
};
|
|
|
|
m_opaque_wallpaper_rects.for_each_intersected(dirty_screen_rects, [&](auto& render_rect) {
|
|
|
|
return paint_desktop_wallpaper.template operator()<true>(render_rect);
|
2021-06-13 15:16:06 +03:00
|
|
|
});
|
2021-07-20 01:26:26 +03:00
|
|
|
m_transparent_wallpaper_rects.for_each_intersected(dirty_screen_rects, [&](auto& render_rect) {
|
|
|
|
return paint_desktop_wallpaper.template operator()<false>(render_rect);
|
|
|
|
});
|
|
|
|
}
|
2019-05-24 20:32:46 +03:00
|
|
|
|
2020-02-06 22:03:37 +03:00
|
|
|
auto compose_window = [&](Window& window) -> IterationDecision {
|
2021-06-13 15:16:06 +03:00
|
|
|
if (window.screens().is_empty()) {
|
|
|
|
// This window doesn't intersect with any screens, so there's nothing to render
|
2019-05-24 20:32:46 +03:00
|
|
|
return IterationDecision::Continue;
|
2021-06-13 15:16:06 +03:00
|
|
|
}
|
2021-06-30 04:51:26 +03:00
|
|
|
auto transition_offset = window_transition_offset(window);
|
|
|
|
auto frame_rect = window.frame().render_rect().translated(transition_offset);
|
|
|
|
auto window_rect = window.rect().translated(transition_offset);
|
2021-02-11 09:08:47 +03:00
|
|
|
auto frame_rects = frame_rect.shatter(window_rect);
|
2020-08-18 07:45:10 +03:00
|
|
|
|
2021-02-07 15:03:24 +03:00
|
|
|
dbgln_if(COMPOSE_DEBUG, " window {} frame rect: {}", window.title(), frame_rect);
|
2020-08-18 07:45:10 +03:00
|
|
|
|
2020-02-06 13:56:38 +03:00
|
|
|
RefPtr<Gfx::Bitmap> backing_store = window.backing_store();
|
2021-06-13 15:16:06 +03:00
|
|
|
auto compose_window_rect = [&](Screen& screen, Gfx::Painter& painter, const Gfx::IntRect& rect) {
|
2020-08-18 07:45:10 +03:00
|
|
|
if (!window.is_fullscreen()) {
|
|
|
|
rect.for_each_intersected(frame_rects, [&](const Gfx::IntRect& intersected_rect) {
|
|
|
|
Gfx::PainterStateSaver saver(painter);
|
|
|
|
painter.add_clip_rect(intersected_rect);
|
2021-06-30 04:51:26 +03:00
|
|
|
painter.translate(transition_offset);
|
2021-02-07 15:03:24 +03:00
|
|
|
dbgln_if(COMPOSE_DEBUG, " render frame: {}", intersected_rect);
|
2021-06-30 04:51:26 +03:00
|
|
|
window.frame().paint(screen, painter, intersected_rect.translated(-transition_offset));
|
2020-08-18 07:45:10 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-02-11 09:08:47 +03:00
|
|
|
auto clear_window_rect = [&](const Gfx::IntRect& clear_rect) {
|
|
|
|
auto fill_color = wm.palette().window();
|
|
|
|
if (!window.is_opaque())
|
|
|
|
fill_color.set_alpha(255 * window.opacity());
|
|
|
|
painter.fill_rect(clear_rect, fill_color);
|
|
|
|
};
|
|
|
|
|
2020-08-18 07:45:10 +03:00
|
|
|
if (!backing_store) {
|
2021-02-11 09:08:47 +03:00
|
|
|
clear_window_rect(window_rect.intersected(rect));
|
2020-08-18 07:45:10 +03:00
|
|
|
return;
|
|
|
|
}
|
2019-05-24 20:32:46 +03:00
|
|
|
|
2019-11-22 19:13:32 +03:00
|
|
|
// Decide where we would paint this window's backing store.
|
|
|
|
// This is subtly different from widow.rect(), because window
|
|
|
|
// size may be different from its backing store size. This
|
|
|
|
// happens when the window has been resized and the client
|
|
|
|
// has not yet attached a new backing store. In this case,
|
|
|
|
// we want to try to blit the backing store at the same place
|
|
|
|
// it was previously, and fill the rest of the window with its
|
|
|
|
// background color.
|
2020-06-10 11:57:59 +03:00
|
|
|
Gfx::IntRect backing_rect;
|
2019-11-22 19:13:32 +03:00
|
|
|
backing_rect.set_size(backing_store->size());
|
2020-02-06 22:03:37 +03:00
|
|
|
switch (WindowManager::the().resize_direction_of_window(window)) {
|
2019-11-22 19:13:32 +03:00
|
|
|
case ResizeDirection::None:
|
|
|
|
case ResizeDirection::Right:
|
|
|
|
case ResizeDirection::Down:
|
|
|
|
case ResizeDirection::DownRight:
|
2021-02-11 09:08:47 +03:00
|
|
|
backing_rect.set_location(window_rect.location());
|
2019-11-22 19:13:32 +03:00
|
|
|
break;
|
|
|
|
case ResizeDirection::Left:
|
|
|
|
case ResizeDirection::Up:
|
|
|
|
case ResizeDirection::UpLeft:
|
2021-02-11 09:08:47 +03:00
|
|
|
backing_rect.set_right_without_resize(window_rect.right());
|
|
|
|
backing_rect.set_bottom_without_resize(window_rect.bottom());
|
2019-11-22 19:13:32 +03:00
|
|
|
break;
|
|
|
|
case ResizeDirection::UpRight:
|
|
|
|
backing_rect.set_left(window.rect().left());
|
2021-02-11 09:08:47 +03:00
|
|
|
backing_rect.set_bottom_without_resize(window_rect.bottom());
|
2019-11-22 19:13:32 +03:00
|
|
|
break;
|
|
|
|
case ResizeDirection::DownLeft:
|
2021-02-11 09:08:47 +03:00
|
|
|
backing_rect.set_right_without_resize(window_rect.right());
|
|
|
|
backing_rect.set_top(window_rect.top());
|
2019-11-22 19:13:32 +03:00
|
|
|
break;
|
2019-05-24 20:32:46 +03:00
|
|
|
}
|
|
|
|
|
2021-02-11 09:08:47 +03:00
|
|
|
Gfx::IntRect dirty_rect_in_backing_coordinates = rect.intersected(window_rect)
|
2020-06-11 23:46:49 +03:00
|
|
|
.intersected(backing_rect)
|
|
|
|
.translated(-backing_rect.location());
|
2019-11-22 19:13:32 +03:00
|
|
|
|
2021-02-11 09:08:47 +03:00
|
|
|
if (!dirty_rect_in_backing_coordinates.is_empty()) {
|
|
|
|
auto dst = backing_rect.location().translated(dirty_rect_in_backing_coordinates.location());
|
2019-11-22 19:13:32 +03:00
|
|
|
|
2021-02-11 09:08:47 +03:00
|
|
|
if (window.client() && window.client()->is_unresponsive()) {
|
2021-02-11 09:30:55 +03:00
|
|
|
if (window.is_opaque()) {
|
|
|
|
painter.blit_filtered(dst, *backing_store, dirty_rect_in_backing_coordinates, [](Color src) {
|
|
|
|
return src.to_grayscale().darkened(0.75f);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
u8 alpha = 255 * window.opacity();
|
|
|
|
painter.blit_filtered(dst, *backing_store, dirty_rect_in_backing_coordinates, [&](Color src) {
|
|
|
|
auto color = src.to_grayscale().darkened(0.75f);
|
|
|
|
color.set_alpha(alpha);
|
|
|
|
return color;
|
|
|
|
});
|
|
|
|
}
|
2021-02-11 09:08:47 +03:00
|
|
|
} else {
|
|
|
|
painter.blit(dst, *backing_store, dirty_rect_in_backing_coordinates, window.opacity());
|
|
|
|
}
|
2020-06-11 23:46:49 +03:00
|
|
|
}
|
|
|
|
|
2021-02-11 09:08:47 +03:00
|
|
|
for (auto background_rect : window_rect.shatter(backing_rect))
|
|
|
|
clear_window_rect(background_rect);
|
2020-08-18 07:45:10 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
auto& dirty_rects = window.dirty_rects();
|
2021-01-17 20:39:00 +03:00
|
|
|
|
2021-01-24 01:59:27 +03:00
|
|
|
if constexpr (COMPOSE_DEBUG) {
|
2021-01-17 20:39:00 +03:00
|
|
|
for (auto& dirty_rect : dirty_rects.rects())
|
|
|
|
dbgln(" dirty: {}", dirty_rect);
|
|
|
|
for (auto& r : window.opaque_rects().rects())
|
|
|
|
dbgln(" opaque: {}", r);
|
|
|
|
for (auto& r : window.transparency_rects().rects())
|
|
|
|
dbgln(" transparent: {}", r);
|
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
|
|
|
|
// Render opaque portions directly to the back buffer
|
|
|
|
auto& opaque_rects = window.opaque_rects();
|
|
|
|
if (!opaque_rects.is_empty()) {
|
|
|
|
opaque_rects.for_each_intersected(dirty_rects, [&](const Gfx::IntRect& render_rect) {
|
2021-06-13 15:16:06 +03:00
|
|
|
for (auto* screen : window.screens()) {
|
|
|
|
auto screen_render_rect = render_rect.intersected(screen->rect());
|
|
|
|
if (screen_render_rect.is_empty())
|
|
|
|
continue;
|
|
|
|
dbgln_if(COMPOSE_DEBUG, " render opaque: {} on screen #{}", screen_render_rect, screen->index());
|
|
|
|
|
|
|
|
prepare_rect(*screen, screen_render_rect);
|
2021-07-18 19:24:41 +03:00
|
|
|
auto& back_painter = *screen->compositor_screen_data().m_back_painter;
|
2021-06-13 15:16:06 +03:00
|
|
|
Gfx::PainterStateSaver saver(back_painter);
|
|
|
|
back_painter.add_clip_rect(screen_render_rect);
|
|
|
|
compose_window_rect(*screen, back_painter, screen_render_rect);
|
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Render the wallpaper for any transparency directly covering
|
|
|
|
// the wallpaper
|
|
|
|
auto& transparency_wallpaper_rects = window.transparency_wallpaper_rects();
|
|
|
|
if (!transparency_wallpaper_rects.is_empty()) {
|
|
|
|
transparency_wallpaper_rects.for_each_intersected(dirty_rects, [&](const Gfx::IntRect& render_rect) {
|
2021-06-13 15:16:06 +03:00
|
|
|
for (auto* screen : window.screens()) {
|
|
|
|
auto screen_rect = screen->rect();
|
|
|
|
auto screen_render_rect = render_rect.intersected(screen_rect);
|
|
|
|
if (screen_render_rect.is_empty())
|
|
|
|
continue;
|
|
|
|
dbgln_if(COMPOSE_DEBUG, " render wallpaper: {} on screen #{}", screen_render_rect, screen->index());
|
|
|
|
|
2021-07-18 19:24:41 +03:00
|
|
|
auto& temp_painter = *screen->compositor_screen_data().m_temp_painter;
|
2021-06-13 15:16:06 +03:00
|
|
|
prepare_transparency_rect(*screen, screen_render_rect);
|
|
|
|
paint_wallpaper(*screen, temp_painter, screen_render_rect, screen_rect);
|
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
auto& transparency_rects = window.transparency_rects();
|
|
|
|
if (!transparency_rects.is_empty()) {
|
|
|
|
transparency_rects.for_each_intersected(dirty_rects, [&](const Gfx::IntRect& render_rect) {
|
2021-06-13 15:16:06 +03:00
|
|
|
for (auto* screen : window.screens()) {
|
|
|
|
auto screen_rect = screen->rect();
|
|
|
|
auto screen_render_rect = render_rect.intersected(screen_rect);
|
|
|
|
if (screen_render_rect.is_empty())
|
|
|
|
continue;
|
|
|
|
dbgln_if(COMPOSE_DEBUG, " render transparent: {} on screen #{}", screen_render_rect, screen->index());
|
|
|
|
|
|
|
|
prepare_transparency_rect(*screen, screen_render_rect);
|
2021-07-18 19:24:41 +03:00
|
|
|
auto& temp_painter = *screen->compositor_screen_data().m_temp_painter;
|
2021-06-13 15:16:06 +03:00
|
|
|
Gfx::PainterStateSaver saver(temp_painter);
|
|
|
|
temp_painter.add_clip_rect(screen_render_rect);
|
|
|
|
compose_window_rect(*screen, temp_painter, screen_render_rect);
|
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
2019-05-24 20:32:46 +03:00
|
|
|
}
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
};
|
|
|
|
|
2019-11-09 12:49:20 +03:00
|
|
|
// Paint the window stack.
|
2020-08-18 07:45:10 +03:00
|
|
|
if (m_invalidated_window) {
|
2021-07-06 22:59:58 +03:00
|
|
|
auto* fullscreen_window = wm.active_fullscreen_window();
|
|
|
|
if (fullscreen_window && fullscreen_window->is_opaque()) {
|
2020-08-18 07:45:10 +03:00
|
|
|
compose_window(*fullscreen_window);
|
2021-02-10 20:11:28 +03:00
|
|
|
fullscreen_window->clear_dirty_rects();
|
2020-08-18 07:45:10 +03:00
|
|
|
} else {
|
2021-06-30 04:51:26 +03:00
|
|
|
wm.for_each_visible_window_from_back_to_front([&](Window& window) {
|
2020-08-18 07:45:10 +03:00
|
|
|
compose_window(window);
|
|
|
|
window.clear_dirty_rects();
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that there are no overlapping transparent and opaque flush rectangles
|
2021-02-23 22:42:32 +03:00
|
|
|
VERIFY(![&]() {
|
2021-06-13 15:16:06 +03:00
|
|
|
bool is_overlapping = false;
|
|
|
|
Screen::for_each([&](auto& screen) {
|
2021-07-18 19:24:41 +03:00
|
|
|
auto& screen_data = screen.compositor_screen_data();
|
2021-06-13 15:16:06 +03:00
|
|
|
auto& flush_transparent_rects = screen_data.m_flush_transparent_rects;
|
|
|
|
auto& flush_rects = screen_data.m_flush_rects;
|
|
|
|
for (auto& rect_transparent : flush_transparent_rects.rects()) {
|
|
|
|
for (auto& rect_opaque : flush_rects.rects()) {
|
|
|
|
if (rect_opaque.intersects(rect_transparent)) {
|
|
|
|
dbgln("Transparent rect {} overlaps opaque rect: {}: {}", rect_transparent, rect_opaque, rect_opaque.intersected(rect_transparent));
|
|
|
|
is_overlapping = true;
|
|
|
|
return IterationDecision::Break;
|
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
}
|
|
|
|
}
|
2021-06-13 15:16:06 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
return is_overlapping;
|
2020-08-18 07:45:10 +03:00
|
|
|
}());
|
|
|
|
|
2021-06-20 22:23:43 +03:00
|
|
|
if (!m_overlay_list.is_empty()) {
|
|
|
|
// Render everything to the temporary buffer before we copy it back
|
|
|
|
render_overlays();
|
|
|
|
}
|
|
|
|
|
2020-08-18 07:45:10 +03:00
|
|
|
// Copy anything rendered to the temporary buffer to the back buffer
|
2021-06-13 15:16:06 +03:00
|
|
|
Screen::for_each([&](auto& screen) {
|
|
|
|
auto screen_rect = screen.rect();
|
2021-07-18 19:24:41 +03:00
|
|
|
auto& screen_data = screen.compositor_screen_data();
|
2021-06-13 15:16:06 +03:00
|
|
|
for (auto& rect : screen_data.m_flush_transparent_rects.rects())
|
|
|
|
screen_data.m_back_painter->blit(rect.location(), *screen_data.m_temp_bitmap, rect.translated(-screen_rect.location()));
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
2020-08-18 07:45:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
m_invalidated_any = false;
|
|
|
|
m_invalidated_window = false;
|
|
|
|
m_invalidated_cursor = false;
|
|
|
|
|
2021-06-29 02:42:13 +03:00
|
|
|
if (!m_animations.is_empty()) {
|
|
|
|
Screen::for_each([&](auto& screen) {
|
2021-07-18 19:24:41 +03:00
|
|
|
auto& screen_data = screen.compositor_screen_data();
|
2021-06-29 02:42:13 +03:00
|
|
|
update_animations(screen, screen_data.m_flush_special_rects);
|
2021-07-03 21:43:35 +03:00
|
|
|
if (!screen_data.m_flush_special_rects.is_empty())
|
|
|
|
screen_data.m_have_flush_rects = true;
|
2021-06-29 02:42:13 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
// As long as animations are running make sure we keep rendering frames
|
|
|
|
m_invalidated_any = true;
|
|
|
|
start_compose_async_timer();
|
|
|
|
}
|
2019-12-03 23:34:34 +03:00
|
|
|
|
2020-08-18 07:45:10 +03:00
|
|
|
if (need_to_draw_cursor) {
|
2021-07-18 19:24:41 +03:00
|
|
|
auto& screen_data = cursor_screen.compositor_screen_data();
|
2021-06-13 15:16:06 +03:00
|
|
|
screen_data.draw_cursor(cursor_screen, cursor_rect);
|
2020-08-18 07:45:10 +03:00
|
|
|
}
|
2019-05-24 20:32:46 +03:00
|
|
|
|
2021-06-13 15:16:06 +03:00
|
|
|
Screen::for_each([&](auto& screen) {
|
|
|
|
flush(screen);
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
2019-05-24 20:32:46 +03:00
|
|
|
}
|
|
|
|
|
2021-06-13 15:16:06 +03:00
|
|
|
void Compositor::flush(Screen& screen)
|
2019-05-24 20:32:46 +03:00
|
|
|
{
|
2021-07-18 19:24:41 +03:00
|
|
|
auto& screen_data = screen.compositor_screen_data();
|
2021-07-03 21:43:35 +03:00
|
|
|
|
|
|
|
bool device_can_flush_buffers = screen.can_device_flush_buffers();
|
|
|
|
if (!screen_data.m_have_flush_rects && (!screen_data.m_screen_can_set_buffer || screen_data.m_has_flipped)) {
|
|
|
|
dbgln_if(COMPOSE_DEBUG, "Nothing to flush on screen #{} {}", screen.index(), screen_data.m_have_flush_rects);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
screen_data.m_have_flush_rects = false;
|
|
|
|
|
2021-07-10 20:28:26 +03:00
|
|
|
auto screen_rect = screen.rect();
|
2021-06-13 15:16:06 +03:00
|
|
|
if (m_flash_flush) {
|
2021-07-10 20:28:26 +03:00
|
|
|
Gfx::IntRect bounding_flash;
|
|
|
|
for (auto& rect : screen_data.m_flush_rects.rects()) {
|
2021-06-13 15:16:06 +03:00
|
|
|
screen_data.m_front_painter->fill_rect(rect, Color::Yellow);
|
2021-07-10 20:28:26 +03:00
|
|
|
bounding_flash = bounding_flash.united(rect);
|
|
|
|
}
|
|
|
|
for (auto& rect : screen_data.m_flush_transparent_rects.rects()) {
|
2021-07-08 02:22:21 +03:00
|
|
|
screen_data.m_front_painter->fill_rect(rect, Color::Green);
|
2021-07-10 20:28:26 +03:00
|
|
|
bounding_flash = bounding_flash.united(rect);
|
|
|
|
}
|
|
|
|
if (!bounding_flash.is_empty()) {
|
|
|
|
if (device_can_flush_buffers) {
|
|
|
|
// If the device needs a flush we need to let it know that we
|
|
|
|
// modified the front buffer!
|
|
|
|
bounding_flash.translate_by(-screen_rect.location());
|
|
|
|
screen.flush_display_front_buffer((!screen_data.m_screen_can_set_buffer || !screen_data.m_buffers_are_flipped) ? 0 : 1, bounding_flash);
|
|
|
|
}
|
|
|
|
usleep(10000);
|
|
|
|
}
|
2019-08-15 16:08:32 +03:00
|
|
|
}
|
|
|
|
|
2021-07-03 21:43:35 +03:00
|
|
|
if (device_can_flush_buffers && screen_data.m_screen_can_set_buffer) {
|
|
|
|
if (!screen_data.m_has_flipped) {
|
|
|
|
// If we have not flipped any buffers before, we should be flushing
|
|
|
|
// the entire buffer to make sure that the device has all the bits we wrote
|
|
|
|
screen_data.m_flush_rects = { screen.rect() };
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we also support buffer flipping we need to make sure we transfer all
|
|
|
|
// updated areas to the device before we flip. We already modified the framebuffer
|
|
|
|
// memory, but the device needs to know what areas we actually did update.
|
|
|
|
for (auto& rect : screen_data.m_flush_rects.rects())
|
|
|
|
screen.queue_flush_display_rect(rect.translated(-screen_rect.location()));
|
|
|
|
for (auto& rect : screen_data.m_flush_transparent_rects.rects())
|
|
|
|
screen.queue_flush_display_rect(rect.translated(-screen_rect.location()));
|
|
|
|
for (auto& rect : screen_data.m_flush_special_rects.rects())
|
|
|
|
screen.queue_flush_display_rect(rect.translated(-screen_rect.location()));
|
|
|
|
|
|
|
|
screen.flush_display((!screen_data.m_screen_can_set_buffer || screen_data.m_buffers_are_flipped) ? 0 : 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (screen_data.m_screen_can_set_buffer) {
|
2021-06-13 15:16:06 +03:00
|
|
|
screen_data.flip_buffers(screen);
|
2021-07-03 21:43:35 +03:00
|
|
|
screen_data.m_has_flipped = true;
|
|
|
|
}
|
2021-06-13 15:16:06 +03:00
|
|
|
|
2021-06-26 20:04:01 +03:00
|
|
|
auto do_flush = [&](Gfx::IntRect rect) {
|
|
|
|
VERIFY(screen_rect.contains(rect));
|
2021-06-13 15:16:06 +03:00
|
|
|
rect.translate_by(-screen_rect.location());
|
|
|
|
|
|
|
|
// Almost everything in Compositor is in logical coordinates, with the painters having
|
|
|
|
// a scale applied. But this routine accesses the backbuffer pixels directly, so it
|
|
|
|
// must work in physical coordinates.
|
2021-06-28 00:43:04 +03:00
|
|
|
auto scaled_rect = rect * screen.scale_factor();
|
|
|
|
Gfx::RGBA32* front_ptr = screen_data.m_front_bitmap->scanline(scaled_rect.y()) + scaled_rect.x();
|
|
|
|
Gfx::RGBA32* back_ptr = screen_data.m_back_bitmap->scanline(scaled_rect.y()) + scaled_rect.x();
|
2021-06-13 15:16:06 +03:00
|
|
|
size_t pitch = screen_data.m_back_bitmap->pitch();
|
|
|
|
|
|
|
|
// NOTE: The meaning of a flush depends on whether we can flip buffers or not.
|
|
|
|
//
|
|
|
|
// If flipping is supported, flushing means that we've flipped, and now we
|
|
|
|
// copy the changed bits from the front buffer to the back buffer, to keep
|
|
|
|
// them in sync.
|
|
|
|
//
|
|
|
|
// If flipping is not supported, flushing means that we copy the changed
|
|
|
|
// rects from the backing bitmap to the display framebuffer.
|
|
|
|
|
|
|
|
Gfx::RGBA32* to_ptr;
|
|
|
|
const Gfx::RGBA32* from_ptr;
|
|
|
|
|
|
|
|
if (screen_data.m_screen_can_set_buffer) {
|
|
|
|
to_ptr = back_ptr;
|
|
|
|
from_ptr = front_ptr;
|
|
|
|
} else {
|
|
|
|
to_ptr = front_ptr;
|
|
|
|
from_ptr = back_ptr;
|
|
|
|
}
|
|
|
|
|
2021-06-28 00:43:04 +03:00
|
|
|
for (int y = 0; y < scaled_rect.height(); ++y) {
|
|
|
|
fast_u32_copy(to_ptr, from_ptr, scaled_rect.width());
|
2021-06-13 15:16:06 +03:00
|
|
|
from_ptr = (const Gfx::RGBA32*)((const u8*)from_ptr + pitch);
|
|
|
|
to_ptr = (Gfx::RGBA32*)((u8*)to_ptr + pitch);
|
|
|
|
}
|
2021-07-03 21:43:35 +03:00
|
|
|
if (device_can_flush_buffers) {
|
|
|
|
// Whether or not we need to flush buffers, we need to at least track what we modified
|
|
|
|
// so that we can flush these areas next time before we flip buffers. Or, if we don't
|
|
|
|
// support buffer flipping then we will flush them shortly.
|
2021-06-26 20:04:01 +03:00
|
|
|
screen.queue_flush_display_rect(rect);
|
2021-07-03 21:43:35 +03:00
|
|
|
}
|
2021-06-13 15:16:06 +03:00
|
|
|
};
|
|
|
|
for (auto& rect : screen_data.m_flush_rects.rects())
|
|
|
|
do_flush(rect);
|
|
|
|
for (auto& rect : screen_data.m_flush_transparent_rects.rects())
|
|
|
|
do_flush(rect);
|
|
|
|
for (auto& rect : screen_data.m_flush_special_rects.rects())
|
|
|
|
do_flush(rect);
|
2021-07-03 21:43:35 +03:00
|
|
|
if (device_can_flush_buffers && !screen_data.m_screen_can_set_buffer) {
|
|
|
|
// If we also support flipping buffers we don't really need to flush these areas right now.
|
|
|
|
// Instead, we skip this step and just keep track of them until shortly before the next flip.
|
|
|
|
// If we however don't support flipping buffers then we need to flush the changed areas right
|
|
|
|
// now so that they can be sent to the device.
|
2021-07-10 20:28:26 +03:00
|
|
|
screen.flush_display(screen_data.m_buffers_are_flipped ? 1 : 0);
|
2021-07-03 21:43:35 +03:00
|
|
|
}
|
2019-05-24 20:32:46 +03:00
|
|
|
}
|
|
|
|
|
2020-08-18 07:45:10 +03:00
|
|
|
void Compositor::invalidate_screen()
|
2019-05-24 20:32:46 +03:00
|
|
|
{
|
2021-06-13 15:16:06 +03:00
|
|
|
invalidate_screen(Screen::bounding_rect());
|
2019-05-24 20:32:46 +03:00
|
|
|
}
|
|
|
|
|
2020-08-18 07:45:10 +03:00
|
|
|
void Compositor::invalidate_screen(const Gfx::IntRect& screen_rect)
|
2019-05-24 20:32:46 +03:00
|
|
|
{
|
2021-06-13 15:16:06 +03:00
|
|
|
m_dirty_screen_rects.add(screen_rect.intersected(Screen::bounding_rect()));
|
2020-09-18 10:49:51 +03:00
|
|
|
|
2020-08-18 07:45:10 +03:00
|
|
|
if (m_invalidated_any)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_invalidated_any = true;
|
|
|
|
m_invalidated_window = true;
|
|
|
|
start_compose_async_timer();
|
|
|
|
}
|
|
|
|
|
2021-07-05 00:47:17 +03:00
|
|
|
void Compositor::invalidate_screen(Gfx::DisjointRectSet const& rects)
|
|
|
|
{
|
|
|
|
m_dirty_screen_rects.add(rects.intersected(Screen::bounding_rect()));
|
|
|
|
|
|
|
|
if (m_invalidated_any)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_invalidated_any = true;
|
|
|
|
m_invalidated_window = true;
|
|
|
|
start_compose_async_timer();
|
|
|
|
}
|
|
|
|
|
2020-08-18 07:45:10 +03:00
|
|
|
void Compositor::invalidate_window()
|
|
|
|
{
|
|
|
|
if (m_invalidated_window)
|
2019-05-24 20:32:46 +03:00
|
|
|
return;
|
2020-08-18 07:45:10 +03:00
|
|
|
m_invalidated_window = true;
|
|
|
|
m_invalidated_any = true;
|
2019-05-24 20:32:46 +03:00
|
|
|
|
2020-08-18 07:45:10 +03:00
|
|
|
start_compose_async_timer();
|
|
|
|
}
|
2019-05-26 18:35:33 +03:00
|
|
|
|
2020-08-18 07:45:10 +03:00
|
|
|
void Compositor::start_compose_async_timer()
|
|
|
|
{
|
2019-05-26 18:35:33 +03:00
|
|
|
// We delay composition by a timer interval, but to not affect latency too
|
|
|
|
// much, if a pending compose is not already scheduled, we also schedule an
|
|
|
|
// immediate compose the next spin of the event loop.
|
2019-09-20 16:19:46 +03:00
|
|
|
if (!m_compose_timer->is_active()) {
|
|
|
|
m_compose_timer->start();
|
|
|
|
m_immediate_compose_timer->start();
|
2019-05-26 18:35:33 +03:00
|
|
|
}
|
2019-05-24 20:32:46 +03:00
|
|
|
}
|
|
|
|
|
2020-04-29 21:58:39 +03:00
|
|
|
bool Compositor::set_background_color(const String& background_color)
|
2020-03-29 13:32:06 +03:00
|
|
|
{
|
2020-12-28 12:56:22 +03:00
|
|
|
auto color = Color::from_string(background_color);
|
|
|
|
if (!color.has_value())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
m_custom_background_color = color;
|
|
|
|
|
2020-03-29 13:32:06 +03:00
|
|
|
auto& wm = WindowManager::the();
|
2020-05-18 13:16:17 +03:00
|
|
|
wm.config()->write_entry("Background", "Color", background_color);
|
|
|
|
bool ret_val = wm.config()->sync();
|
2020-03-29 13:32:06 +03:00
|
|
|
|
|
|
|
if (ret_val)
|
2020-08-18 07:45:10 +03:00
|
|
|
Compositor::invalidate_screen();
|
2020-03-29 13:32:06 +03:00
|
|
|
|
|
|
|
return ret_val;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Compositor::set_wallpaper_mode(const String& mode)
|
|
|
|
{
|
|
|
|
auto& wm = WindowManager::the();
|
2020-05-18 13:16:17 +03:00
|
|
|
wm.config()->write_entry("Background", "Mode", mode);
|
|
|
|
bool ret_val = wm.config()->sync();
|
2020-03-29 13:32:06 +03:00
|
|
|
|
|
|
|
if (ret_val) {
|
|
|
|
m_wallpaper_mode = mode_to_enum(mode);
|
2020-08-18 07:45:10 +03:00
|
|
|
Compositor::invalidate_screen();
|
2020-03-29 13:32:06 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret_val;
|
|
|
|
}
|
|
|
|
|
2020-02-06 22:03:37 +03:00
|
|
|
bool Compositor::set_wallpaper(const String& path, Function<void(bool)>&& callback)
|
2019-05-24 20:32:46 +03:00
|
|
|
{
|
2021-05-22 19:47:42 +03:00
|
|
|
Threading::BackgroundAction<RefPtr<Gfx::Bitmap>>::create(
|
2021-07-02 17:34:19 +03:00
|
|
|
[path](auto&) {
|
2021-07-21 19:02:15 +03:00
|
|
|
return Gfx::Bitmap::try_load_from_file(path);
|
2019-08-25 19:28:09 +03:00
|
|
|
},
|
|
|
|
|
2020-02-06 13:56:38 +03:00
|
|
|
[this, path, callback = move(callback)](RefPtr<Gfx::Bitmap> bitmap) {
|
2019-08-25 19:28:09 +03:00
|
|
|
m_wallpaper_path = path;
|
|
|
|
m_wallpaper = move(bitmap);
|
2020-08-18 07:45:10 +03:00
|
|
|
invalidate_screen();
|
2019-08-25 19:28:09 +03:00
|
|
|
callback(true);
|
2019-05-24 20:32:46 +03:00
|
|
|
});
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-07-18 19:24:41 +03:00
|
|
|
void CompositorScreenData::flip_buffers(Screen& screen)
|
2019-05-24 20:32:46 +03:00
|
|
|
{
|
2021-02-23 22:42:32 +03:00
|
|
|
VERIFY(m_screen_can_set_buffer);
|
2019-05-24 20:32:46 +03:00
|
|
|
swap(m_front_bitmap, m_back_bitmap);
|
|
|
|
swap(m_front_painter, m_back_painter);
|
2021-06-13 15:16:06 +03:00
|
|
|
screen.set_buffer(m_buffers_are_flipped ? 0 : 1);
|
2019-05-24 20:32:46 +03:00
|
|
|
m_buffers_are_flipped = !m_buffers_are_flipped;
|
|
|
|
}
|
|
|
|
|
2021-06-13 15:16:06 +03:00
|
|
|
void Compositor::screen_resolution_changed()
|
2019-05-24 20:32:46 +03:00
|
|
|
{
|
2021-06-15 06:19:56 +03:00
|
|
|
// Screens may be gone now, invalidate any references to them
|
|
|
|
m_current_cursor_screen = nullptr;
|
|
|
|
|
2019-08-18 07:32:14 +03:00
|
|
|
init_bitmaps();
|
2020-08-18 07:45:10 +03:00
|
|
|
invalidate_occlusions();
|
2021-06-20 22:23:43 +03:00
|
|
|
overlay_rects_changed();
|
2019-05-24 20:32:46 +03:00
|
|
|
compose();
|
|
|
|
}
|
|
|
|
|
2020-06-10 11:57:59 +03:00
|
|
|
Gfx::IntRect Compositor::current_cursor_rect() const
|
2019-05-24 20:32:46 +03:00
|
|
|
{
|
2020-02-06 22:03:37 +03:00
|
|
|
auto& wm = WindowManager::the();
|
2020-12-17 08:15:14 +03:00
|
|
|
auto& current_cursor = m_current_cursor ? *m_current_cursor : wm.active_cursor();
|
2021-06-13 15:16:06 +03:00
|
|
|
return { ScreenInput::the().cursor_location().translated(-current_cursor.params().hotspot()), current_cursor.size() };
|
2019-05-24 20:32:46 +03:00
|
|
|
}
|
|
|
|
|
2020-12-17 08:15:14 +03:00
|
|
|
void Compositor::invalidate_cursor(bool compose_immediately)
|
2019-05-24 20:32:46 +03:00
|
|
|
{
|
2020-08-18 07:45:10 +03:00
|
|
|
if (m_invalidated_cursor)
|
|
|
|
return;
|
|
|
|
m_invalidated_cursor = true;
|
|
|
|
m_invalidated_any = true;
|
|
|
|
|
2020-12-17 08:15:14 +03:00
|
|
|
if (compose_immediately)
|
|
|
|
compose();
|
|
|
|
else
|
|
|
|
start_compose_async_timer();
|
2019-05-24 20:32:46 +03:00
|
|
|
}
|
|
|
|
|
2020-12-17 08:15:14 +03:00
|
|
|
void Compositor::change_cursor(const Cursor* cursor)
|
|
|
|
{
|
|
|
|
if (m_current_cursor == cursor)
|
|
|
|
return;
|
|
|
|
m_current_cursor = cursor;
|
|
|
|
m_current_cursor_frame = 0;
|
|
|
|
if (m_cursor_timer) {
|
|
|
|
m_cursor_timer->stop();
|
|
|
|
m_cursor_timer = nullptr;
|
|
|
|
}
|
|
|
|
if (cursor && cursor->params().frames() > 1 && cursor->params().frame_ms() != 0) {
|
|
|
|
m_cursor_timer = add<Core::Timer>(
|
|
|
|
cursor->params().frame_ms(), [this, cursor] {
|
|
|
|
if (m_current_cursor != cursor)
|
|
|
|
return;
|
|
|
|
auto frames = cursor->params().frames();
|
|
|
|
if (++m_current_cursor_frame >= frames)
|
|
|
|
m_current_cursor_frame = 0;
|
|
|
|
invalidate_cursor(true);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-20 22:23:43 +03:00
|
|
|
void Compositor::render_overlays()
|
|
|
|
{
|
|
|
|
// NOTE: overlays should always be rendered to the temporary buffer!
|
|
|
|
for (auto& overlay : m_overlay_list) {
|
|
|
|
for (auto* screen : overlay.m_screens) {
|
2021-07-18 19:24:41 +03:00
|
|
|
auto& screen_data = screen->compositor_screen_data();
|
2021-06-20 22:23:43 +03:00
|
|
|
auto& painter = screen_data.overlay_painter();
|
2021-07-20 01:26:26 +03:00
|
|
|
|
|
|
|
auto render_overlay_rect = [&](auto& intersected_overlay_rect) {
|
2021-06-20 22:23:43 +03:00
|
|
|
Gfx::PainterStateSaver saver(painter);
|
|
|
|
painter.add_clip_rect(intersected_overlay_rect);
|
|
|
|
painter.translate(overlay.m_current_rect.location());
|
|
|
|
overlay.render(painter, *screen);
|
|
|
|
return IterationDecision::Continue;
|
2021-07-20 01:26:26 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
auto const& render_rect = overlay.current_render_rect();
|
|
|
|
screen_data.for_each_intersected_flushing_rect(render_rect, render_overlay_rect);
|
|
|
|
|
|
|
|
// Now render any areas that are not somehow underneath any window or transparency area
|
|
|
|
m_transparent_wallpaper_rects.for_each_intersected(render_rect, render_overlay_rect);
|
2021-06-20 22:23:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Compositor::add_overlay(Overlay& overlay)
|
|
|
|
{
|
|
|
|
VERIFY(!overlay.m_list_node.is_in_list());
|
|
|
|
auto zorder = overlay.zorder();
|
|
|
|
bool did_insert = false;
|
|
|
|
for (auto& other_overlay : m_overlay_list) {
|
|
|
|
if (other_overlay.zorder() > zorder) {
|
|
|
|
m_overlay_list.insert_before(other_overlay, overlay);
|
|
|
|
did_insert = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!did_insert)
|
|
|
|
m_overlay_list.append(overlay);
|
|
|
|
|
|
|
|
overlay.clear_invalidated();
|
|
|
|
overlay_rects_changed();
|
|
|
|
auto& rect = overlay.rect();
|
|
|
|
if (!rect.is_empty())
|
|
|
|
invalidate_screen(rect);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Compositor::remove_overlay(Overlay& overlay)
|
|
|
|
{
|
|
|
|
auto& current_render_rect = overlay.current_render_rect();
|
|
|
|
if (!current_render_rect.is_empty())
|
|
|
|
invalidate_screen(current_render_rect);
|
|
|
|
m_overlay_list.remove(overlay);
|
2021-07-17 04:05:01 +03:00
|
|
|
overlay_rects_changed();
|
2021-06-20 22:23:43 +03:00
|
|
|
}
|
|
|
|
|
2021-07-18 19:24:41 +03:00
|
|
|
void CompositorScreenData::draw_cursor(Screen& screen, const Gfx::IntRect& cursor_rect)
|
2019-05-24 20:32:46 +03:00
|
|
|
{
|
2020-02-06 22:03:37 +03:00
|
|
|
auto& wm = WindowManager::the();
|
2020-09-18 10:49:51 +03:00
|
|
|
|
2021-06-13 15:16:06 +03:00
|
|
|
if (!m_cursor_back_bitmap || m_cursor_back_bitmap->size() != cursor_rect.size() || m_cursor_back_bitmap->scale() != screen.scale_factor()) {
|
2021-07-21 19:02:15 +03:00
|
|
|
m_cursor_back_bitmap = Gfx::Bitmap::try_create(Gfx::BitmapFormat::BGRx8888, cursor_rect.size(), screen.scale_factor());
|
2020-08-18 07:45:10 +03:00
|
|
|
m_cursor_back_painter = make<Gfx::Painter>(*m_cursor_back_bitmap);
|
2019-12-08 18:50:23 +03:00
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
|
2021-06-13 15:16:06 +03:00
|
|
|
auto& compositor = Compositor::the();
|
|
|
|
auto& current_cursor = compositor.m_current_cursor ? *compositor.m_current_cursor : wm.active_cursor();
|
|
|
|
auto screen_rect = screen.rect();
|
|
|
|
m_cursor_back_painter->blit({ 0, 0 }, *m_back_bitmap, current_cursor.rect().translated(cursor_rect.location()).intersected(screen_rect).translated(-screen_rect.location()));
|
2021-07-03 21:43:35 +03:00
|
|
|
auto cursor_src_rect = current_cursor.source_rect(compositor.m_current_cursor_frame);
|
|
|
|
m_back_painter->blit(cursor_rect.location(), current_cursor.bitmap(screen.scale_factor()), cursor_src_rect);
|
|
|
|
m_flush_special_rects.add(Gfx::IntRect(cursor_rect.location(), cursor_src_rect.size()).intersected(screen.rect()));
|
|
|
|
m_have_flush_rects = true;
|
2019-05-24 20:32:46 +03:00
|
|
|
m_last_cursor_rect = cursor_rect;
|
2021-06-13 15:16:06 +03:00
|
|
|
VERIFY(compositor.m_current_cursor_screen == &screen);
|
|
|
|
m_cursor_back_is_valid = true;
|
2019-05-24 20:32:46 +03:00
|
|
|
}
|
2020-02-06 22:03:37 +03:00
|
|
|
|
2021-07-18 19:24:41 +03:00
|
|
|
bool CompositorScreenData::restore_cursor_back(Screen& screen, Gfx::IntRect& last_cursor_rect)
|
2020-08-18 07:45:10 +03:00
|
|
|
{
|
2021-06-13 15:16:06 +03:00
|
|
|
if (!m_cursor_back_is_valid || !m_cursor_back_bitmap || m_cursor_back_bitmap->scale() != m_back_bitmap->scale())
|
|
|
|
return false;
|
2020-08-18 07:45:10 +03:00
|
|
|
|
2021-06-13 15:16:06 +03:00
|
|
|
last_cursor_rect = m_last_cursor_rect.intersected(screen.rect());
|
2021-01-19 20:10:47 +03:00
|
|
|
m_back_painter->blit(last_cursor_rect.location(), *m_cursor_back_bitmap, { { 0, 0 }, last_cursor_rect.size() });
|
2021-07-03 21:43:35 +03:00
|
|
|
m_flush_special_rects.add(last_cursor_rect.intersected(screen.rect()));
|
|
|
|
m_have_flush_rects = true;
|
2021-06-13 15:16:06 +03:00
|
|
|
m_cursor_back_is_valid = false;
|
|
|
|
return true;
|
2020-08-18 07:45:10 +03:00
|
|
|
}
|
|
|
|
|
2021-06-20 22:23:43 +03:00
|
|
|
void Compositor::update_fonts()
|
|
|
|
{
|
|
|
|
ScreenNumberOverlay::pick_font();
|
|
|
|
}
|
|
|
|
|
2020-03-22 23:13:23 +03:00
|
|
|
void Compositor::notify_display_links()
|
|
|
|
{
|
|
|
|
ClientConnection::for_each_client([](auto& client) {
|
|
|
|
client.notify_display_link({});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-04-22 01:07:48 +03:00
|
|
|
void Compositor::increment_display_link_count(Badge<ClientConnection>)
|
|
|
|
{
|
|
|
|
++m_display_link_count;
|
|
|
|
if (m_display_link_count == 1)
|
|
|
|
m_display_link_notify_timer->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Compositor::decrement_display_link_count(Badge<ClientConnection>)
|
|
|
|
{
|
2021-02-23 22:42:32 +03:00
|
|
|
VERIFY(m_display_link_count);
|
2020-04-22 01:07:48 +03:00
|
|
|
--m_display_link_count;
|
|
|
|
if (!m_display_link_count)
|
|
|
|
m_display_link_notify_timer->stop();
|
|
|
|
}
|
|
|
|
|
2021-06-20 22:23:43 +03:00
|
|
|
void Compositor::invalidate_current_screen_number_rects()
|
|
|
|
{
|
2021-07-18 19:24:41 +03:00
|
|
|
Screen::for_each([&](auto& screen) {
|
|
|
|
auto& screen_data = screen.compositor_screen_data();
|
2021-06-20 22:23:43 +03:00
|
|
|
if (screen_data.m_screen_number_overlay)
|
|
|
|
screen_data.m_screen_number_overlay->invalidate();
|
2021-07-18 19:24:41 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
2021-06-20 22:23:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void Compositor::increment_show_screen_number(Badge<ClientConnection>)
|
|
|
|
{
|
|
|
|
if (m_show_screen_number_count++ == 0) {
|
|
|
|
Screen::for_each([&](auto& screen) {
|
2021-07-18 19:24:41 +03:00
|
|
|
auto& screen_data = screen.compositor_screen_data();
|
2021-06-20 22:23:43 +03:00
|
|
|
VERIFY(!screen_data.m_screen_number_overlay);
|
|
|
|
screen_data.m_screen_number_overlay = create_overlay<ScreenNumberOverlay>(screen);
|
|
|
|
screen_data.m_screen_number_overlay->set_enabled(true);
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void Compositor::decrement_show_screen_number(Badge<ClientConnection>)
|
|
|
|
{
|
|
|
|
if (--m_show_screen_number_count == 0) {
|
|
|
|
invalidate_current_screen_number_rects();
|
2021-07-18 19:24:41 +03:00
|
|
|
Screen::for_each([&](auto& screen) {
|
|
|
|
screen.compositor_screen_data().m_screen_number_overlay = nullptr;
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
2021-06-20 22:23:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Compositor::overlays_theme_changed()
|
|
|
|
{
|
|
|
|
for (auto& overlay : m_overlay_list)
|
|
|
|
overlay.theme_changed();
|
|
|
|
overlay_rects_changed();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Compositor::overlay_rects_changed()
|
|
|
|
{
|
|
|
|
if (m_overlay_rects_changed)
|
|
|
|
return;
|
2021-06-25 18:18:20 +03:00
|
|
|
|
2021-06-20 22:23:43 +03:00
|
|
|
m_overlay_rects_changed = true;
|
|
|
|
m_invalidated_any = true;
|
|
|
|
invalidate_occlusions();
|
|
|
|
for (auto& rect : m_overlay_rects.rects())
|
|
|
|
invalidate_screen(rect);
|
2021-06-30 04:51:26 +03:00
|
|
|
start_compose_async_timer();
|
2021-06-20 22:23:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void Compositor::recompute_overlay_rects()
|
|
|
|
{
|
|
|
|
// The purpose of this is to gather all areas that we will render over
|
|
|
|
// regular window contents. This effectively just forces those areas to
|
|
|
|
// be rendered as transparency areas, which allows us to render these
|
|
|
|
// flicker-free.
|
|
|
|
m_overlay_rects.clear_with_capacity();
|
|
|
|
for (auto& overlay : m_overlay_list) {
|
|
|
|
auto& render_rect = overlay.rect();
|
|
|
|
m_overlay_rects.add(render_rect);
|
|
|
|
|
|
|
|
// Save the rectangle we are using for rendering from now on
|
|
|
|
overlay.did_recompute_occlusions();
|
|
|
|
|
|
|
|
// Cache which screens this overlay are rendered on
|
|
|
|
overlay.m_screens.clear_with_capacity();
|
|
|
|
Screen::for_each([&](auto& screen) {
|
|
|
|
if (render_rect.intersects(screen.rect()))
|
|
|
|
overlay.m_screens.append(&screen);
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
|
|
|
|
invalidate_screen(render_rect);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-19 20:02:15 +03:00
|
|
|
void Compositor::recompute_occlusions()
|
|
|
|
{
|
|
|
|
auto& wm = WindowManager::the();
|
2021-06-30 04:51:26 +03:00
|
|
|
bool is_switcher_visible = wm.m_switcher.is_visible();
|
2021-07-02 22:27:39 +03:00
|
|
|
auto never_occlude = [&](WindowStack& window_stack) {
|
2021-07-02 05:42:48 +03:00
|
|
|
if (is_switcher_visible) {
|
|
|
|
switch (wm.m_switcher.mode()) {
|
|
|
|
case WindowSwitcher::Mode::ShowCurrentDesktop:
|
2021-07-02 22:27:39 +03:00
|
|
|
// Any window on the currently rendered desktop should not be occluded, even if it's behind
|
|
|
|
// another window entirely.
|
|
|
|
return &window_stack == m_current_window_stack || &window_stack == m_transitioning_to_window_stack;
|
2021-07-02 05:42:48 +03:00
|
|
|
case WindowSwitcher::Mode::ShowAllWindows:
|
2021-07-02 22:27:39 +03:00
|
|
|
// The window switcher wants to know about all windows, even those on other desktops
|
|
|
|
return true;
|
2021-07-02 05:42:48 +03:00
|
|
|
}
|
|
|
|
}
|
2021-07-02 22:27:39 +03:00
|
|
|
return false;
|
|
|
|
};
|
2021-07-02 05:42:48 +03:00
|
|
|
|
2021-07-02 22:27:39 +03:00
|
|
|
wm.for_each_window_stack([&](WindowStack& window_stack) {
|
|
|
|
if (&window_stack == m_current_window_stack || &window_stack == m_transitioning_to_window_stack) {
|
|
|
|
// We'll calculate precise occlusions for these further down. Changing occlusions right now
|
|
|
|
// may trigger an additional unnecessary notification
|
|
|
|
} else {
|
|
|
|
window_stack.set_all_occluded(!never_occlude(window_stack));
|
|
|
|
}
|
2021-06-30 04:51:26 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
2020-08-18 07:45:10 +03:00
|
|
|
|
2021-06-20 22:23:43 +03:00
|
|
|
if (m_overlay_rects_changed) {
|
|
|
|
m_overlay_rects_changed = false;
|
|
|
|
recompute_overlay_rects();
|
|
|
|
}
|
|
|
|
|
|
|
|
if constexpr (OCCLUSIONS_DEBUG) {
|
|
|
|
dbgln("OCCLUSIONS:");
|
|
|
|
for (auto& rect : m_overlay_rects.rects())
|
|
|
|
dbgln(" overlay: {}", rect);
|
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
|
2021-06-30 04:51:26 +03:00
|
|
|
bool window_stack_transition_in_progress = m_transitioning_to_window_stack != nullptr;
|
2021-06-13 15:16:06 +03:00
|
|
|
auto& main_screen = Screen::main();
|
2021-07-06 22:59:58 +03:00
|
|
|
auto* fullscreen_window = wm.active_fullscreen_window();
|
|
|
|
if (fullscreen_window) {
|
2021-06-13 15:16:06 +03:00
|
|
|
// TODO: support fullscreen windows on all screens
|
|
|
|
auto screen_rect = main_screen.rect();
|
2021-06-30 04:51:26 +03:00
|
|
|
wm.for_each_visible_window_from_front_to_back([&](Window& w) {
|
2020-08-18 07:45:10 +03:00
|
|
|
auto& visible_opaque = w.opaque_rects();
|
|
|
|
auto& transparency_rects = w.transparency_rects();
|
|
|
|
auto& transparency_wallpaper_rects = w.transparency_wallpaper_rects();
|
2021-07-19 05:44:32 +03:00
|
|
|
w.affected_transparency_rects().clear();
|
2021-06-13 15:16:06 +03:00
|
|
|
w.screens().clear_with_capacity();
|
2020-08-18 07:45:10 +03:00
|
|
|
if (&w == fullscreen_window) {
|
2021-06-13 15:16:06 +03:00
|
|
|
w.screens().append(&main_screen);
|
2020-08-18 07:45:10 +03:00
|
|
|
if (w.is_opaque()) {
|
|
|
|
visible_opaque = screen_rect;
|
|
|
|
transparency_rects.clear();
|
|
|
|
transparency_wallpaper_rects.clear();
|
|
|
|
} else {
|
|
|
|
visible_opaque.clear();
|
|
|
|
transparency_rects = screen_rect;
|
|
|
|
transparency_wallpaper_rects = screen_rect;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
visible_opaque.clear();
|
|
|
|
transparency_rects.clear();
|
|
|
|
transparency_wallpaper_rects.clear();
|
|
|
|
}
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
|
|
|
|
m_opaque_wallpaper_rects.clear();
|
2021-07-06 22:59:58 +03:00
|
|
|
}
|
|
|
|
if (!fullscreen_window || (fullscreen_window && !fullscreen_window->is_opaque())) {
|
2021-07-18 06:15:17 +03:00
|
|
|
Gfx::DisjointRectSet remaining_visible_screen_rects;
|
|
|
|
remaining_visible_screen_rects.add_many(Screen::rects());
|
2020-08-18 07:45:10 +03:00
|
|
|
bool have_transparent = false;
|
2021-06-30 04:51:26 +03:00
|
|
|
wm.for_each_visible_window_from_front_to_back([&](Window& w) {
|
2021-07-05 00:47:17 +03:00
|
|
|
VERIFY(!w.is_minimized());
|
2020-08-18 07:45:10 +03:00
|
|
|
w.transparency_wallpaper_rects().clear();
|
2021-07-05 00:47:17 +03:00
|
|
|
auto previous_visible_opaque = move(w.opaque_rects());
|
|
|
|
auto previous_visible_transparency = move(w.transparency_rects());
|
|
|
|
|
|
|
|
auto invalidate_previous_render_rects = [&](Gfx::IntRect const& new_render_rect) {
|
|
|
|
if (!previous_visible_opaque.is_empty()) {
|
|
|
|
if (new_render_rect.is_empty())
|
|
|
|
invalidate_screen(previous_visible_opaque);
|
|
|
|
else
|
|
|
|
invalidate_screen(previous_visible_opaque.shatter(new_render_rect));
|
|
|
|
}
|
|
|
|
if (!previous_visible_transparency.is_empty()) {
|
|
|
|
if (new_render_rect.is_empty())
|
|
|
|
invalidate_screen(previous_visible_transparency);
|
|
|
|
else
|
|
|
|
invalidate_screen(previous_visible_transparency.shatter(new_render_rect));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-08-18 07:45:10 +03:00
|
|
|
auto& visible_opaque = w.opaque_rects();
|
|
|
|
auto& transparency_rects = w.transparency_rects();
|
2021-07-05 00:47:17 +03:00
|
|
|
bool should_invalidate_old = w.should_invalidate_last_rendered_screen_rects();
|
|
|
|
|
2021-07-18 06:15:17 +03:00
|
|
|
auto& affected_transparency_rects = w.affected_transparency_rects();
|
|
|
|
affected_transparency_rects.clear();
|
|
|
|
|
2021-06-13 15:16:06 +03:00
|
|
|
w.screens().clear_with_capacity();
|
2020-08-18 07:45:10 +03:00
|
|
|
|
2021-06-30 04:51:26 +03:00
|
|
|
auto transition_offset = window_transition_offset(w);
|
2021-06-13 15:16:06 +03:00
|
|
|
auto transparent_frame_render_rects = w.frame().transparent_render_rects();
|
|
|
|
auto opaque_frame_render_rects = w.frame().opaque_render_rects();
|
2021-06-30 04:51:26 +03:00
|
|
|
if (window_stack_transition_in_progress) {
|
|
|
|
transparent_frame_render_rects.translate_by(transition_offset);
|
|
|
|
opaque_frame_render_rects.translate_by(transition_offset);
|
|
|
|
}
|
2021-07-05 00:47:17 +03:00
|
|
|
if (should_invalidate_old) {
|
|
|
|
for (auto& rect : opaque_frame_render_rects.rects())
|
|
|
|
invalidate_previous_render_rects(rect);
|
|
|
|
for (auto& rect : transparent_frame_render_rects.rects())
|
|
|
|
invalidate_previous_render_rects(rect);
|
|
|
|
}
|
2021-06-05 23:37:48 +03:00
|
|
|
|
2021-07-18 06:15:17 +03:00
|
|
|
if (auto transparent_render_rects = transparent_frame_render_rects.intersected(remaining_visible_screen_rects); !transparent_render_rects.is_empty())
|
|
|
|
transparency_rects = move(transparent_render_rects);
|
|
|
|
if (auto opaque_render_rects = opaque_frame_render_rects.intersected(remaining_visible_screen_rects); !opaque_render_rects.is_empty())
|
|
|
|
visible_opaque = move(opaque_render_rects);
|
|
|
|
|
|
|
|
auto render_rect_on_screen = w.frame().render_rect().translated(transition_offset);
|
|
|
|
auto visible_window_rects = remaining_visible_screen_rects.intersected(w.rect().translated(transition_offset));
|
2021-06-05 23:37:48 +03:00
|
|
|
Gfx::DisjointRectSet opaque_covering;
|
2021-07-18 06:15:17 +03:00
|
|
|
Gfx::DisjointRectSet transparent_covering;
|
2020-08-18 07:45:10 +03:00
|
|
|
bool found_this_window = false;
|
2021-06-30 04:51:26 +03:00
|
|
|
wm.for_each_visible_window_from_back_to_front([&](Window& w2) {
|
2020-08-18 07:45:10 +03:00
|
|
|
if (!found_this_window) {
|
|
|
|
if (&w == &w2)
|
|
|
|
found_this_window = true;
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
}
|
|
|
|
|
2021-07-05 00:47:17 +03:00
|
|
|
VERIFY(!w2.is_minimized());
|
2021-06-05 23:37:48 +03:00
|
|
|
|
2021-06-30 04:51:26 +03:00
|
|
|
auto w2_render_rect = w2.frame().render_rect();
|
|
|
|
auto w2_render_rect_on_screen = w2_render_rect;
|
|
|
|
auto w2_transition_offset = window_transition_offset(w2);
|
|
|
|
if (window_stack_transition_in_progress)
|
|
|
|
w2_render_rect_on_screen.translate_by(w2_transition_offset);
|
|
|
|
if (!render_rect_on_screen.intersects(w2_render_rect_on_screen))
|
2021-06-05 23:37:48 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
2021-06-30 04:51:26 +03:00
|
|
|
auto opaque_rects = w2.frame().opaque_render_rects();
|
|
|
|
auto transparent_rects = w2.frame().transparent_render_rects();
|
|
|
|
if (window_stack_transition_in_progress) {
|
|
|
|
auto transition_offset_2 = window_transition_offset(w2);
|
|
|
|
opaque_rects.translate_by(transition_offset_2);
|
|
|
|
transparent_rects.translate_by(transition_offset_2);
|
|
|
|
}
|
|
|
|
opaque_rects = opaque_rects.intersected(render_rect_on_screen);
|
|
|
|
transparent_rects = transparent_rects.intersected(render_rect_on_screen);
|
2021-06-05 23:37:48 +03:00
|
|
|
if (opaque_rects.is_empty() && transparent_rects.is_empty())
|
2020-08-18 07:45:10 +03:00
|
|
|
return IterationDecision::Continue;
|
2021-07-18 06:15:17 +03:00
|
|
|
VERIFY(!opaque_rects.intersects(transparent_rects));
|
2021-06-05 23:37:48 +03:00
|
|
|
for (auto& covering : opaque_rects.rects()) {
|
2021-02-08 03:44:04 +03:00
|
|
|
opaque_covering.add(covering);
|
2021-07-02 22:27:39 +03:00
|
|
|
if (!visible_window_rects.is_empty())
|
|
|
|
visible_window_rects = visible_window_rects.shatter(covering);
|
2020-08-18 07:45:10 +03:00
|
|
|
if (!visible_opaque.is_empty()) {
|
2021-02-08 03:44:04 +03:00
|
|
|
auto uncovered_opaque = visible_opaque.shatter(covering);
|
2020-08-18 07:45:10 +03:00
|
|
|
visible_opaque = move(uncovered_opaque);
|
|
|
|
}
|
|
|
|
if (!transparency_rects.is_empty()) {
|
2021-02-08 03:44:04 +03:00
|
|
|
auto uncovered_transparency = transparency_rects.shatter(covering);
|
2020-08-18 07:45:10 +03:00
|
|
|
transparency_rects = move(uncovered_transparency);
|
|
|
|
}
|
2021-07-18 06:15:17 +03:00
|
|
|
if (!transparent_covering.is_empty()) {
|
|
|
|
auto uncovered_transparency = transparent_covering.shatter(covering);
|
|
|
|
transparent_covering = move(uncovered_transparency);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!transparent_rects.is_empty())
|
|
|
|
transparent_covering.add(transparent_rects.shatter(opaque_covering));
|
|
|
|
VERIFY(!transparent_covering.intersects(opaque_covering));
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
VERIFY(opaque_covering.is_empty() || render_rect_on_screen.contains(opaque_covering.rects()));
|
|
|
|
if (!m_overlay_rects.is_empty() && m_overlay_rects.intersects(visible_opaque)) {
|
|
|
|
// In order to render overlays flicker-free we need to force this area into the
|
|
|
|
// temporary transparency rendering buffer
|
|
|
|
transparent_covering.add(m_overlay_rects.intersected(visible_opaque));
|
|
|
|
}
|
|
|
|
if (!transparent_covering.is_empty()) {
|
|
|
|
VERIFY(!transparent_covering.intersects(opaque_covering));
|
|
|
|
transparency_rects.add(transparent_covering);
|
|
|
|
if (!visible_opaque.is_empty()) {
|
|
|
|
auto uncovered_opaque = visible_opaque.shatter(transparent_covering);
|
|
|
|
visible_opaque = move(uncovered_opaque);
|
2021-06-05 23:37:48 +03:00
|
|
|
}
|
2021-07-02 22:27:39 +03:00
|
|
|
|
2021-07-18 06:15:17 +03:00
|
|
|
// Now that we know what transparency rectangles are immediately covering our window
|
|
|
|
// figure out what windows they belong to and add them to the affected transparency rects.
|
|
|
|
// We can't do the same with the windows below as we haven't gotten to those yet. These
|
|
|
|
// will be determined after we're done with this pass.
|
|
|
|
found_this_window = false;
|
|
|
|
wm.for_each_visible_window_from_back_to_front([&](Window& w2) {
|
|
|
|
if (!found_this_window) {
|
|
|
|
if (&w == &w2)
|
|
|
|
found_this_window = true;
|
2020-08-18 07:45:10 +03:00
|
|
|
return IterationDecision::Continue;
|
2021-07-18 06:15:17 +03:00
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
|
2021-07-18 06:15:17 +03:00
|
|
|
auto affected_transparency = transparent_covering.intersected(w2.transparency_rects());
|
|
|
|
if (!affected_transparency.is_empty()) {
|
|
|
|
auto result = affected_transparency_rects.set(&w2, move(affected_transparency));
|
|
|
|
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
|
|
|
|
}
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
|
2021-07-02 22:27:39 +03:00
|
|
|
// This window should not be occluded while the window switcher is interested in it (depending
|
|
|
|
// on the mode it's in). If it isn't then determine occlusions based on whether the window
|
|
|
|
// rect has any visible areas at all.
|
2021-07-02 22:52:23 +03:00
|
|
|
w.set_occluded(never_occlude(w.window_stack()) ? false : visible_window_rects.is_empty());
|
2021-07-02 22:27:39 +03:00
|
|
|
|
2021-06-13 15:16:06 +03:00
|
|
|
bool have_opaque = !visible_opaque.is_empty();
|
2020-08-18 07:45:10 +03:00
|
|
|
if (!transparency_rects.is_empty())
|
|
|
|
have_transparent = true;
|
2021-06-13 15:16:06 +03:00
|
|
|
if (have_transparent || have_opaque) {
|
|
|
|
// Figure out what screens this window is rendered on
|
|
|
|
// We gather this information so we can more quickly
|
|
|
|
// render the window on each of the screens that it
|
|
|
|
// needs to be rendered on.
|
|
|
|
Screen::for_each([&](auto& screen) {
|
|
|
|
auto screen_rect = screen.rect();
|
|
|
|
for (auto& r : visible_opaque.rects()) {
|
|
|
|
if (r.intersects(screen_rect)) {
|
|
|
|
w.screens().append(&screen);
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (auto& r : transparency_rects.rects()) {
|
|
|
|
if (r.intersects(screen_rect)) {
|
|
|
|
w.screens().append(&screen);
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
|
2021-07-20 01:26:26 +03:00
|
|
|
if (!visible_opaque.is_empty()) {
|
|
|
|
VERIFY(!visible_opaque.intersects(transparency_rects));
|
2020-08-18 07:45:10 +03:00
|
|
|
|
2021-07-20 01:26:26 +03:00
|
|
|
// Determine visible area for the window below
|
|
|
|
remaining_visible_screen_rects = remaining_visible_screen_rects.shatter(visible_opaque);
|
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (have_transparent) {
|
2021-07-18 06:15:17 +03:00
|
|
|
// Also, now that we have completed the first pass we can determine the affected
|
|
|
|
// transparency rects below a given window
|
2021-06-30 04:51:26 +03:00
|
|
|
wm.for_each_visible_window_from_back_to_front([&](Window& w) {
|
2021-07-18 06:15:17 +03:00
|
|
|
// Any area left in remaining_visible_screen_rects will need to be rendered with the wallpaper first
|
|
|
|
auto& transparency_rects = w.transparency_rects();
|
2020-08-18 07:45:10 +03:00
|
|
|
auto& transparency_wallpaper_rects = w.transparency_wallpaper_rects();
|
2021-07-20 01:26:26 +03:00
|
|
|
if (transparency_rects.is_empty()) {
|
|
|
|
VERIFY(transparency_wallpaper_rects.is_empty()); // Should have been cleared in the first pass
|
|
|
|
} else {
|
2021-07-18 06:15:17 +03:00
|
|
|
transparency_wallpaper_rects = remaining_visible_screen_rects.intersected(transparency_rects);
|
2020-09-18 10:49:51 +03:00
|
|
|
|
2021-07-20 01:26:26 +03:00
|
|
|
if (!transparency_wallpaper_rects.is_empty()) {
|
|
|
|
auto remaining_visible = remaining_visible_screen_rects.shatter(transparency_wallpaper_rects);
|
|
|
|
remaining_visible_screen_rects = move(remaining_visible);
|
|
|
|
}
|
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
|
2021-07-18 06:15:17 +03:00
|
|
|
// Figure out the affected transparency rects underneath. First figure out if any transparency is visible at all
|
|
|
|
Gfx::DisjointRectSet transparent_underneath;
|
|
|
|
wm.for_each_visible_window_from_back_to_front([&](Window& w2) {
|
|
|
|
if (&w == &w2)
|
|
|
|
return IterationDecision::Break;
|
|
|
|
auto& opaque_rects2 = w2.opaque_rects();
|
|
|
|
if (!opaque_rects2.is_empty()) {
|
|
|
|
auto uncovered_transparency = transparent_underneath.shatter(opaque_rects2);
|
|
|
|
transparent_underneath = move(uncovered_transparency);
|
|
|
|
}
|
|
|
|
w2.transparency_rects().for_each_intersected(transparency_rects, [&](auto& rect) {
|
|
|
|
transparent_underneath.add(rect);
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
if (!transparent_underneath.is_empty()) {
|
|
|
|
// Now that we know there are some transparency rects underneath that are visible
|
|
|
|
// figure out what windows they belong to
|
|
|
|
auto& affected_transparency_rects = w.affected_transparency_rects();
|
|
|
|
wm.for_each_visible_window_from_back_to_front([&](Window& w2) {
|
|
|
|
if (&w == &w2)
|
|
|
|
return IterationDecision::Break;
|
|
|
|
auto& transparency_rects2 = w2.transparency_rects();
|
|
|
|
if (transparency_rects2.is_empty())
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
|
|
|
auto affected_transparency = transparent_underneath.intersected(transparency_rects2);
|
|
|
|
if (!affected_transparency.is_empty()) {
|
|
|
|
auto result = affected_transparency_rects.set(&w2, move(affected_transparency));
|
|
|
|
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
|
|
|
|
}
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-07-20 01:26:26 +03:00
|
|
|
m_transparent_wallpaper_rects.clear_with_capacity();
|
|
|
|
if (!m_overlay_rects.is_empty() && m_overlay_rects.intersects(remaining_visible_screen_rects)) {
|
|
|
|
// Check if any overlay rects are remaining that are not somehow above any windows
|
|
|
|
m_transparent_wallpaper_rects = m_overlay_rects.intersected(remaining_visible_screen_rects);
|
|
|
|
auto remaining_visible_not_covered = remaining_visible_screen_rects.shatter(m_overlay_rects);
|
|
|
|
remaining_visible_screen_rects = move(remaining_visible_not_covered);
|
|
|
|
}
|
|
|
|
|
2021-07-18 06:15:17 +03:00
|
|
|
m_opaque_wallpaper_rects = move(remaining_visible_screen_rects);
|
2020-08-18 07:45:10 +03:00
|
|
|
}
|
|
|
|
|
2021-01-24 01:59:27 +03:00
|
|
|
if constexpr (OCCLUSIONS_DEBUG) {
|
2021-01-17 20:39:00 +03:00
|
|
|
for (auto& r : m_opaque_wallpaper_rects.rects())
|
|
|
|
dbgln(" wallpaper opaque: {}", r);
|
|
|
|
}
|
2020-08-18 07:45:10 +03:00
|
|
|
|
2021-06-30 04:51:26 +03:00
|
|
|
wm.for_each_visible_window_from_back_to_front([&](Window& w) {
|
2021-06-13 15:16:06 +03:00
|
|
|
auto window_frame_rect = w.frame().render_rect();
|
|
|
|
if (w.is_minimized() || window_frame_rect.is_empty() || w.screens().is_empty())
|
2020-08-18 07:45:10 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
|
2021-01-24 01:59:27 +03:00
|
|
|
if constexpr (OCCLUSIONS_DEBUG) {
|
2021-06-13 15:16:06 +03:00
|
|
|
dbgln(" Window {} frame rect: {} rendered on screens: {}", w.title(), window_frame_rect, w.screens().size());
|
|
|
|
for (auto& s : w.screens())
|
|
|
|
dbgln(" screen: #{}", s->index());
|
2021-01-17 20:39:00 +03:00
|
|
|
for (auto& r : w.opaque_rects().rects())
|
|
|
|
dbgln(" opaque: {}", r);
|
|
|
|
for (auto& r : w.transparency_wallpaper_rects().rects())
|
|
|
|
dbgln(" transparent wallpaper: {}", r);
|
|
|
|
for (auto& r : w.transparency_rects().rects())
|
|
|
|
dbgln(" transparent: {}", r);
|
2021-07-18 06:15:17 +03:00
|
|
|
for (auto& it : w.affected_transparency_rects()) {
|
|
|
|
dbgln(" affects {}:", it.key->title());
|
|
|
|
for (auto& r : it.value.rects())
|
|
|
|
dbgln(" transparent: {}", r);
|
|
|
|
}
|
2021-01-17 20:39:00 +03:00
|
|
|
}
|
|
|
|
|
2021-02-23 22:42:32 +03:00
|
|
|
VERIFY(!w.opaque_rects().intersects(m_opaque_wallpaper_rects));
|
|
|
|
VERIFY(!w.transparency_rects().intersects(m_opaque_wallpaper_rects));
|
|
|
|
VERIFY(!w.transparency_wallpaper_rects().intersects(m_opaque_wallpaper_rects));
|
2020-08-18 07:45:10 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
2020-05-19 20:02:15 +03:00
|
|
|
}
|
|
|
|
|
2021-06-27 17:47:52 +03:00
|
|
|
void Compositor::register_animation(Badge<Animation>, Animation& animation)
|
|
|
|
{
|
2021-06-29 02:42:13 +03:00
|
|
|
bool was_empty = m_animations.is_empty();
|
2021-06-27 17:47:52 +03:00
|
|
|
auto result = m_animations.set(&animation);
|
|
|
|
VERIFY(result == AK::HashSetResult::InsertedNewEntry);
|
2021-06-29 02:42:13 +03:00
|
|
|
if (was_empty)
|
|
|
|
start_compose_async_timer();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Compositor::animation_started(Badge<Animation>)
|
|
|
|
{
|
|
|
|
m_invalidated_any = true;
|
|
|
|
start_compose_async_timer();
|
2021-06-27 17:47:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void Compositor::unregister_animation(Badge<Animation>, Animation& animation)
|
|
|
|
{
|
|
|
|
bool was_removed = m_animations.remove(&animation);
|
|
|
|
VERIFY(was_removed);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Compositor::update_animations(Screen& screen, Gfx::DisjointRectSet& flush_rects)
|
|
|
|
{
|
2021-07-18 19:24:41 +03:00
|
|
|
auto& painter = *screen.compositor_screen_data().m_back_painter;
|
2021-06-27 17:47:52 +03:00
|
|
|
for (RefPtr<Animation> animation : m_animations) {
|
|
|
|
animation->update({}, painter, screen, flush_rects);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-30 04:51:26 +03:00
|
|
|
void Compositor::create_window_stack_switch_overlay(WindowStack& target_stack)
|
|
|
|
{
|
2021-07-01 04:12:02 +03:00
|
|
|
stop_window_stack_switch_overlay_timer();
|
2021-06-30 04:51:26 +03:00
|
|
|
Screen::for_each([&](auto& screen) {
|
2021-07-18 19:24:41 +03:00
|
|
|
auto& screen_data = screen.compositor_screen_data();
|
2021-06-30 04:51:26 +03:00
|
|
|
screen_data.m_window_stack_switch_overlay = nullptr; // delete it first
|
|
|
|
screen_data.m_window_stack_switch_overlay = create_overlay<WindowStackSwitchOverlay>(screen, target_stack);
|
|
|
|
screen_data.m_window_stack_switch_overlay->set_enabled(true);
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-07-01 04:12:02 +03:00
|
|
|
void Compositor::remove_window_stack_switch_overlays()
|
|
|
|
{
|
|
|
|
Screen::for_each([&](auto& screen) {
|
2021-07-18 19:24:41 +03:00
|
|
|
screen.compositor_screen_data().m_window_stack_switch_overlay = nullptr;
|
2021-07-01 04:12:02 +03:00
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void Compositor::stop_window_stack_switch_overlay_timer()
|
|
|
|
{
|
|
|
|
if (m_stack_switch_overlay_timer) {
|
|
|
|
// Cancel any timer, we're going to delete the overlay
|
|
|
|
m_stack_switch_overlay_timer->stop();
|
|
|
|
m_stack_switch_overlay_timer = nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-30 04:51:26 +03:00
|
|
|
void Compositor::start_window_stack_switch_overlay_timer()
|
|
|
|
{
|
|
|
|
if (m_stack_switch_overlay_timer) {
|
|
|
|
m_stack_switch_overlay_timer->stop();
|
|
|
|
m_stack_switch_overlay_timer = nullptr;
|
|
|
|
}
|
2021-07-01 04:12:02 +03:00
|
|
|
bool have_overlay = false;
|
|
|
|
Screen::for_each([&](auto& screen) {
|
2021-07-18 19:24:41 +03:00
|
|
|
if (screen.compositor_screen_data().m_window_stack_switch_overlay) {
|
2021-07-01 04:12:02 +03:00
|
|
|
have_overlay = true;
|
|
|
|
return IterationDecision::Break;
|
|
|
|
}
|
|
|
|
return IterationDecision::Continue;
|
|
|
|
});
|
|
|
|
if (!have_overlay)
|
|
|
|
return;
|
2021-06-30 04:51:26 +03:00
|
|
|
m_stack_switch_overlay_timer = Core::Timer::create_single_shot(
|
|
|
|
500,
|
|
|
|
[this] {
|
2021-07-01 04:12:02 +03:00
|
|
|
remove_window_stack_switch_overlays();
|
2021-06-30 04:51:26 +03:00
|
|
|
},
|
|
|
|
this);
|
|
|
|
m_stack_switch_overlay_timer->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Compositor::finish_window_stack_switch()
|
|
|
|
{
|
|
|
|
VERIFY(m_transitioning_to_window_stack);
|
|
|
|
VERIFY(m_current_window_stack);
|
|
|
|
VERIFY(m_transitioning_to_window_stack != m_current_window_stack);
|
|
|
|
|
|
|
|
m_current_window_stack->set_transition_offset({}, {});
|
|
|
|
m_transitioning_to_window_stack->set_transition_offset({}, {});
|
|
|
|
|
|
|
|
auto* previous_window_stack = m_current_window_stack;
|
|
|
|
m_current_window_stack = m_transitioning_to_window_stack;
|
|
|
|
m_transitioning_to_window_stack = nullptr;
|
|
|
|
|
|
|
|
m_window_stack_transition_animation = nullptr;
|
|
|
|
|
|
|
|
auto& wm = WindowManager::the();
|
|
|
|
if (!wm.m_switcher.is_visible())
|
|
|
|
previous_window_stack->set_all_occluded(true);
|
|
|
|
wm.did_switch_window_stack({}, *previous_window_stack, *m_current_window_stack);
|
|
|
|
|
|
|
|
invalidate_occlusions();
|
|
|
|
|
|
|
|
// Rather than invalidating the entire we could invalidate all render rectangles
|
|
|
|
// that are affected by the transition offset before and after changing it.
|
|
|
|
invalidate_screen();
|
|
|
|
|
|
|
|
start_window_stack_switch_overlay_timer();
|
|
|
|
}
|
|
|
|
|
2021-07-01 04:12:02 +03:00
|
|
|
void Compositor::set_current_window_stack_no_transition(WindowStack& new_window_stack)
|
|
|
|
{
|
|
|
|
if (m_transitioning_to_window_stack) {
|
|
|
|
finish_window_stack_switch();
|
|
|
|
VERIFY(!m_window_stack_transition_animation);
|
|
|
|
VERIFY(!m_transitioning_to_window_stack);
|
|
|
|
}
|
|
|
|
if (m_current_window_stack == &new_window_stack)
|
|
|
|
return;
|
|
|
|
m_current_window_stack = &new_window_stack;
|
|
|
|
invalidate_for_window_stack_merge_or_change();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Compositor::invalidate_for_window_stack_merge_or_change()
|
|
|
|
{
|
|
|
|
invalidate_occlusions();
|
|
|
|
invalidate_screen();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Compositor::switch_to_window_stack(WindowStack& new_window_stack, bool show_overlay)
|
2021-06-30 04:51:26 +03:00
|
|
|
{
|
|
|
|
if (m_transitioning_to_window_stack) {
|
|
|
|
if (m_transitioning_to_window_stack == &new_window_stack)
|
|
|
|
return;
|
|
|
|
// A switch is in progress, but the user is impatient. Finish the transition instantly
|
|
|
|
finish_window_stack_switch();
|
|
|
|
VERIFY(!m_window_stack_transition_animation);
|
|
|
|
// Now switch to the next target as usual
|
|
|
|
}
|
|
|
|
VERIFY(m_current_window_stack);
|
|
|
|
|
|
|
|
if (&new_window_stack == m_current_window_stack) {
|
|
|
|
// So that the user knows which stack they're on, show the overlay briefly
|
2021-07-01 04:12:02 +03:00
|
|
|
if (show_overlay) {
|
|
|
|
create_window_stack_switch_overlay(*m_current_window_stack);
|
|
|
|
start_window_stack_switch_overlay_timer();
|
|
|
|
} else {
|
|
|
|
stop_window_stack_switch_overlay_timer();
|
|
|
|
remove_window_stack_switch_overlays();
|
|
|
|
}
|
2021-06-30 04:51:26 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
VERIFY(!m_transitioning_to_window_stack);
|
|
|
|
m_transitioning_to_window_stack = &new_window_stack;
|
|
|
|
|
|
|
|
auto window_stack_size = Screen::bounding_rect().size();
|
|
|
|
|
|
|
|
int delta_x = 0;
|
|
|
|
if (new_window_stack.column() < m_current_window_stack->column())
|
|
|
|
delta_x = window_stack_size.width();
|
|
|
|
else if (new_window_stack.column() > m_current_window_stack->column())
|
|
|
|
delta_x = -window_stack_size.width();
|
|
|
|
int delta_y = 0;
|
|
|
|
if (new_window_stack.row() < m_current_window_stack->row())
|
|
|
|
delta_y = window_stack_size.height();
|
|
|
|
else if (new_window_stack.row() > m_current_window_stack->row()) {
|
|
|
|
delta_y = -window_stack_size.height();
|
|
|
|
}
|
|
|
|
|
|
|
|
m_transitioning_to_window_stack->set_transition_offset({}, { -delta_x, -delta_y });
|
|
|
|
m_current_window_stack->set_transition_offset({}, {});
|
|
|
|
|
2021-07-01 04:12:02 +03:00
|
|
|
if (show_overlay) {
|
|
|
|
// We start the timer when the animation ends!
|
|
|
|
create_window_stack_switch_overlay(*m_transitioning_to_window_stack);
|
|
|
|
} else {
|
|
|
|
stop_window_stack_switch_overlay_timer();
|
|
|
|
remove_window_stack_switch_overlays();
|
|
|
|
}
|
2021-06-30 04:51:26 +03:00
|
|
|
|
|
|
|
VERIFY(!m_window_stack_transition_animation);
|
|
|
|
m_window_stack_transition_animation = Animation::create();
|
|
|
|
m_window_stack_transition_animation->set_duration(250);
|
|
|
|
m_window_stack_transition_animation->on_update = [this, delta_x, delta_y](float progress, Gfx::Painter&, Screen&, Gfx::DisjointRectSet&) {
|
|
|
|
VERIFY(m_transitioning_to_window_stack);
|
|
|
|
VERIFY(m_current_window_stack);
|
|
|
|
|
|
|
|
// Set transition offset for the window stack we're transitioning out of
|
|
|
|
auto previous_transition_offset_from = m_current_window_stack->transition_offset();
|
|
|
|
Gfx::IntPoint transition_offset_from { (float)delta_x * progress, (float)delta_y * progress };
|
|
|
|
if (previous_transition_offset_from == transition_offset_from)
|
|
|
|
return;
|
|
|
|
|
|
|
|
{
|
|
|
|
// we need to render both, the existing dirty rectangles as well as where we're shifting to
|
|
|
|
auto translated_dirty_rects = m_dirty_screen_rects.clone();
|
|
|
|
auto transition_delta = transition_offset_from - previous_transition_offset_from;
|
|
|
|
translated_dirty_rects.translate_by(transition_delta);
|
|
|
|
m_dirty_screen_rects.add(translated_dirty_rects.intersected(Screen::bounding_rect()));
|
|
|
|
}
|
|
|
|
m_current_window_stack->set_transition_offset({}, transition_offset_from);
|
|
|
|
|
|
|
|
// Set transition offset for the window stack we're transitioning to
|
|
|
|
Gfx::IntPoint transition_offset_to { (float)-delta_x * (1.0f - progress), (float)-delta_y * (1.0f - progress) };
|
|
|
|
m_transitioning_to_window_stack->set_transition_offset({}, transition_offset_to);
|
|
|
|
|
|
|
|
invalidate_occlusions();
|
|
|
|
|
|
|
|
// Rather than invalidating the entire we could invalidate all render rectangles
|
|
|
|
// that are affected by the transition offset before and after changing it.
|
|
|
|
invalidate_screen();
|
|
|
|
};
|
|
|
|
|
|
|
|
m_window_stack_transition_animation->on_stop = [this] {
|
|
|
|
finish_window_stack_switch();
|
|
|
|
};
|
|
|
|
m_window_stack_transition_animation->start();
|
|
|
|
}
|
|
|
|
|
2020-02-06 22:03:37 +03:00
|
|
|
}
|