WindowServer: Add an Overlay class for flicker-free overlay rendering

An Overlay is similar to a transparent window, but has less overhead
and does not get rendered within the window stack. Basically, the area
that an Overlay occupies forces transparency rendering for any window
underneath, which allows us to render them flicker-free.

This also adds a new API that allows displaying the screen numbers,
e.g. while the user configures the screen layout in DisplaySettings

Because other things like drag&drop or the window-size label are not
yet converted to use this new mechanism, they will be drawn over the
screen-number currently.
This commit is contained in:
Tom 2021-06-20 13:23:43 -06:00 committed by Andreas Kling
parent 42cb38b71a
commit 41859ad3fe
14 changed files with 638 additions and 7 deletions

View file

@ -38,6 +38,9 @@ Drag=/res/cursors/drag.png
Wait=/res/cursors/wait.f14t100.png
Crosshair=/res/cursors/crosshair.png
[Graphics]
OverlayRectShadow=/res/graphics/overlay-rect-shadow.png
[Input]
DoubleClickSpeed=250

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -22,6 +22,7 @@ set(SOURCES
MenuItem.cpp
MenuManager.cpp
MultiScaleBitmaps.cpp
Overlays.cpp
Screen.cpp
ScreenLayout.cpp
Window.cpp

View file

@ -68,6 +68,9 @@ ClientConnection::~ClientConnection()
if (window.value->type() == WindowType::Applet)
AppletManager::the().remove_applet(window.value);
}
if (m_show_screen_number)
Compositor::the().decrement_show_screen_number({});
}
void ClientConnection::die()
@ -316,6 +319,17 @@ Messages::WindowServer::SaveScreenLayoutResponse ClientConnection::save_screen_l
return { success, move(error_msg) };
}
void ClientConnection::show_screen_numbers(bool show)
{
if (m_show_screen_number == show)
return;
m_show_screen_number = show;
if (show)
Compositor::the().increment_show_screen_number({});
else
Compositor::the().decrement_show_screen_number({});
}
void ClientConnection::set_window_title(i32 window_id, String const& title)
{
auto it = m_windows.find(window_id);

View file

@ -130,6 +130,7 @@ private:
virtual Messages::WindowServer::SetScreenLayoutResponse set_screen_layout(ScreenLayout const&, bool) override;
virtual Messages::WindowServer::GetScreenLayoutResponse get_screen_layout() override;
virtual Messages::WindowServer::SaveScreenLayoutResponse save_screen_layout() override;
virtual void show_screen_numbers(bool) override;
virtual void set_window_cursor(i32, i32) override;
virtual void set_window_custom_cursor(i32, Gfx::ShareableBitmap const&) override;
virtual void popup_menu(i32, Gfx::IntPoint const&) override;
@ -168,6 +169,7 @@ private:
RefPtr<Core::Timer> m_ping_timer;
bool m_has_display_link { false };
bool m_show_screen_number { false };
bool m_unresponsive { false };
// Need this to get private client connection stuff

View file

@ -8,6 +8,7 @@
#include "ClientConnection.h"
#include "Event.h"
#include "EventLoop.h"
#include "MultiScaleBitmaps.h"
#include "Screen.h"
#include "Window.h"
#include "WindowManager.h"
@ -76,7 +77,7 @@ const Gfx::Bitmap& Compositor::front_bitmap_for_screenshot(Badge<ClientConnectio
return *m_screen_data[screen.index()].m_front_bitmap;
}
void Compositor::ScreenData::init_bitmaps(Screen& screen)
void Compositor::ScreenData::init_bitmaps(Compositor& compositor, Screen& screen)
{
auto size = screen.size();
@ -97,13 +98,21 @@ void Compositor::ScreenData::init_bitmaps(Screen& screen)
m_buffers_are_flipped = false;
m_screen_can_set_buffer = screen.can_set_buffer();
// 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;
}
}
void Compositor::init_bitmaps()
{
m_screen_data.resize(Screen::count());
Screen::for_each([&](auto& screen) {
m_screen_data[screen.index()].init_bitmaps(screen);
m_screen_data[screen.index()].init_bitmaps(*this, screen);
return IterationDecision::Continue;
});
@ -143,6 +152,9 @@ void Compositor::compose()
recompute_occlusions();
}
// We should have recomputed occlusions if any overlay rects were changed
VERIFY(!m_overlay_rects_changed);
auto dirty_screen_rects = move(m_dirty_screen_rects);
auto* dnd_client = wm.dnd_client();
if (!m_last_geometry_label_damage_rect.is_empty() || !m_last_dnd_rect.is_empty() || (m_invalidated_cursor && dnd_client)) {
@ -517,6 +529,11 @@ void Compositor::compose()
return is_overlapping;
}());
if (!m_overlay_list.is_empty()) {
// Render everything to the temporary buffer before we copy it back
render_overlays();
}
// Copy anything rendered to the temporary buffer to the back buffer
Screen::for_each([&](auto& screen) {
auto screen_rect = screen.rect();
@ -828,6 +845,7 @@ void Compositor::screen_resolution_changed()
init_bitmaps();
invalidate_occlusions();
overlay_rects_changed();
compose();
}
@ -914,6 +932,54 @@ void Compositor::change_cursor(const Cursor* cursor)
}
}
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) {
auto& screen_data = m_screen_data[screen->index()];
auto& painter = screen_data.overlay_painter();
screen_data.for_each_intersected_flushing_rect(overlay.current_render_rect(), [&](auto& intersected_overlay_rect) {
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;
});
}
}
}
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);
}
void Compositor::ScreenData::draw_cursor(Screen& screen, const Gfx::IntRect& cursor_rect)
{
auto& wm = WindowManager::the();
@ -944,6 +1010,11 @@ bool Compositor::ScreenData::restore_cursor_back(Screen& screen, Gfx::IntRect& l
return true;
}
void Compositor::update_fonts()
{
ScreenNumberOverlay::pick_font();
}
void Compositor::notify_display_links()
{
ClientConnection::for_each_client([](auto& client) {
@ -966,6 +1037,35 @@ void Compositor::decrement_display_link_count(Badge<ClientConnection>)
m_display_link_notify_timer->stop();
}
void Compositor::invalidate_current_screen_number_rects()
{
for (auto& screen_data : m_screen_data) {
if (screen_data.m_screen_number_overlay)
screen_data.m_screen_number_overlay->invalidate();
}
}
void Compositor::increment_show_screen_number(Badge<ClientConnection>)
{
if (m_show_screen_number_count++ == 0) {
Screen::for_each([&](auto& screen) {
auto& screen_data = m_screen_data[screen.index()];
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();
for (auto& screen_data : m_screen_data)
screen_data.m_screen_number_overlay = nullptr;
}
}
bool Compositor::any_opaque_window_above_this_one_contains_rect(const Window& a_window, const Gfx::IntRect& rect)
{
bool found_containing_window = false;
@ -992,6 +1092,50 @@ bool Compositor::any_opaque_window_above_this_one_contains_rect(const Window& a_
return found_containing_window;
};
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;
m_overlay_rects_changed = true;
m_invalidated_any = true;
invalidate_occlusions();
for (auto& rect : m_overlay_rects.rects())
invalidate_screen(rect);
}
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);
}
}
void Compositor::recompute_occlusions()
{
auto& wm = WindowManager::the();
@ -1007,7 +1151,16 @@ void Compositor::recompute_occlusions()
return IterationDecision::Continue;
});
dbgln_if(OCCLUSIONS_DEBUG, "OCCLUSIONS:");
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);
}
auto& main_screen = Screen::main();
if (auto* fullscreen_window = wm.active_fullscreen_window()) {
@ -1127,6 +1280,13 @@ void Compositor::recompute_occlusions()
return IterationDecision::Continue;
});
if (!m_overlay_rects.is_empty() && m_overlay_rects.intersects(visible_opaque)) {
// In order to render overlays flicker-free we need to force these area into the
// temporary transparency rendering buffer
transparency_rects.add(m_overlay_rects.intersected(visible_opaque));
visible_opaque = visible_opaque.shatter(m_overlay_rects);
}
bool have_opaque = !visible_opaque.is_empty();
if (!transparency_rects.is_empty())
have_transparent = true;

