1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-26 04:27:44 +00:00

WindowServer: Load multiple scaled versions of Bitmaps and Cursors

This enables rendering of mixed-scale screen layouts with e.g. high
resolution cursors and window button icons on high-dpi screens while
using lower resolution bitmaps on regular screens.
This commit is contained in:
Tom 2021-06-18 19:21:30 -06:00 committed by Andreas Kling
parent aa15bf81e4
commit 61af9d882e
15 changed files with 269 additions and 99 deletions

View file

@ -9,6 +9,7 @@
#include <LibGfx/StylePainter.h> #include <LibGfx/StylePainter.h>
#include <WindowServer/Button.h> #include <WindowServer/Button.h>
#include <WindowServer/Event.h> #include <WindowServer/Event.h>
#include <WindowServer/Screen.h>
#include <WindowServer/WindowManager.h> #include <WindowServer/WindowManager.h>
namespace WindowServer { namespace WindowServer {
@ -23,7 +24,7 @@ Button::~Button()
{ {
} }
void Button::paint(Gfx::Painter& painter) void Button::paint(Screen& screen, Gfx::Painter& painter)
{ {
auto palette = WindowManager::the().palette(); auto palette = WindowManager::the().palette();
Gfx::PainterStateSaver saver(painter); Gfx::PainterStateSaver saver(painter);
@ -31,10 +32,11 @@ void Button::paint(Gfx::Painter& painter)
Gfx::StylePainter::paint_button(painter, rect(), palette, Gfx::ButtonStyle::Normal, m_pressed, m_hovered); Gfx::StylePainter::paint_button(painter, rect(), palette, Gfx::ButtonStyle::Normal, m_pressed, m_hovered);
if (m_icon) { if (m_icon) {
auto icon_location = rect().center().translated(-(m_icon->width() / 2), -(m_icon->height() / 2)); auto& bitmap = m_icon->bitmap(screen.scale_factor());
auto icon_location = rect().center().translated(-(bitmap.width() / 2), -(bitmap.height() / 2));
if (m_pressed) if (m_pressed)
painter.translate(1, 1); painter.translate(1, 1);
painter.blit(icon_location, *m_icon, m_icon->rect()); painter.blit(icon_location, bitmap, bitmap.rect());
} }
} }

View file

@ -11,10 +11,12 @@
#include <LibGfx/Bitmap.h> #include <LibGfx/Bitmap.h>
#include <LibGfx/Forward.h> #include <LibGfx/Forward.h>
#include <LibGfx/Rect.h> #include <LibGfx/Rect.h>
#include <WindowServer/MultiScaleBitmaps.h>
namespace WindowServer { namespace WindowServer {
class MouseEvent; class MouseEvent;
class Screen;
class WindowFrame; class WindowFrame;
class Button : public Weakable<Button> { class Button : public Weakable<Button> {
@ -28,7 +30,7 @@ public:
Gfx::IntRect rect() const { return { {}, m_relative_rect.size() }; } Gfx::IntRect rect() const { return { {}, m_relative_rect.size() }; }
Gfx::IntRect screen_rect() const; Gfx::IntRect screen_rect() const;
void paint(Gfx::Painter&); void paint(Screen&, Gfx::Painter&);
void on_mouse_event(const MouseEvent&); void on_mouse_event(const MouseEvent&);
@ -38,12 +40,12 @@ public:
bool is_visible() const { return m_visible; } bool is_visible() const { return m_visible; }
void set_icon(const Gfx::Bitmap& icon) { m_icon = icon; } void set_icon(const RefPtr<MultiScaleBitmaps>& icon) { m_icon = icon; }
private: private:
WindowFrame& m_frame; WindowFrame& m_frame;
Gfx::IntRect m_relative_rect; Gfx::IntRect m_relative_rect;
RefPtr<Gfx::Bitmap> m_icon; RefPtr<MultiScaleBitmaps> m_icon;
bool m_pressed { false }; bool m_pressed { false };
bool m_visible { true }; bool m_visible { true };
bool m_hovered { false }; bool m_hovered { false };

View file

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

View file

@ -682,7 +682,7 @@ void ClientConnection::set_window_custom_cursor(i32 window_id, Gfx::ShareableBit
return; return;
} }
window.set_cursor(Cursor::create(*cursor.bitmap())); window.set_cursor(Cursor::create(*cursor.bitmap(), 1));
Compositor::the().invalidate_cursor(); Compositor::the().invalidate_cursor();
} }

View file

@ -919,7 +919,7 @@ void Compositor::ScreenData::draw_cursor(Screen& screen, const Gfx::IntRect& cur
auto& current_cursor = compositor.m_current_cursor ? *compositor.m_current_cursor : wm.active_cursor(); auto& current_cursor = compositor.m_current_cursor ? *compositor.m_current_cursor : wm.active_cursor();
auto screen_rect = screen.rect(); auto screen_rect = screen.rect();
m_cursor_back_painter->blit({ 0, 0 }, *m_back_bitmap, current_cursor.rect().translated(cursor_rect.location()).intersected(screen_rect).translated(-screen_rect.location())); m_cursor_back_painter->blit({ 0, 0 }, *m_back_bitmap, current_cursor.rect().translated(cursor_rect.location()).intersected(screen_rect).translated(-screen_rect.location()));
m_back_painter->blit(cursor_rect.location(), current_cursor.bitmap(), current_cursor.source_rect(compositor.m_current_cursor_frame)); m_back_painter->blit(cursor_rect.location(), current_cursor.bitmap(screen.scale_factor()), current_cursor.source_rect(compositor.m_current_cursor_frame));
m_last_cursor_rect = cursor_rect; m_last_cursor_rect = cursor_rect;
VERIFY(compositor.m_current_cursor_screen == &screen); VERIFY(compositor.m_current_cursor_screen == &screen);
m_cursor_back_is_valid = true; m_cursor_back_is_valid = true;

View file

@ -6,6 +6,7 @@
#include <AK/LexicalPath.h> #include <AK/LexicalPath.h>
#include <WindowServer/Cursor.h> #include <WindowServer/Cursor.h>
#include <WindowServer/Screen.h>
#include <WindowServer/WindowManager.h> #include <WindowServer/WindowManager.h>
namespace WindowServer { namespace WindowServer {
@ -77,6 +78,7 @@ CursorParams CursorParams::parse_from_filename(const StringView& cursor_path, co
return { default_hotspot }; return { default_hotspot };
} }
} }
return params; return params;
} }
@ -93,37 +95,66 @@ CursorParams CursorParams::constrained(const Gfx::Bitmap& bitmap) const
} }
} }
if (params.m_have_hotspot) if (params.m_have_hotspot)
params.m_hotspot = params.m_hotspot.constrained(rect); params.m_hotspot.constrain(rect);
else else
params.m_hotspot = rect.center(); params.m_hotspot = rect.center();
return params; return params;
} }
Cursor::Cursor(NonnullRefPtr<Gfx::Bitmap>&& bitmap, const CursorParams& cursor_params) Cursor::Cursor(NonnullRefPtr<Gfx::Bitmap>&& bitmap, int scale_factor, const CursorParams& cursor_params)
: m_bitmap(move(bitmap)) : m_params(cursor_params.constrained(*bitmap))
, m_params(cursor_params.constrained(*m_bitmap)) , m_rect(bitmap->rect())
, m_rect(m_bitmap->rect())
{ {
m_bitmaps.set(scale_factor, move(bitmap));
if (m_params.frames() > 1) { if (m_params.frames() > 1) {
VERIFY(m_rect.width() % m_params.frames() == 0); VERIFY(m_rect.width() % m_params.frames() == 0);
m_rect.set_width(m_rect.width() / m_params.frames()); m_rect.set_width(m_rect.width() / m_params.frames());
} }
} }
Cursor::~Cursor() NonnullRefPtr<Cursor> Cursor::create(NonnullRefPtr<Gfx::Bitmap>&& bitmap, int scale_factor)
{
}
NonnullRefPtr<Cursor> Cursor::create(NonnullRefPtr<Gfx::Bitmap>&& bitmap)
{ {
auto hotspot = bitmap->rect().center(); auto hotspot = bitmap->rect().center();
return adopt_ref(*new Cursor(move(bitmap), CursorParams(hotspot))); return adopt_ref(*new Cursor(move(bitmap), scale_factor, CursorParams(hotspot)));
} }
NonnullRefPtr<Cursor> Cursor::create(NonnullRefPtr<Gfx::Bitmap>&& bitmap, const StringView& filename) RefPtr<Cursor> Cursor::create(const StringView& filename, const StringView& default_filename)
{ {
auto default_hotspot = bitmap->rect().center(); auto cursor = adopt_ref(*new Cursor());
return adopt_ref(*new Cursor(move(bitmap), CursorParams::parse_from_filename(filename, default_hotspot))); if (cursor->load(filename, default_filename))
return cursor;
return {};
}
bool Cursor::load(const StringView& filename, const StringView& default_filename)
{
bool did_load_any = false;
bool first = true;
auto load_bitmap = [&](const StringView& path, int scale_factor) {
auto bitmap = Gfx::Bitmap::load_from_file(path, scale_factor);
if (bitmap) {
did_load_any = true;
if (first) {
m_params = CursorParams::parse_from_filename(filename, bitmap->rect().center());
m_rect = bitmap->rect();
first = false;
}
m_bitmaps.set(scale_factor, bitmap.release_nonnull());
}
};
Screen::for_each_scale_factor_in_use([&](int scale_factor) {
load_bitmap(filename, scale_factor);
return IterationDecision::Continue;
});
if (!did_load_any) {
Screen::for_each_scale_factor_in_use([&](int scale_factor) {
load_bitmap(default_filename, scale_factor);
return IterationDecision::Continue;
});
}
return did_load_any;
} }
RefPtr<Cursor> Cursor::create(Gfx::StandardCursor standard_cursor) RefPtr<Cursor> Cursor::create(Gfx::StandardCursor standard_cursor)

