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 0000000000..72257746c9 Binary files /dev/null and b/Base/res/graphics/overlay-rect-shadow.png differ 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) =|