View file

@ -11,12 +11,14 @@
#include <LibCore/Object.h>
#include <LibGfx/Color.h>
#include <LibGfx/DisjointRectSet.h>
#include <WindowServer/Screen.h>
#include <LibGfx/Font.h>
#include <WindowServer/Overlays.h>
namespace WindowServer {
class ClientConnection;
class Cursor;
class MultiScaleBitmaps;
class Window;
class WindowManager;
@ -29,6 +31,8 @@ enum class WallpaperMode {
class Compositor final : public Core::Object {
C_OBJECT(Compositor)
friend class Overlay;
public:
static Compositor& the();
@ -54,7 +58,37 @@ public:
void increment_display_link_count(Badge<ClientConnection>);
void decrement_display_link_count(Badge<ClientConnection>);
void increment_show_screen_number(Badge<ClientConnection>);
void decrement_show_screen_number(Badge<ClientConnection>);
bool showing_screen_numbers() const { return m_show_screen_number_count > 0; }
void invalidate_after_theme_or_font_change()
{
update_fonts();
invalidate_occlusions();
overlays_theme_changed();
invalidate_screen();
}
void invalidate_occlusions() { m_occlusions_dirty = true; }
void overlay_rects_changed();
template<typename T, typename... Args>
OwnPtr<T> create_overlay(Args&&... args)
{
return adopt_own(*new T(forward<Args>(args)...));
}
template<typename F>
IterationDecision for_each_overlay(F f)
{
for (auto& overlay : m_overlay_list) {
IterationDecision decision = f(overlay);
if (decision != IterationDecision::Continue)
return decision;
}
return IterationDecision::Continue;
}
void did_construct_window_manager(Badge<WindowManager>);
@ -66,8 +100,16 @@ private:
void init_bitmaps();
bool render_animation_frame(Screen&, Gfx::DisjointRectSet&);
void step_animations();
void invalidate_current_screen_number_rects();
void overlays_theme_changed();
void render_overlays();
void add_overlay(Overlay&);
void remove_overlay(Overlay&);
void update_fonts();
void notify_display_links();
void start_compose_async_timer();
void recompute_overlay_rects();
void recompute_occlusions();
bool any_opaque_window_above_this_one_contains_rect(const Window&, const Gfx::IntRect&);
void change_cursor(const Cursor*);
@ -81,6 +123,7 @@ private:
bool m_invalidated_any { true };
bool m_invalidated_window { false };
bool m_invalidated_cursor { false };
bool m_overlay_rects_changed { false };
struct ScreenData {
RefPtr<Gfx::Bitmap> m_front_bitmap;
@ -92,6 +135,7 @@ private:
RefPtr<Gfx::Bitmap> m_cursor_back_bitmap;
OwnPtr<Gfx::Painter> m_cursor_back_painter;
Gfx::IntRect m_last_cursor_rect;
OwnPtr<ScreenNumberOverlay> m_screen_number_overlay;
bool m_buffers_are_flipped { false };
bool m_screen_can_set_buffer { false };
bool m_cursor_back_is_valid { false };
@ -100,14 +144,41 @@ private:
Gfx::DisjointRectSet m_flush_transparent_rects;
Gfx::DisjointRectSet m_flush_special_rects;
void init_bitmaps(Screen&);
Gfx::Painter& overlay_painter() { return *m_temp_painter; }
void init_bitmaps(Compositor&, Screen&);
void flip_buffers(Screen&);
void draw_cursor(Screen&, const Gfx::IntRect&);
bool restore_cursor_back(Screen&, Gfx::IntRect&);
template<typename F>
IterationDecision for_each_intersected_flushing_rect(Gfx::IntRect const& intersecting_rect, F f)
{
auto iterate_flush_rects = [&](Gfx::DisjointRectSet const& flush_rects) {
for (auto& rect : flush_rects.rects()) {
auto intersection = intersecting_rect.intersected(rect);
if (intersection.is_empty())
continue;
IterationDecision decision = f(intersection);
if (decision != IterationDecision::Continue)
return decision;
}
return IterationDecision::Continue;
};
auto decision = iterate_flush_rects(m_flush_rects);
if (decision != IterationDecision::Continue)
return decision;
// We do not have to iterate m_flush_special_rects here as these
// technically should be removed anyway. m_flush_rects and
// m_flush_transparent_rects should cover everything already
return iterate_flush_rects(m_flush_transparent_rects);
}
};
friend class ScreenData;
Vector<ScreenData, default_screen_count> m_screen_data;
IntrusiveList<Overlay, RawPtr<Overlay>, &Overlay::m_list_node> m_overlay_list;
Gfx::DisjointRectSet m_overlay_rects;
Gfx::DisjointRectSet m_dirty_screen_rects;
Gfx::DisjointRectSet m_opaque_wallpaper_rects;
@ -126,6 +197,7 @@ private:
RefPtr<Core::Timer> m_display_link_notify_timer;
size_t m_display_link_count { 0 };
size_t m_show_screen_number_count { 0 };
Optional<Gfx::Color> m_custom_background_color;
};

View file

@ -25,6 +25,17 @@ const Gfx::Bitmap& MultiScaleBitmaps::bitmap(int scale_factor) const
return it->value;
}
Gfx::Bitmap const* MultiScaleBitmaps::find_bitmap(int scale_factor) const
{
auto it = m_bitmaps.find(scale_factor);
return it != m_bitmaps.end() ? it->value.ptr() : nullptr;
}
RefPtr<MultiScaleBitmaps> MultiScaleBitmaps::create_empty()
{
return adopt_ref(*new MultiScaleBitmaps());
}
RefPtr<MultiScaleBitmaps> MultiScaleBitmaps::create(StringView const& filename, StringView const& default_filename)
{
auto per_scale_bitmap = adopt_ref(*new MultiScaleBitmaps());
@ -51,6 +62,7 @@ bool MultiScaleBitmaps::load(StringView const& filename, StringView const& defau
did_load_any = true;
m_bitmaps.set(scale_factor, bitmap.release_nonnull());
} else {
// Gracefully ignore, we have at least one bitmap already
dbgln("Bitmap {} (scale {}) has format inconsistent with the other per-scale bitmaps", path, bitmap->scale());
}
}
@ -69,4 +81,17 @@ bool MultiScaleBitmaps::load(StringView const& filename, StringView const& defau
return did_load_any;
}
void MultiScaleBitmaps::add_bitmap(int scale_factor, NonnullRefPtr<Gfx::Bitmap>&& bitmap)
{
auto bitmap_format = bitmap->format();
if (m_format == Gfx::BitmapFormat::Invalid || m_format == bitmap_format) {
if (m_format == Gfx::BitmapFormat::Invalid)
m_format = bitmap_format;
m_bitmaps.set(scale_factor, move(bitmap));
} else {
dbgln("MultiScaleBitmaps::add_bitmap (scale {}) has format inconsistent with the other per-scale bitmaps", bitmap->scale());
VERIFY_NOT_REACHED(); // The caller of this function should have made sure it is consistent!
}
}
}