View file

@ -6,12 +6,15 @@
#pragma once #pragma once
#include <AK/HashMap.h>
#include <LibGfx/Bitmap.h> #include <LibGfx/Bitmap.h>
#include <LibGfx/StandardCursor.h> #include <LibGfx/StandardCursor.h>
namespace WindowServer { namespace WindowServer {
class CursorParams { class CursorParams {
friend class Cursor;
public: public:
static CursorParams parse_from_filename(const StringView&, const Gfx::IntPoint&); static CursorParams parse_from_filename(const StringView&, const Gfx::IntPoint&);
CursorParams(const Gfx::IntPoint& hotspot) CursorParams(const Gfx::IntPoint& hotspot)
@ -34,13 +37,27 @@ private:
class Cursor : public RefCounted<Cursor> { class Cursor : public RefCounted<Cursor> {
public: public:
static NonnullRefPtr<Cursor> create(NonnullRefPtr<Gfx::Bitmap>&&, const StringView&); static RefPtr<Cursor> create(const StringView&, const StringView&);
static NonnullRefPtr<Cursor> create(NonnullRefPtr<Gfx::Bitmap>&&); static NonnullRefPtr<Cursor> create(NonnullRefPtr<Gfx::Bitmap>&&, int);
static RefPtr<Cursor> create(Gfx::StandardCursor); static RefPtr<Cursor> create(Gfx::StandardCursor);
~Cursor(); ~Cursor() = default;
const CursorParams& params() const { return m_params; } const CursorParams& params() const { return m_params; }
const Gfx::Bitmap& bitmap() const { return *m_bitmap; } const Gfx::Bitmap& bitmap(int scale_factor) const
{
auto it = m_bitmaps.find(scale_factor);
if (it == m_bitmaps.end()) {
it = m_bitmaps.find(1);
if (it == m_bitmaps.end())
it = m_bitmaps.begin();
}
// We better found something
if (it == m_bitmaps.end()) {
dbgln("Could not find any bitmap in this Cursor");
VERIFY_NOT_REACHED();
}
return it->value;
}
Gfx::IntRect source_rect(unsigned frame) const Gfx::IntRect source_rect(unsigned frame) const
{ {
@ -51,9 +68,12 @@ public:
Gfx::IntSize size() const { return m_rect.size(); } Gfx::IntSize size() const { return m_rect.size(); }
private: private:
Cursor(NonnullRefPtr<Gfx::Bitmap>&&, const CursorParams&); Cursor() { }
Cursor(NonnullRefPtr<Gfx::Bitmap>&&, int, const CursorParams&);
RefPtr<Gfx::Bitmap> m_bitmap; bool load(const StringView&, const StringView&);
HashMap<int, NonnullRefPtr<Gfx::Bitmap>> m_bitmaps;
CursorParams m_params; CursorParams m_params;
Gfx::IntRect m_rect; Gfx::IntRect m_rect;
}; };

View file

@ -0,0 +1,70 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include "MultiScaleBitmaps.h"
#include "Screen.h"
namespace WindowServer {
const Gfx::Bitmap& MultiScaleBitmaps::bitmap(int scale_factor) const
{
auto it = m_bitmaps.find(scale_factor);
if (it == m_bitmaps.end()) {
it = m_bitmaps.find(1);
if (it == m_bitmaps.end())
it = m_bitmaps.begin();
}
// We better found *something*
if (it == m_bitmaps.end()) {
dbgln("Could not find any bitmap in this MultiScaleBitmaps");
VERIFY_NOT_REACHED();
}
return it->value;
}
RefPtr<MultiScaleBitmaps> MultiScaleBitmaps::create(StringView const& filename, StringView const& default_filename)
{
auto per_scale_bitmap = adopt_ref(*new MultiScaleBitmaps());
if (per_scale_bitmap->load(filename, default_filename))
return per_scale_bitmap;
return {};
}
bool MultiScaleBitmaps::load(StringView const& filename, StringView const& default_filename)
{
Optional<Gfx::BitmapFormat> bitmap_format;
bool did_load_any = false;
auto add_bitmap = [&](StringView const& path, int scale_factor) {
auto bitmap = Gfx::Bitmap::load_from_file(path, scale_factor);
if (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;
did_load_any = true;
m_bitmaps.set(scale_factor, bitmap.release_nonnull());
} else {
dbgln("Bitmap {} (scale {}) has format inconsistent with the other per-scale bitmaps", path, bitmap->scale());
}
}
};
Screen::for_each_scale_factor_in_use([&](int scale_factor) {
add_bitmap(filename, scale_factor);
return IterationDecision::Continue;
});
if (!did_load_any && !default_filename.is_null() && !default_filename.is_empty()) {
Screen::for_each_scale_factor_in_use([&](int scale_factor) {
add_bitmap(default_filename, scale_factor);
return IterationDecision::Continue;
});
}
return did_load_any;
}
}

View file

@ -0,0 +1,32 @@
/*
* Copyright (c) 2020, the SerenityOS developers.
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/HashMap.h>
#include <AK/RefCounted.h>
#include <AK/RefPtr.h>
#include <LibGfx/Bitmap.h>
namespace WindowServer {
class MultiScaleBitmaps : public RefCounted<MultiScaleBitmaps> {
public:
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::BitmapFormat format() const { return m_format; }
private:
MultiScaleBitmaps() = default;
bool load(StringView const& filename, StringView const& default_filename);
HashMap<int, NonnullRefPtr<Gfx::Bitmap>> m_bitmaps;
Gfx::BitmapFormat m_format { Gfx::BitmapFormat::Invalid };
};
}

View file

@ -23,6 +23,7 @@ NonnullOwnPtrVector<Screen, default_screen_count> Screen::s_screens;
Screen* Screen::s_main_screen { nullptr }; Screen* Screen::s_main_screen { nullptr };
Gfx::IntRect Screen::s_bounding_screens_rect {}; Gfx::IntRect Screen::s_bounding_screens_rect {};
ScreenLayout Screen::s_layout; ScreenLayout Screen::s_layout;
Vector<int, default_scale_factors_in_use_count> Screen::s_scale_factors_in_use;
ScreenInput& ScreenInput::the() ScreenInput& ScreenInput::the()
{ {
@ -83,9 +84,25 @@ bool Screen::apply_layout(ScreenLayout&& screen_layout, String& error_msg)
rollback.disarm(); rollback.disarm();
update_bounding_rect(); update_bounding_rect();
update_scale_factors_in_use();
return true; return true;
} }
void Screen::update_scale_factors_in_use()
{
s_scale_factors_in_use.clear();
for_each([&](auto& screen) {
auto scale_factor = screen.scale_factor();
// The This doesn't have to be extremely efficient as this
// code is only run when we start up or the screen configuration
// changes. But using a vector allows for efficient iteration,
// which is the most common use case.
if (!s_scale_factors_in_use.contains_slow(scale_factor))
s_scale_factors_in_use.append(scale_factor);
return IterationDecision::Continue;
});
}
Screen::Screen(ScreenLayout::Screen& screen_info) Screen::Screen(ScreenLayout::Screen& screen_info)
: m_virtual_rect(screen_info.location, { screen_info.resolution.width() / screen_info.scale_factor, screen_info.resolution.height() / screen_info.scale_factor }) : m_virtual_rect(screen_info.location, { screen_info.resolution.width() / screen_info.scale_factor, screen_info.resolution.height() / screen_info.scale_factor })
, m_info(screen_info) , m_info(screen_info)

View file

@ -9,6 +9,7 @@
#include "ScreenLayout.h" #include "ScreenLayout.h"
#include <AK/NonnullOwnPtrVector.h> #include <AK/NonnullOwnPtrVector.h>
#include <Kernel/API/KeyCode.h> #include <Kernel/API/KeyCode.h>
#include <LibGfx/Bitmap.h>
#include <LibGfx/Color.h> #include <LibGfx/Color.h>
#include <LibGfx/Rect.h> #include <LibGfx/Rect.h>
#include <LibGfx/Size.h> #include <LibGfx/Size.h>
@ -23,6 +24,8 @@ constexpr unsigned scroll_step_size_min = 1;
// Most people will probably have 4 screens or less // Most people will probably have 4 screens or less
constexpr size_t default_screen_count = 4; constexpr size_t default_screen_count = 4;
// We currently only support 2 scale factors: 1x and 2x
constexpr size_t default_scale_factors_in_use_count = 2;
class Screen; class Screen;
@ -125,6 +128,17 @@ public:
return IterationDecision::Continue; return IterationDecision::Continue;
} }
template<typename F>
static IterationDecision for_each_scale_factor_in_use(F f)
{
for (auto& scale_factor : s_scale_factors_in_use) {
IterationDecision decision = f(scale_factor);
if (decision != IterationDecision::Continue)
return decision;
}
return IterationDecision::Continue;
}
void make_main_screen() { s_main_screen = this; } void make_main_screen() { s_main_screen = this; }
bool is_main_screen() const { return s_main_screen == this; } bool is_main_screen() const { return s_main_screen == this; }
@ -158,6 +172,7 @@ private:
s_screens[i].m_index = i; s_screens[i].m_index = i;
} }
static void update_bounding_rect(); static void update_bounding_rect();
static void update_scale_factors_in_use();
bool is_opened() const { return m_framebuffer_fd >= 0; } bool is_opened() const { return m_framebuffer_fd >= 0; }
@ -165,6 +180,7 @@ private:
static Screen* s_main_screen; static Screen* s_main_screen;
static Gfx::IntRect s_bounding_screens_rect; static Gfx::IntRect s_bounding_screens_rect;
static ScreenLayout s_layout; static ScreenLayout s_layout;
static Vector<int, default_scale_factors_in_use_count> s_scale_factors_in_use;
size_t m_index { 0 }; size_t m_index { 0 };
size_t m_size_in_bytes; size_t m_size_in_bytes;

View file

@ -13,6 +13,7 @@
#include <WindowServer/Button.h> #include <WindowServer/Button.h>
#include <WindowServer/Compositor.h> #include <WindowServer/Compositor.h>
#include <WindowServer/Event.h> #include <WindowServer/Event.h>
#include <WindowServer/MultiScaleBitmaps.h>
#include <WindowServer/Screen.h> #include <WindowServer/Screen.h>
#include <WindowServer/Window.h> #include <WindowServer/Window.h>
#include <WindowServer/WindowFrame.h> #include <WindowServer/WindowFrame.h>
@ -34,20 +35,19 @@ static Gfx::WindowTheme::WindowType to_theme_window_type(WindowType type)
} }
} }
static RefPtr<Gfx::Bitmap> s_minimize_icon; static RefPtr<MultiScaleBitmaps> s_minimize_icon;
static RefPtr<Gfx::Bitmap> s_maximize_icon; static RefPtr<MultiScaleBitmaps> s_maximize_icon;
static RefPtr<Gfx::Bitmap> s_restore_icon; static RefPtr<MultiScaleBitmaps> s_restore_icon;
static RefPtr<Gfx::Bitmap> s_close_icon; static RefPtr<MultiScaleBitmaps> s_close_icon;
static RefPtr<Gfx::Bitmap> s_close_modified_icon; static RefPtr<MultiScaleBitmaps> s_close_modified_icon;
static String s_last_title_button_icons_path; static String s_last_title_button_icons_path;
static int s_last_title_button_icons_scale;
static RefPtr<Gfx::Bitmap> s_active_window_shadow; static RefPtr<MultiScaleBitmaps> s_active_window_shadow;
static RefPtr<Gfx::Bitmap> s_inactive_window_shadow; static RefPtr<MultiScaleBitmaps> s_inactive_window_shadow;
static RefPtr<Gfx::Bitmap> s_menu_shadow; static RefPtr<MultiScaleBitmaps> s_menu_shadow;
static RefPtr<Gfx::Bitmap> s_taskbar_shadow; static RefPtr<MultiScaleBitmaps> s_taskbar_shadow;
static RefPtr<Gfx::Bitmap> s_tooltip_shadow; static RefPtr<MultiScaleBitmaps> s_tooltip_shadow;
static String s_last_active_window_shadow_path; static String s_last_active_window_shadow_path;
static String s_last_inactive_window_shadow_path; static String s_last_inactive_window_shadow_path;
static String s_last_menu_shadow_path; static String s_last_menu_shadow_path;
@ -117,7 +117,7 @@ void WindowFrame::set_button_icons()
m_close_button->set_icon(m_window.is_modified() ? *s_close_modified_icon : *s_close_icon); m_close_button->set_icon(m_window.is_modified() ? *s_close_modified_icon : *s_close_icon);
if (m_window.is_minimizable()) if (m_window.is_minimizable())
m_minimize_button->set_icon(*s_minimize_icon); m_minimize_button->set_icon(s_minimize_icon);
if (m_window.is_resizable()) if (m_window.is_resizable())
m_maximize_button->set_icon(m_window.is_maximized() ? *s_restore_icon : *s_maximize_icon); m_maximize_button->set_icon(m_window.is_maximized() ? *s_restore_icon : *s_maximize_icon);
} }
@ -125,54 +125,47 @@ void WindowFrame::set_button_icons()
void WindowFrame::reload_config() void WindowFrame::reload_config()
{ {
String icons_path = WindowManager::the().palette().title_button_icons_path(); String icons_path = WindowManager::the().palette().title_button_icons_path();
int icons_scale = WindowManager::the().compositor_icon_scale(); // TODO: We'll need to load icons for all scales in use!
StringBuilder full_path; StringBuilder full_path;
if (!s_minimize_icon || s_last_title_button_icons_path != icons_path || s_last_title_button_icons_scale != icons_scale) { if (!s_minimize_icon || s_last_title_button_icons_path != icons_path) {
full_path.append(icons_path); full_path.append(icons_path);
full_path.append("window-minimize.png"); full_path.append("window-minimize.png");
if (!(s_minimize_icon = Gfx::Bitmap::load_from_file(full_path.to_string(), icons_scale))) s_minimize_icon = MultiScaleBitmaps::create(full_path.to_string(), "/res/icons/16x16/downward-triangle.png");
s_minimize_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/downward-triangle.png", icons_scale);
full_path.clear(); full_path.clear();
} }
if (!s_maximize_icon || s_last_title_button_icons_path != icons_path || s_last_title_button_icons_scale != icons_scale) { if (!s_maximize_icon || s_last_title_button_icons_path != icons_path) {
full_path.append(icons_path); full_path.append(icons_path);
full_path.append("window-maximize.png"); full_path.append("window-maximize.png");
if (!(s_maximize_icon = Gfx::Bitmap::load_from_file(full_path.to_string(), icons_scale))) s_maximize_icon = MultiScaleBitmaps::create(full_path.to_string(), "/res/icons/16x16/upward-triangle.png");
s_maximize_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/upward-triangle.png", icons_scale);
full_path.clear(); full_path.clear();
} }
if (!s_restore_icon || s_last_title_button_icons_path != icons_path || s_last_title_button_icons_scale != icons_scale) { if (!s_restore_icon || s_last_title_button_icons_path != icons_path) {
full_path.append(icons_path); full_path.append(icons_path);
full_path.append("window-restore.png"); full_path.append("window-restore.png");
if (!(s_restore_icon = Gfx::Bitmap::load_from_file(full_path.to_string(), icons_scale))) s_restore_icon = MultiScaleBitmaps::create(full_path.to_string(), "/res/icons/16x16/window-restore.png");
s_restore_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-restore.png", icons_scale);
full_path.clear(); full_path.clear();
} }
if (!s_close_icon || s_last_title_button_icons_path != icons_path || s_last_title_button_icons_scale != icons_scale) { if (!s_close_icon || s_last_title_button_icons_path != icons_path) {
full_path.append(icons_path); full_path.append(icons_path);
full_path.append("window-close.png"); full_path.append("window-close.png");
if (!(s_close_icon = Gfx::Bitmap::load_from_file(full_path.to_string(), icons_scale))) s_close_icon = MultiScaleBitmaps::create(full_path.to_string(), "/res/icons/16x16/window-close.png");
s_close_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-close.png", icons_scale);
full_path.clear(); full_path.clear();
} }
if (!s_close_modified_icon || s_last_title_button_icons_path != icons_path || s_last_title_button_icons_scale != icons_scale) { if (!s_close_modified_icon || s_last_title_button_icons_path != icons_path) {
full_path.append(icons_path); full_path.append(icons_path);
full_path.append("window-close-modified.png"); full_path.append("window-close-modified.png");
if (!(s_close_modified_icon = Gfx::Bitmap::load_from_file(full_path.to_string(), icons_scale))) s_close_modified_icon = MultiScaleBitmaps::create(full_path.to_string(), "/res/icons/16x16/window-close-modified.png");
s_close_modified_icon = Gfx::Bitmap::load_from_file("/res/icons/16x16/window-close-modified.png", icons_scale);
full_path.clear(); full_path.clear();
} }
s_last_title_button_icons_path = icons_path; s_last_title_button_icons_path = icons_path;
s_last_title_button_icons_scale = icons_scale;
auto load_shadow = [](const String& path, String& last_path, RefPtr<Gfx::Bitmap>& shadow_bitmap) { auto load_shadow = [](const String& path, String& last_path, RefPtr<MultiScaleBitmaps>& shadow_bitmap) {
if (path.is_empty()) { if (path.is_empty()) {
last_path = String::empty(); last_path = String::empty();
shadow_bitmap = nullptr; shadow_bitmap = nullptr;
} else if (!shadow_bitmap || shadow_bitmap->scale() != s_last_title_button_icons_scale || last_path != path) { } else if (!shadow_bitmap || last_path != path) {
shadow_bitmap = Gfx::Bitmap::load_from_file(path, s_last_title_button_icons_scale); shadow_bitmap = MultiScaleBitmaps::create(path);
if (shadow_bitmap) if (shadow_bitmap)
last_path = path; last_path = path;
else else
@ -186,7 +179,7 @@ void WindowFrame::reload_config()
load_shadow(WindowManager::the().palette().tooltip_shadow_path(), s_last_tooltip_shadow_path, s_tooltip_shadow); load_shadow(WindowManager::the().palette().tooltip_shadow_path(), s_last_tooltip_shadow_path, s_tooltip_shadow);
} }
Gfx::Bitmap* WindowFrame::shadow_bitmap() const MultiScaleBitmaps* WindowFrame::shadow_bitmap() const
{ {
if (m_window.is_frameless()) if (m_window.is_frameless())
return nullptr; return nullptr;
@ -317,7 +310,7 @@ void WindowFrame::paint(Screen& screen, Gfx::Painter& painter, const Gfx::IntRec
cached->paint(*this, painter, rect); cached->paint(*this, painter, rect);
} }
void WindowFrame::RenderedCache::paint(WindowFrame& frame, Gfx::Painter& painter, const Gfx::IntRect& rect) void WindowFrame::PerScaleRenderedCache::paint(WindowFrame& frame, Gfx::Painter& painter, const Gfx::IntRect& rect)
{ {
auto frame_rect = frame.unconstrained_render_rect(); auto frame_rect = frame.unconstrained_render_rect();
auto window_rect = frame.window().rect(); auto window_rect = frame.window().rect();
@ -357,7 +350,7 @@ void WindowFrame::RenderedCache::paint(WindowFrame& frame, Gfx::Painter& painter
} }
} }
void WindowFrame::render(Screen&, Gfx::Painter& painter) void WindowFrame::render(Screen& screen, Gfx::Painter& painter)
{ {
if (m_window.is_frameless()) if (m_window.is_frameless())
return; return;
@ -371,9 +364,8 @@ void WindowFrame::render(Screen&, Gfx::Painter& painter)
else else
return; return;
for (auto& button : m_buttons) { for (auto& button : m_buttons)
button.paint(painter); button.paint(screen, painter);
}
} }
void WindowFrame::theme_changed() void WindowFrame::theme_changed()
@ -386,13 +378,13 @@ void WindowFrame::theme_changed()
m_has_alpha_channel = Gfx::WindowTheme::current().frame_uses_alpha(window_state_for_theme(), WindowManager::the().palette()); m_has_alpha_channel = Gfx::WindowTheme::current().frame_uses_alpha(window_state_for_theme(), WindowManager::the().palette());
} }
auto WindowFrame::render_to_cache(Screen& screen) -> RenderedCache* auto WindowFrame::render_to_cache(Screen& screen) -> PerScaleRenderedCache*
{ {
auto scale = screen.scale_factor(); auto scale = screen.scale_factor();
RenderedCache* rendered_cache; PerScaleRenderedCache* rendered_cache;
auto cached_it = m_rendered_cache.find(scale); auto cached_it = m_rendered_cache.find(scale);
if (cached_it == m_rendered_cache.end()) { if (cached_it == m_rendered_cache.end()) {
auto new_rendered_cache = make<RenderedCache>(); auto new_rendered_cache = make<PerScaleRenderedCache>();
rendered_cache = new_rendered_cache.ptr(); rendered_cache = new_rendered_cache.ptr();
m_rendered_cache.set(scale, move(new_rendered_cache)); m_rendered_cache.set(scale, move(new_rendered_cache));
} else { } else {
@ -402,7 +394,7 @@ auto WindowFrame::render_to_cache(Screen& screen) -> RenderedCache*
return rendered_cache; return rendered_cache;
} }
void WindowFrame::RenderedCache::render(WindowFrame& frame, Screen& screen) void WindowFrame::PerScaleRenderedCache::render(WindowFrame& frame, Screen& screen)
{ {
if (!m_dirty) if (!m_dirty)
return; return;
@ -417,7 +409,7 @@ void WindowFrame::RenderedCache::render(WindowFrame& frame, Screen& screen)
Gfx::IntPoint shadow_offset; Gfx::IntPoint shadow_offset;
if (shadow_bitmap) { if (shadow_bitmap) {
auto total_shadow_size = shadow_bitmap->height(); auto total_shadow_size = shadow_bitmap->bitmap(screen.scale_factor()).height();
frame_rect_including_shadow.inflate(total_shadow_size, total_shadow_size); frame_rect_including_shadow.inflate(total_shadow_size, total_shadow_size);
auto offset = total_shadow_size / 2; auto offset = total_shadow_size / 2;
shadow_offset = { offset, offset }; shadow_offset = { offset, offset };
@ -481,7 +473,7 @@ void WindowFrame::RenderedCache::render(WindowFrame& frame, Screen& screen)
painter.clear_rect({ rect.location() - frame_rect_to_update.location(), rect.size() }, { 255, 255, 255, 0 }); painter.clear_rect({ rect.location() - frame_rect_to_update.location(), rect.size() }, { 255, 255, 255, 0 });
if (m_shadow_dirty && shadow_bitmap) if (m_shadow_dirty && shadow_bitmap)
frame.paint_simple_rect_shadow(painter, { { 0, 0 }, frame_rect_including_shadow.size() }, *shadow_bitmap); frame.paint_simple_rect_shadow(painter, { { 0, 0 }, frame_rect_including_shadow.size() }, shadow_bitmap->bitmap(screen.scale_factor()));
{ {
Gfx::PainterStateSaver save(painter); Gfx::PainterStateSaver save(painter);
@ -535,7 +527,7 @@ void WindowFrame::set_opacity(float opacity)
Gfx::IntRect WindowFrame::inflated_for_shadow(const Gfx::IntRect& frame_rect) const Gfx::IntRect WindowFrame::inflated_for_shadow(const Gfx::IntRect& frame_rect) const
{ {
if (auto* shadow = shadow_bitmap()) { if (auto* shadow = shadow_bitmap()) {
auto total_shadow_size = shadow->height(); auto total_shadow_size = shadow->default_bitmap().height();
return frame_rect.inflated(total_shadow_size, total_shadow_size); return frame_rect.inflated(total_shadow_size, total_shadow_size);
} }
return frame_rect; return frame_rect;
@ -678,7 +670,7 @@ Optional<HitTestResult> WindowFrame::hit_test(Gfx::IntPoint const& position)
return cached->hit_test(*this, position, window_relative_position); return cached->hit_test(*this, position, window_relative_position);
} }
Optional<HitTestResult> WindowFrame::RenderedCache::hit_test(WindowFrame& frame, Gfx::IntPoint const& position, Gfx::IntPoint const& window_relative_position) Optional<HitTestResult> WindowFrame::PerScaleRenderedCache::hit_test(WindowFrame& frame, Gfx::IntPoint const& position, Gfx::IntPoint const& window_relative_position)
{ {
HitTestResult result { HitTestResult result {
.window = frame.window(), .window = frame.window(),

View file

@ -19,12 +19,13 @@ namespace WindowServer {
class Button; class Button;
class Menu; class Menu;
class MouseEvent; class MouseEvent;
class Window; class MultiScaleBitmaps;
class Screen; class Screen;
class Window;
class WindowFrame { class WindowFrame {
public: public:
class RenderedCache { class PerScaleRenderedCache {
friend class WindowFrame; friend class WindowFrame;
public: public:
@ -60,7 +61,7 @@ public:
void paint(Screen&, Gfx::Painter&, const Gfx::IntRect&); void paint(Screen&, Gfx::Painter&, const Gfx::IntRect&);
void render(Screen&, Gfx::Painter&); void render(Screen&, Gfx::Painter&);
RenderedCache* render_to_cache(Screen&); PerScaleRenderedCache* render_to_cache(Screen&);
void handle_mouse_event(MouseEvent const&); void handle_mouse_event(MouseEvent const&);
void handle_titlebar_mouse_event(MouseEvent const&); void handle_titlebar_mouse_event(MouseEvent const&);
@ -123,7 +124,7 @@ private:
void paint_normal_frame(Gfx::Painter&); void paint_normal_frame(Gfx::Painter&);
void paint_tool_window_frame(Gfx::Painter&); void paint_tool_window_frame(Gfx::Painter&);
void paint_menubar(Gfx::Painter&); void paint_menubar(Gfx::Painter&);
Gfx::Bitmap* shadow_bitmap() const; MultiScaleBitmaps* shadow_bitmap() const;
Gfx::IntRect inflated_for_shadow(const Gfx::IntRect&) const; Gfx::IntRect inflated_for_shadow(const Gfx::IntRect&) const;
void handle_menubar_mouse_event(const MouseEvent&); void handle_menubar_mouse_event(const MouseEvent&);
@ -140,7 +141,7 @@ private:
Button* m_maximize_button { nullptr }; Button* m_maximize_button { nullptr };
Button* m_minimize_button { nullptr }; Button* m_minimize_button { nullptr };
HashMap<int, NonnullOwnPtr<RenderedCache>> m_rendered_cache; HashMap<int, NonnullOwnPtr<PerScaleRenderedCache>> m_rendered_cache;
RefPtr<Core::Timer> m_flash_timer; RefPtr<Core::Timer> m_flash_timer;
size_t m_flash_counter { 0 }; size_t m_flash_counter { 0 };

View file

@ -48,14 +48,10 @@ WindowManager::~WindowManager()
{ {
} }
NonnullRefPtr<Cursor> WindowManager::get_cursor(String const& name) RefPtr<Cursor> WindowManager::get_cursor(String const& name)
{ {
static auto const s_default_cursor_path = "/res/cursors/arrow.x2y2.png"; static auto const s_default_cursor_path = "/res/cursors/arrow.x2y2.png";
auto path = m_config->read_entry("Cursor", name, s_default_cursor_path); return Cursor::create(m_config->read_entry("Cursor", name, s_default_cursor_path), s_default_cursor_path);
auto gb = Gfx::Bitmap::load_from_file(path, compositor_icon_scale());
if (gb)
return Cursor::create(*gb, path);
return Cursor::create(*Gfx::Bitmap::load_from_file(s_default_cursor_path), s_default_cursor_path);
} }
void WindowManager::reload_config() void WindowManager::reload_config()
@ -1179,7 +1175,7 @@ void WindowManager::process_key_event(KeyEvent& event)
} }
if (event.type() == Event::KeyDown && (event.modifiers() == (Mod_Ctrl | Mod_Super | Mod_Shift) && event.key() == Key_I)) { if (event.type() == Event::KeyDown && (event.modifiers() == (Mod_Ctrl | Mod_Super | Mod_Shift) && event.key() == Key_I)) {
reload_icon_bitmaps_after_scale_change(!m_allow_hidpi_icons); reload_icon_bitmaps_after_scale_change();
Compositor::the().invalidate_screen(); Compositor::the().invalidate_screen();
return; return;
} }
@ -1595,16 +1591,8 @@ Gfx::IntPoint WindowManager::get_recommended_window_position(Gfx::IntPoint const
return point; return point;
} }
int WindowManager::compositor_icon_scale() const void WindowManager::reload_icon_bitmaps_after_scale_change()
{ {
if (!m_allow_hidpi_icons)
return 1;
return Screen::main().scale_factor(); // TODO: There is no *one* scale factor...
}
void WindowManager::reload_icon_bitmaps_after_scale_change(bool allow_hidpi_icons)
{
m_allow_hidpi_icons = allow_hidpi_icons;
reload_config(); reload_config();
m_window_stack.for_each_window([&](Window& window) { m_window_stack.for_each_window([&](Window& window) {
auto& window_frame = window.frame(); auto& window_frame = window.frame();

View file

@ -224,8 +224,7 @@ public:
Gfx::IntPoint get_recommended_window_position(Gfx::IntPoint const& desired); Gfx::IntPoint get_recommended_window_position(Gfx::IntPoint const& desired);
int compositor_icon_scale() const; void reload_icon_bitmaps_after_scale_change();
void reload_icon_bitmaps_after_scale_change(bool allow_hidpi_icons = true);
void reevaluate_hovered_window(Window* = nullptr); void reevaluate_hovered_window(Window* = nullptr);
Window* hovered_window() const { return m_hovered_window.ptr(); } Window* hovered_window() const { return m_hovered_window.ptr(); }
@ -233,7 +232,7 @@ public:
WindowStack& window_stack() { return m_window_stack; } WindowStack& window_stack() { return m_window_stack; }
private: private:
NonnullRefPtr<Cursor> get_cursor(String const& name); RefPtr<Cursor> get_cursor(String const& name);
void process_mouse_event(MouseEvent&); void process_mouse_event(MouseEvent&);
void process_event_for_doubleclick(Window& window, MouseEvent& event); void process_event_for_doubleclick(Window& window, MouseEvent& event);
@ -257,7 +256,6 @@ private:
void do_move_to_front(Window&, bool, bool); void do_move_to_front(Window&, bool, bool);
bool m_allow_hidpi_icons { true };
RefPtr<Cursor> m_hidden_cursor; RefPtr<Cursor> m_hidden_cursor;
RefPtr<Cursor> m_arrow_cursor; RefPtr<Cursor> m_arrow_cursor;
RefPtr<Cursor> m_hand_cursor; RefPtr<Cursor> m_hand_cursor;