diff --git a/src/server/shell/decoration/basic_decoration.cpp b/src/server/shell/decoration/basic_decoration.cpp index 93e2d24ae18..7d56d239bc7 100644 --- a/src/server/shell/decoration/basic_decoration.cpp +++ b/src/server/shell/decoration/basic_decoration.cpp @@ -266,9 +266,15 @@ void msd::BasicDecoration::set_cursor(std::string const& cursor_image_name) shell->modify_surface(session, decoration_surface, spec); } +void msd::BasicDecoration::set_scale(float new_scale) +{ + scale = new_scale; + window_state_updated(); +} + auto msd::BasicDecoration::new_window_state() const -> std::unique_ptr { - return std::make_unique(static_geometry, window_surface); + return std::make_unique(static_geometry, window_surface, scale); } auto msd::BasicDecoration::create_surface() const -> std::shared_ptr @@ -369,7 +375,8 @@ void msd::BasicDecoration::update( &WindowState::titlebar_rect, &WindowState::left_border_rect, &WindowState::right_border_rect, - &WindowState::bottom_border_rect}) || + &WindowState::bottom_border_rect, + &WindowState::scale}) || input_updated({ &InputState::buttons})) { @@ -383,7 +390,8 @@ void msd::BasicDecoration::update( if (window_updated({ &WindowState::focused_state, &WindowState::side_border_width, - &WindowState::side_border_height})) + &WindowState::side_border_height, + &WindowState::scale})) { new_buffers.emplace_back( buffer_streams->left_border, @@ -396,7 +404,8 @@ void msd::BasicDecoration::update( if (window_updated({ &WindowState::focused_state, &WindowState::bottom_border_width, - &WindowState::bottom_border_height})) + &WindowState::bottom_border_height, + &WindowState::scale})) { new_buffers.emplace_back( buffer_streams->bottom_border, @@ -406,7 +415,8 @@ void msd::BasicDecoration::update( if (window_updated({ &WindowState::focused_state, &WindowState::window_name, - &WindowState::titlebar_rect}) || + &WindowState::titlebar_rect, + &WindowState::scale}) || input_updated({ &InputState::buttons})) { diff --git a/src/server/shell/decoration/basic_decoration.h b/src/server/shell/decoration/basic_decoration.h index ba7e4bd8587..f2071902c6a 100644 --- a/src/server/shell/decoration/basic_decoration.h +++ b/src/server/shell/decoration/basic_decoration.h @@ -85,6 +85,8 @@ class BasicDecoration void request_close(); void set_cursor(std::string const& cursor_image_name); + void set_scale(float scale) override; + protected: /// Creates an up-to-date WindowState object auto new_window_state() const -> std::unique_ptr; @@ -108,6 +110,8 @@ class BasicDecoration std::shared_ptr const cursor_images; std::shared_ptr const session; + float scale{1.0f}; + class BufferStreams; std::unique_ptr buffer_streams; std::unique_ptr renderer; diff --git a/src/server/shell/decoration/basic_manager.cpp b/src/server/shell/decoration/basic_manager.cpp index f710ad8ee35..4056707df00 100644 --- a/src/server/shell/decoration/basic_manager.cpp +++ b/src/server/shell/decoration/basic_manager.cpp @@ -17,6 +17,9 @@ #include "basic_manager.h" #include "decoration.h" +#include +#include + #include #include @@ -25,9 +28,47 @@ namespace mg = mir::graphics; namespace msh = mir::shell; namespace msd = mir::shell::decoration; -msd::BasicManager::BasicManager(DecorationBuilder&& decoration_builder) - : decoration_builder{decoration_builder} +class msd::DisplayConfigurationListener : public mg::NullDisplayConfigurationObserver { +public: + using Callback = std::function; + + explicit DisplayConfigurationListener(Callback callback) : callback{std::move(callback)} {} + +private: + void initial_configuration(std::shared_ptr const& config) override + { + callback(*config); + } + void configuration_applied(std::shared_ptr const& config) override + { + callback(*config); + } + + Callback const callback; +}; + +msd::BasicManager::BasicManager( + ObserverRegistrar& display_configuration_observers, + DecorationBuilder&& decoration_builder) : + decoration_builder{std::move(decoration_builder)}, + display_config_monitor{std::make_shared( + [&](mg::DisplayConfiguration const& config) + { + // Use the maximum scale to ensure sharp-looking decorations on all outputs + auto max_output_scale{0.0f}; + config.for_each_output( + [&](mg::DisplayConfigurationOutput const& output) + { + if (!output.used || !output.connected) return; + if (!output.valid() || (output.current_mode_index >= output.modes.size())) return; + + max_output_scale = std::max(max_output_scale, output.scale); + }); + set_scale(max_output_scale); + })} +{ + display_configuration_observers.register_interest(display_config_monitor); } msd::BasicManager::~BasicManager() @@ -53,6 +94,7 @@ void msd::BasicManager::decorate(std::shared_ptr const& surface) lock.unlock(); auto decoration = decoration_builder(locked_shell, surface); lock.lock(); + decoration->set_scale(scale); decorations[surface.get()] = std::move(decoration); } } @@ -85,3 +127,16 @@ void msd::BasicManager::undecorate_all() // Destroy the decorations outside the lock to_destroy.clear(); } + +void msd::BasicManager::set_scale(float new_scale) +{ + std::lock_guard lock{mutex}; + if (new_scale != scale) + { + scale = new_scale; + for (auto& it : decorations) + { + it.second->set_scale(scale); + } + } +} \ No newline at end of file diff --git a/src/server/shell/decoration/basic_manager.h b/src/server/shell/decoration/basic_manager.h index 684461e88f2..be4f2413924 100644 --- a/src/server/shell/decoration/basic_manager.h +++ b/src/server/shell/decoration/basic_manager.h @@ -18,10 +18,15 @@ #define MIR_SHELL_DECORATION_BASIC_MANAGER_H_ #include "manager.h" + +#include + #include #include #include +namespace mir::graphics { class DisplayConfigurationObserver; } + namespace mir { class Executor; @@ -35,17 +40,20 @@ class Shell; namespace decoration { class Decoration; +class DisplayConfigurationListener; /// Facilitates decorating windows with Mir's built-in server size decorations -class BasicManager - : public Manager +class BasicManager : + public Manager { public: using DecorationBuilder = std::function( std::shared_ptr const& shell, std::shared_ptr const& surface)>; - BasicManager(DecorationBuilder&& decoration_builder); + BasicManager( + mir::ObserverRegistrar& display_configuration_observers, + DecorationBuilder&& decoration_builder); ~BasicManager(); void init(std::weak_ptr const& shell) override; @@ -55,10 +63,14 @@ class BasicManager private: DecorationBuilder const decoration_builder; + std::shared_ptr const display_config_monitor; std::weak_ptr shell; std::mutex mutex; std::unordered_map> decorations; + float scale{1.0f}; + + void set_scale(float new_scale); }; } } diff --git a/src/server/shell/decoration/decoration.h b/src/server/shell/decoration/decoration.h index 8823d177208..2389ad2531c 100644 --- a/src/server/shell/decoration/decoration.h +++ b/src/server/shell/decoration/decoration.h @@ -30,6 +30,8 @@ class Decoration Decoration() = default; virtual ~Decoration() = default; + virtual void set_scale(float new_scale) = 0; + private: Decoration(Decoration const&) = delete; Decoration& operator=(Decoration const&) = delete; diff --git a/src/server/shell/decoration/renderer.cpp b/src/server/shell/decoration/renderer.cpp index de614b674f0..d9013e505de 100644 --- a/src/server/shell/decoration/renderer.cpp +++ b/src/server/shell/decoration/renderer.cpp @@ -479,6 +479,19 @@ msd::Renderer::Renderer( void msd::Renderer::update_state(WindowState const& window_state, InputState const& input_state) { + if (auto const new_scale{window_state.scale()}; new_scale != scale) + { + scale = new_scale; + + needs_solid_color_redraw = true; + solid_color_pixels.reset(); // force a reallocation next time it's needed + + needs_titlebar_redraw = true; + titlebar_pixels.reset(); // force a reallocation next time it's needed + + needs_titlebar_buttons_redraw = true; + } + left_border_size = window_state.left_border_rect().size; right_border_size = window_state.right_border_rect().size; bottom_border_size = window_state.bottom_border_rect().size; @@ -488,9 +501,12 @@ void msd::Renderer::update_state(WindowState const& window_state, InputState con length = std::max(length, area(right_border_size)); length = std::max(length, area(bottom_border_size)); - if (length != solid_color_pixels_length) + if (auto const scaled_length{static_cast(length * scale * scale)}; + length != solid_color_pixels_length || + scaled_length != scaled_solid_color_pixels_length) { solid_color_pixels_length = length; + scaled_solid_color_pixels_length = scaled_length; solid_color_pixels.reset(); // force a reallocation next time it's needed } @@ -540,31 +556,35 @@ void msd::Renderer::update_state(WindowState const& window_state, InputState con auto msd::Renderer::render_titlebar() -> std::optional> { - if (!area(titlebar_size)) + auto const scaled_titlebar_size{titlebar_size * scale}; + + if (!area(scaled_titlebar_size)) return std::nullopt; if (!titlebar_pixels) { - titlebar_pixels = alloc_pixels(titlebar_size); + titlebar_pixels = alloc_pixels(scaled_titlebar_size); needs_titlebar_redraw = true; } if (needs_titlebar_redraw) { - for (geom::Y y{0}; y < as_y(titlebar_size.height); y += geom::DeltaY{1}) + for (geom::Y y{0}; y < as_y(scaled_titlebar_size.height); y += geom::DeltaY{1}) { render_row( - titlebar_pixels.get(), titlebar_size, - {0, y}, titlebar_size.width, + titlebar_pixels.get(), scaled_titlebar_size, + {0, y}, scaled_titlebar_size.width, current_theme->background_color); } text->render( titlebar_pixels.get(), - titlebar_size, + scaled_titlebar_size, name, - static_geometry->title_font_top_left, - static_geometry->title_font_height, + geom::Point{ + static_geometry->title_font_top_left.x.as_value() * scale, + static_geometry->title_font_top_left.y.as_value() * scale}, + static_geometry->title_font_height * scale, current_theme->text_color); } @@ -572,30 +592,35 @@ auto msd::Renderer::render_titlebar() -> std::optionalsecond.normal_color; if (button.state == ButtonState::Hovered) button_color = icon->second.active_color; - for (geom::Y y{button.rect.top()}; y < button.rect.bottom(); y += geom::DeltaY{1}) + for (geom::Y y{scaled_button_rect.top()}; y < scaled_button_rect.bottom(); y += geom::DeltaY{1}) { render_row( titlebar_pixels.get(), - titlebar_size, - {button.rect.left(), y}, - button.rect.size.width, + scaled_titlebar_size, + {scaled_button_rect.left(), y}, + scaled_button_rect.size.width, button_color); } geom::Rectangle const icon_rect = { - button.rect.top_left + static_geometry->icon_padding, { - button.rect.size.width - static_geometry->icon_padding.dx * 2, - button.rect.size.height - static_geometry->icon_padding.dy * 2}}; + scaled_button_rect.top_left + static_geometry->icon_padding * scale, { + scaled_button_rect.size.width - static_geometry->icon_padding.dx * scale * 2, + scaled_button_rect.size.height - static_geometry->icon_padding.dy * scale * 2}}; icon->second.render_icon( titlebar_pixels.get(), - titlebar_size, + scaled_titlebar_size, icon_rect, - static_geometry->icon_line_width, + static_geometry->icon_line_width * scale, icon->second.icon_color); } else @@ -608,46 +633,49 @@ auto msd::Renderer::render_titlebar() -> std::optional std::optional> { - if (!area(left_border_size)) + auto const scaled_left_border_size{left_border_size * scale}; + if (!area(scaled_left_border_size)) return std::nullopt; update_solid_color_pixels(); - return make_buffer(solid_color_pixels.get(), left_border_size); + return make_buffer(solid_color_pixels.get(), scaled_left_border_size); } auto msd::Renderer::render_right_border() -> std::optional> { - if (!area(right_border_size)) + auto const scaled_right_border_size{right_border_size * scale}; + if (!area(scaled_right_border_size)) return std::nullopt; update_solid_color_pixels(); - return make_buffer(solid_color_pixels.get(), right_border_size); + return make_buffer(solid_color_pixels.get(), scaled_right_border_size); } auto msd::Renderer::render_bottom_border() -> std::optional> { - if (!area(bottom_border_size)) + auto const scaled_bottom_border_size{bottom_border_size * scale}; + if (!area(scaled_bottom_border_size)) return std::nullopt; update_solid_color_pixels(); - return make_buffer(solid_color_pixels.get(), bottom_border_size); + return make_buffer(solid_color_pixels.get(), scaled_bottom_border_size); } void msd::Renderer::update_solid_color_pixels() { if (!solid_color_pixels) { - solid_color_pixels = alloc_pixels(geom::Size{solid_color_pixels_length, 1}); + solid_color_pixels = alloc_pixels(geom::Size{scaled_solid_color_pixels_length, 1}); needs_solid_color_redraw = true; } if (needs_solid_color_redraw) { render_row( - solid_color_pixels.get(), geom::Size{solid_color_pixels_length, 1}, - geom::Point{}, geom::Width{solid_color_pixels_length}, + solid_color_pixels.get(), geom::Size{scaled_solid_color_pixels_length, 1}, + geom::Point{}, geom::Width{scaled_solid_color_pixels_length}, current_theme->background_color); } @@ -687,4 +715,4 @@ auto msd::Renderer::alloc_pixels(geometry::Size size) -> std::unique_ptr{new uint32_t[buf_size]}; else return nullptr; -} +} \ No newline at end of file diff --git a/src/server/shell/decoration/renderer.h b/src/server/shell/decoration/renderer.h index 7329dac103f..09754a4c1da 100644 --- a/src/server/shell/decoration/renderer.h +++ b/src/server/shell/decoration/renderer.h @@ -115,6 +115,7 @@ class Renderer geometry::Size right_border_size; geometry::Size bottom_border_size; size_t solid_color_pixels_length{0}; + size_t scaled_solid_color_pixels_length{0}; std::unique_ptr solid_color_pixels; // can be nullptr geometry::Size titlebar_size{}; @@ -127,6 +128,8 @@ class Renderer std::shared_ptr const text; + float scale{1.0f}; + void update_solid_color_pixels(); auto make_buffer( Pixel const* pixels, diff --git a/src/server/shell/decoration/window.cpp b/src/server/shell/decoration/window.cpp index 12bae4c3c2f..77a61f364e8 100644 --- a/src/server/shell/decoration/window.cpp +++ b/src/server/shell/decoration/window.cpp @@ -76,12 +76,14 @@ auto border_type_for(std::shared_ptr const& surface) -> msd::Border msd::WindowState::WindowState( std::shared_ptr const& static_geometry, - std::shared_ptr const& surface) + std::shared_ptr const& surface, + float scale) : static_geometry{static_geometry}, window_size_{surface->window_size()}, border_type_{border_type_for(surface)}, focus_state_{surface->focus_state()}, - window_name_{surface->name()} + window_name_{surface->name()}, + scale_{scale} { } @@ -226,6 +228,11 @@ auto msd::WindowState::button_rect(unsigned n) const -> geom::Rectangle {static_geometry->button_width, titlebar.size.height}}; } +auto msd::WindowState::scale() const -> float +{ + return scale_; +} + class msd::WindowSurfaceObserverManager::Observer : public ms::NullSurfaceObserver { diff --git a/src/server/shell/decoration/window.h b/src/server/shell/decoration/window.h index 6c4b28ecef1..5448d61c5b4 100644 --- a/src/server/shell/decoration/window.h +++ b/src/server/shell/decoration/window.h @@ -70,7 +70,8 @@ class WindowState public: WindowState( std::shared_ptr const& static_geometry, - std::shared_ptr const& window_surface); + std::shared_ptr const& window_surface, + float scale); auto window_size() const -> geometry::Size; auto border_type() const -> BorderType; @@ -94,6 +95,8 @@ class WindowState /// Returns the rectangle of the nth button auto button_rect(unsigned n) const -> geometry::Rectangle; + auto scale() const -> float; + private: WindowState(WindowState const&) = delete; WindowState& operator=(WindowState const&) = delete; @@ -103,6 +106,7 @@ class WindowState BorderType const border_type_; MirWindowFocusState const focus_state_; std::string window_name_; + float scale_; }; /// Observes the decorated window and calls on_update when its state changes diff --git a/src/server/shell/default_configuration.cpp b/src/server/shell/default_configuration.cpp index 282c953f304..d9ebb69f7b5 100644 --- a/src/server/shell/default_configuration.cpp +++ b/src/server/shell/default_configuration.cpp @@ -70,6 +70,7 @@ auto mir::DefaultServerConfiguration::the_decoration_manager() -> std::shared_pt [this]()->std::shared_ptr { return std::make_shared( + *the_display_configuration_observer_registrar(), [buffer_allocator = the_buffer_allocator(), executor = the_main_loop(), cursor_images = the_cursor_images()]( @@ -149,4 +150,3 @@ mir::DefaultServerConfiguration::the_shell_display_layout() return std::make_shared(the_display()); }); } - diff --git a/tests/unit-tests/shell/test_decoration_basic_manager.cpp b/tests/unit-tests/shell/test_decoration_basic_manager.cpp index 8cbc439d60f..5dcf7e6ba98 100644 --- a/tests/unit-tests/shell/test_decoration_basic_manager.cpp +++ b/tests/unit-tests/shell/test_decoration_basic_manager.cpp @@ -17,6 +17,7 @@ #include "src/server/shell/decoration/basic_manager.h" #include "src/server/shell/decoration/decoration.h" +#include "mir/test/doubles/stub_observer_registrar.h" #include "mir/test/doubles/stub_shell.h" #include @@ -33,6 +34,7 @@ using namespace testing; class StubDecoration : public msd::Decoration { + void set_scale(float /*new_scale*/) override {}; }; struct DecorationBasicManager @@ -53,7 +55,12 @@ struct DecorationBasicManager MOCK_METHOD1(decoration_destroyed, void(msd::Decoration*)); - msd::BasicManager manager{[this]( + std::shared_ptr> + registrar{std::make_shared>()}; + + msd::BasicManager manager{ + *registrar, + [this]( std::shared_ptr const&, std::shared_ptr const&) -> std::unique_ptr { @@ -76,6 +83,8 @@ class MockDecoration mock->decoration_destroyed(this); } + MOCK_METHOD1(set_scale, void(float)); + DecorationBasicManager* const mock; }; @@ -83,7 +92,8 @@ TEST_F(DecorationBasicManager, calls_build_decoration) { auto const surface = std::make_shared(); EXPECT_CALL(*this, build_decoration()) - .Times(1); + .Times(1) + .WillOnce(Invoke([](){ return new StubDecoration; })); manager.decorate(surface); } @@ -99,7 +109,8 @@ TEST_F(DecorationBasicManager, decorating_a_surface_is_idempotent) { auto const surface = std::make_shared(); EXPECT_CALL(*this, build_decoration()) - .Times(1); + .Times(1) + .WillOnce(Invoke([](){ return new StubDecoration; })); manager.decorate(surface); manager.decorate(surface); manager.decorate(surface);