From 41859ad3feda56463ce0f73b898714ae17670ca6 Mon Sep 17 00:00:00 2001 From: Tom Date: Sun, 20 Jun 2021 13:23:43 -0600 Subject: [PATCH] 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. --- Base/etc/WindowServer.ini | 3 + Base/res/graphics/overlay-rect-shadow.png | Bin 0 -> 1178 bytes Userland/Services/WindowServer/CMakeLists.txt | 1 + .../WindowServer/ClientConnection.cpp | 14 ++ .../Services/WindowServer/ClientConnection.h | 2 + Userland/Services/WindowServer/Compositor.cpp | 166 +++++++++++++- Userland/Services/WindowServer/Compositor.h | 76 ++++++- .../WindowServer/MultiScaleBitmaps.cpp | 25 +++ .../Services/WindowServer/MultiScaleBitmaps.h | 3 + Userland/Services/WindowServer/Overlays.cpp | 208 ++++++++++++++++++ Userland/Services/WindowServer/Overlays.h | 127 +++++++++++ .../Services/WindowServer/WindowManager.cpp | 15 +- .../Services/WindowServer/WindowManager.h | 4 + .../Services/WindowServer/WindowServer.ipc | 1 + 14 files changed, 638 insertions(+), 7 deletions(-) create mode 100644 Base/res/graphics/overlay-rect-shadow.png create mode 100644 Userland/Services/WindowServer/Overlays.cpp create mode 100644 Userland/Services/WindowServer/Overlays.h diff --git a/Base/etc/WindowServer.ini b/Base/etc/WindowServer.ini index 177edeef48..62843c13dc 100644 --- a/Base/etc/WindowServer.ini +++ b/Base/etc/WindowServer.ini @@ -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 diff --git a/Base/res/graphics/overlay-rect-shadow.png b/Base/res/graphics/overlay-rect-shadow.png new file mode 100644 index 0000000000000000000000000000000000000000..72257746c995f05a83659b2be42d18264d706a96 GIT binary patch literal 1178 zcmeAS@N?(olHy`uVBq!ia0y~yVCZ0AU{K;(^~& zU|{k0ba4!+xb=3<`Mk$2GRN(Wx0(BH4zb+OuH)3#QqiFCY9e#c{RQkQa-FyL9r2PW zdU$B55P!fb8HYy<+Nw?$4i`m~n)?PHm*eX>ZD4&){+efwzq`~;U)l6$)BE@Dw>Mt9 zV2|30`e5q~?F>ua30`2cW3pqjW3-d*KOTSn`R98-fBu~RbN2f6>;2bOdny>bD`c`^ zwqv#1ziZd8tc?*i+P~!%9yiFb(hpr!>ysh3&tU14HLDz-D9cpOUSIHMj&o4t>^6Ce zgUifQ&xTIr=`yHV%P7cdbkil`id4X4qhrzHx1D zagK5iT%b|fexya&xNi!N=hL@hc{7(i&74#z!dqZ|YN3qe1S$80=Qqnb1X(-86tMjI z&idywsCK{X8A9_e0uZd&GI}I)7<7H zU324GsFZbmzNDDV%Wq7VnH36U=9gK`)mNK*a`!U#4}a_aXUZIBNM5T|Zl&kBB{zrl zrn<^0#$Rzs0-K-DJT>{wQ5DZi^L*6apAp@^Y}>_*FMDpkeYSd@`GM%eYTnw+IM**E0CYSTyr`2n(eEGk@Wle$a)~Gd` z|3>eVE1WOhw@72w_uqe)FTVIyPfT;fq`sFrl>I%DSCN z0-IyzJipfY@%JCj>hAfc|E^QGI{Rsz^dy-HOH=n$|G8r@#p=r8-Mr4O6K1gae8@kY z_)J=6HAA6el9b=4e@RpMXHT2zU$BNbCDdoq!|U^H0|YHXt}<*?+!&lFwM5SK-Le;3 zo%{SlIR$hSLPSs4Em$S9#L|pqr50lnpNEMIm)0+a9jAU-%THgka$`+%bCW~m^JE@x slUtANC)`}yZFu>uFVnN2XP?Zs-_w0Fou!F|fq{X+)78&qol`;+07^F&vj6}9 literal 0 HcmV?d00001 diff --git a/Userland/Services/WindowServer/CMakeLists.txt b/Userland/Services/WindowServer/CMakeLists.txt index 4d9092ea49..3dd7c0be51 100644 --- a/Userland/Services/WindowServer/CMakeLists.txt +++ b/Userland/Services/WindowServer/CMakeLists.txt @@ -22,6 +22,7 @@ set(SOURCES MenuItem.cpp MenuManager.cpp MultiScaleBitmaps.cpp + Overlays.cpp Screen.cpp ScreenLayout.cpp Window.cpp diff --git a/Userland/Services/WindowServer/ClientConnection.cpp b/Userland/Services/WindowServer/ClientConnection.cpp index b6a267d3b0..135914a251 100644 --- a/Userland/Services/WindowServer/ClientConnection.cpp +++ b/Userland/Services/WindowServer/ClientConnection.cpp @@ -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); diff --git a/Userland/Services/WindowServer/ClientConnection.h b/Userland/Services/WindowServer/ClientConnection.h index 944b490c9f..40df34b717 100644 --- a/Userland/Services/WindowServer/ClientConnection.h +++ b/Userland/Services/WindowServer/ClientConnection.h @@ -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 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 diff --git a/Userland/Services/WindowServer/Compositor.cpp b/Userland/Services/WindowServer/Compositor.cpp index 54141bc686..191166fffa 100644 --- a/Userland/Services/WindowServer/Compositor.cpp +++ b/Userland/Services/WindowServer/Compositor.cpp @@ -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(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) 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) +{ + 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(screen); + screen_data.m_screen_number_overlay->set_enabled(true); + return IterationDecision::Continue; + }); + } +} +void Compositor::decrement_show_screen_number(Badge) +{ + 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; diff --git a/Userland/Services/WindowServer/Compositor.h b/Userland/Services/WindowServer/Compositor.h index 357aa56944..6e91afd11e 100644 --- a/Userland/Services/WindowServer/Compositor.h +++ b/Userland/Services/WindowServer/Compositor.h @@ -11,12 +11,14 @@ #include #include #include -#include +#include +#include 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); void decrement_display_link_count(Badge); + void increment_show_screen_number(Badge); + void decrement_show_screen_number(Badge); + 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 + OwnPtr create_overlay(Args&&... args) + { + return adopt_own(*new T(forward(args)...)); + } + + template + 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); @@ -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 m_front_bitmap; @@ -92,6 +135,7 @@ private: RefPtr m_cursor_back_bitmap; OwnPtr m_cursor_back_painter; Gfx::IntRect m_last_cursor_rect; + OwnPtr 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 + 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 m_screen_data; + IntrusiveList, &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 m_display_link_notify_timer; size_t m_display_link_count { 0 }; + size_t m_show_screen_number_count { 0 }; Optional m_custom_background_color; }; diff --git a/Userland/Services/WindowServer/MultiScaleBitmaps.cpp b/Userland/Services/WindowServer/MultiScaleBitmaps.cpp index 8ad1a2184e..f5c49f20f6 100644 --- a/Userland/Services/WindowServer/MultiScaleBitmaps.cpp +++ b/Userland/Services/WindowServer/MultiScaleBitmaps.cpp @@ -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::create_empty() +{ + return adopt_ref(*new MultiScaleBitmaps()); +} + RefPtr 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&& 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! + } +} + } diff --git a/Userland/Services/WindowServer/MultiScaleBitmaps.h b/Userland/Services/WindowServer/MultiScaleBitmaps.h index 830ef97c58..ee0593c44d 100644 --- a/Userland/Services/WindowServer/MultiScaleBitmaps.h +++ b/Userland/Services/WindowServer/MultiScaleBitmaps.h @@ -15,12 +15,15 @@ namespace WindowServer { class MultiScaleBitmaps : public RefCounted { public: + static RefPtr create_empty(); static RefPtr 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&&); private: MultiScaleBitmaps() = default; diff --git a/Userland/Services/WindowServer/Overlays.cpp b/Userland/Services/WindowServer/Overlays.cpp new file mode 100644 index 0000000000..b751784b7c --- /dev/null +++ b/Userland/Services/WindowServer/Overlays.cpp @@ -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); +} + +} diff --git a/Userland/Services/WindowServer/Overlays.h b/Userland/Services/WindowServer/Overlays.h new file mode 100644 index 0000000000..8514b1e6b5 --- /dev/null +++ b/Userland/Services/WindowServer/Overlays.h @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2021, the SerenityOS developers. + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +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 m_screens; + IntrusiveListNode m_list_node; + bool m_invalidated { false }; +}; + +class BitmapOverlay : public Overlay { +public: + virtual RefPtr create_bitmap(int) = 0; + + virtual void render(Gfx::Painter&, Screen const&) override; + +protected: + BitmapOverlay(); + + void clear_bitmaps(); + virtual void rect_changed() override; + +private: + RefPtr 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 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; +}; + +} diff --git a/Userland/Services/WindowServer/WindowManager.cpp b/Userland/Services/WindowServer/WindowManager.cpp index 1df6316a58..762899285c 100644 --- a/Userland/Services/WindowServer/WindowManager.cpp +++ b/Userland/Services/WindowServer/WindowManager.cpp @@ -85,6 +85,18 @@ void WindowManager::reload_config() reload_cursor(m_wait_cursor, "Wait"); reload_cursor(m_crosshair_cursor, "Crosshair"); + auto reload_graphic = [&](RefPtr& 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) diff --git a/Userland/Services/WindowServer/WindowManager.h b/Userland/Services/WindowServer/WindowManager.h index 66a3246365..cb20c8ff33 100644 --- a/Userland/Services/WindowServer/WindowManager.h +++ b/Userland/Services/WindowServer/WindowManager.h @@ -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 get_cursor(String const& name); @@ -273,6 +275,8 @@ private: RefPtr m_wait_cursor; RefPtr m_crosshair_cursor; + RefPtr m_overlay_rect_shadow; + WindowStack m_window_stack; struct DoubleClickInfo { diff --git a/Userland/Services/WindowServer/WindowServer.ipc b/Userland/Services/WindowServer/WindowServer.ipc index 8559b1464b..6f3efe54c0 100644 --- a/Userland/Services/WindowServer/WindowServer.ipc +++ b/Userland/Services/WindowServer/WindowServer.ipc @@ -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) =|