View file

@ -15,12 +15,15 @@ namespace WindowServer {
class MultiScaleBitmaps : public RefCounted<MultiScaleBitmaps> {
public:
static RefPtr<MultiScaleBitmaps> create_empty();
static RefPtr<MultiScaleBitmaps> create(StringView const& filename, StringView const& default_filename = {});
Gfx::Bitmap const& default_bitmap() const { return bitmap(1); }
Gfx::Bitmap const& bitmap(int scale_factor) const;
Gfx::Bitmap const* find_bitmap(int scale_factor) const;
Gfx::BitmapFormat format() const { return m_format; }
bool load(StringView const& filename, StringView const& default_filename = {});
void add_bitmap(int scale_factor, NonnullRefPtr<Gfx::Bitmap>&&);
private:
MultiScaleBitmaps() = default;

View file

@ -0,0 +1,208 @@
/*
* 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;
m_rect = rect;
invalidate();
if (is_enabled())
Compositor::the().overlay_rects_changed();
rect_changed();
}
BitmapOverlay::BitmapOverlay()
{
clear_bitmaps();
}
void BitmapOverlay::rect_changed()
{
clear_bitmaps();
Overlay::rect_changed();
}
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()
{
clear_bitmaps();
}
void RectangularOverlay::clear_bitmaps()
{
m_rendered_bitmaps = MultiScaleBitmaps::create_empty();
}
void RectangularOverlay::render(Gfx::Painter& painter, Screen const& screen)
{
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));
}
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);
}
}

View file

@ -0,0 +1,127 @@
/*
* Copyright (c) 2021, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/IntrusiveList.h>
#include <AK/Vector.h>
#include <LibGfx/Painter.h>
#include <WindowServer/MultiScaleBitmaps.h>
#include <WindowServer/Screen.h>
namespace WindowServer {
class Screen;
class Overlay {
friend class Compositor;
public:
virtual ~Overlay();
enum class ZOrder {
WindowGeometry,
ScreenNumber,
};
[[nodiscard]] virtual ZOrder zorder() const = 0;
virtual void render(Gfx::Painter&, Screen const&) = 0;
Gfx::IntRect const& rect() const { return m_rect; }
Gfx::IntRect const& current_render_rect() const { return m_current_rect; }
void set_enabled(bool);
bool is_enabled() const { return m_list_node.is_in_list(); }
virtual void theme_changed()
{
rect_changed();
}
bool invalidate();
protected:
Overlay() = default;
void set_rect(Gfx::IntRect const&);
virtual void rect_changed() {};
private:
void clear_invalidated() { m_invalidated = false; }
void did_recompute_occlusions()
{
m_invalidated = false;
m_current_rect = m_rect;
}
Gfx::IntRect m_rect;
Gfx::IntRect m_current_rect;
Vector<Screen*, default_screen_count> m_screens;
IntrusiveListNode<Overlay> m_list_node;
bool m_invalidated { false };
};
class BitmapOverlay : public Overlay {
public:
virtual RefPtr<Gfx::Bitmap> create_bitmap(int) = 0;
virtual void render(Gfx::Painter&, Screen const&) override;
protected:
BitmapOverlay();
void clear_bitmaps();
virtual void rect_changed() override;
private:
RefPtr<MultiScaleBitmaps> m_bitmaps;
};
class RectangularOverlay : public Overlay {
public:
static constexpr int default_minimum_size = 10;
static constexpr int default_frame_thickness = 5;
virtual void render(Gfx::Painter&, Screen const&) override;
virtual void render_overlay_bitmap(Gfx::Painter&) = 0;
protected:
RectangularOverlay();
static Gfx::IntRect calculate_frame_rect(Gfx::IntRect const&);
void set_content_rect(Gfx::IntRect const&);
void clear_bitmaps();
virtual void rect_changed() override;
private:
RefPtr<MultiScaleBitmaps> m_rendered_bitmaps;
};
class ScreenNumberOverlay : public RectangularOverlay {
public:
static constexpr int default_offset = 20;
static constexpr int default_size = 120;
static constexpr int screen_number_padding = 10;
ScreenNumberOverlay(Screen&);
static Gfx::IntRect calculate_content_rect_for_screen(Screen&);
virtual ZOrder zorder() const override { return ZOrder::ScreenNumber; }
virtual void render_overlay_bitmap(Gfx::Painter&) override;
static void pick_font();
private:
Gfx::Font const& font();
Screen& m_screen;
static Gfx::Font const* s_font;
};
}

View file

@ -85,6 +85,18 @@ void WindowManager::reload_config()
reload_cursor(m_wait_cursor, "Wait");
reload_cursor(m_crosshair_cursor, "Crosshair");
auto reload_graphic = [&](RefPtr<MultiScaleBitmaps>& bitmap, String const& name) {
if (bitmap) {
if (!bitmap->load(name))
bitmap = nullptr;
} else {
bitmap = MultiScaleBitmaps::create(name);
}
};
reload_graphic(m_overlay_rect_shadow, m_config->read_entry("Graphics", "OverlayRectShadow"));
Compositor::the().invalidate_after_theme_or_font_change();
WindowFrame::reload_config();
}
@ -1531,8 +1543,7 @@ void WindowManager::invalidate_after_theme_or_font_change()
});
MenuManager::the().did_change_theme();
AppletManager::the().did_change_theme();
Compositor::the().invalidate_occlusions();
Compositor::the().invalidate_screen();
Compositor::the().invalidate_after_theme_or_font_change();
}
bool WindowManager::update_theme(String theme_path, String theme_name)

View file

@ -231,6 +231,8 @@ public:
WindowStack& window_stack() { return m_window_stack; }
MultiScaleBitmaps const* overlay_rect_shadow() const { return m_overlay_rect_shadow.ptr(); }
private:
RefPtr<Cursor> get_cursor(String const& name);
@ -273,6 +275,8 @@ private:
RefPtr<Cursor> m_wait_cursor;
RefPtr<Cursor> m_crosshair_cursor;
RefPtr<MultiScaleBitmaps> m_overlay_rect_shadow;
WindowStack m_window_stack;
struct DoubleClickInfo {

View file

@ -96,6 +96,7 @@ endpoint WindowServer
set_screen_layout(::WindowServer::ScreenLayout screen_layout, bool save) => (bool success, String error_msg)
get_screen_layout() => (::WindowServer::ScreenLayout screen_layout)
save_screen_layout() => (bool success, String error_msg)
show_screen_numbers(bool show) =|
set_window_icon_bitmap(i32 window_id, Gfx::ShareableBitmap icon) =|