mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-15 16:48:24 +03:00
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:
parent
42cb38b71a
commit
41859ad3fe
Notes:
sideshowbarker
2024-07-18 11:31:06 +09:00
Author: https://github.com/tomuta Commit: https://github.com/SerenityOS/serenity/commit/41859ad3fed Pull-request: https://github.com/SerenityOS/serenity/pull/8174 Reviewed-by: https://github.com/MaxWipfli Reviewed-by: https://github.com/alimpfard Reviewed-by: https://github.com/gunnarbeutner
@ -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
|
||||
|
||||
|
BIN
Base/res/graphics/overlay-rect-shadow.png
Normal file
BIN
Base/res/graphics/overlay-rect-shadow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
@ -22,6 +22,7 @@ set(SOURCES
|
||||
MenuItem.cpp
|
||||
MenuManager.cpp
|
||||
MultiScaleBitmaps.cpp
|
||||
Overlays.cpp
|
||||
Screen.cpp
|
||||
ScreenLayout.cpp
|
||||
Window.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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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!
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
208
Userland/Services/WindowServer/Overlays.cpp
Normal file
208
Userland/Services/WindowServer/Overlays.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
127
Userland/Services/WindowServer/Overlays.h
Normal file
127
Userland/Services/WindowServer/Overlays.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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) =|
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user