From ab76b99f1e19b87b68638226098ad38e198856d4 Mon Sep 17 00:00:00 2001 From: Aliaksandr Kalenik Date: Sat, 17 Aug 2024 18:56:56 +0200 Subject: [PATCH] LibWeb: Update scroll thumb position by mutating display list A new display list item type named PaintScrollBar is introduced. Having a dedicated type for scroll bars allows the thumb position to be updated without rebuilding a display list. This was not possible with FillRectWithRoundedCorners that does not allow to tell whether it belongs to scroll thumb. --- .../LibWeb/Painting/ClippableAndScrollable.h | 6 ++ Userland/Libraries/LibWeb/Painting/Command.h | 15 +++- .../Libraries/LibWeb/Painting/DisplayList.cpp | 14 ++++ .../Libraries/LibWeb/Painting/DisplayList.h | 1 + .../LibWeb/Painting/DisplayListPlayerSkia.cpp | 21 +++++ .../LibWeb/Painting/DisplayListPlayerSkia.h | 1 + .../LibWeb/Painting/DisplayListRecorder.cpp | 9 +++ .../LibWeb/Painting/DisplayListRecorder.h | 2 + .../LibWeb/Painting/PaintableBox.cpp | 80 +++++++++---------- .../Libraries/LibWeb/Painting/PaintableBox.h | 5 ++ .../Libraries/LibWeb/Painting/ScrollFrame.h | 1 + .../LibWeb/Painting/ViewportPaintable.cpp | 1 + 12 files changed, 111 insertions(+), 45 deletions(-) diff --git a/Userland/Libraries/LibWeb/Painting/ClippableAndScrollable.h b/Userland/Libraries/LibWeb/Painting/ClippableAndScrollable.h index 9cc5e982a6c..023a73c7a85 100644 --- a/Userland/Libraries/LibWeb/Painting/ClippableAndScrollable.h +++ b/Userland/Libraries/LibWeb/Painting/ClippableAndScrollable.h @@ -24,6 +24,12 @@ public: [[nodiscard]] Optional clip_rect_for_hit_testing() const; [[nodiscard]] Optional own_scroll_frame_id() const; + [[nodiscard]] CSSPixelPoint own_scroll_frame_offset() const + { + if (m_own_scroll_frame) + return m_own_scroll_frame->own_offset; + return {}; + } void set_own_scroll_frame(RefPtr scroll_frame) { m_own_scroll_frame = scroll_frame; } void apply_clip(PaintContext&) const; diff --git a/Userland/Libraries/LibWeb/Painting/Command.h b/Userland/Libraries/LibWeb/Painting/Command.h index 92019fccd67..cd97e06cd53 100644 --- a/Userland/Libraries/LibWeb/Painting/Command.h +++ b/Userland/Libraries/LibWeb/Painting/Command.h @@ -374,6 +374,18 @@ struct PaintNestedDisplayList { } }; +struct PaintScrollBar { + int scroll_frame_id; + Gfx::IntRect rect; + CSSPixelFraction scroll_size; + bool vertical; + + void translate_by(Gfx::IntPoint const& offset) + { + rect.translate_by(offset); + } +}; + using Command = Variant< DrawGlyphRun, FillRect, @@ -404,6 +416,7 @@ using Command = Variant< DrawTriangleWave, AddRoundedRectClip, AddMask, - PaintNestedDisplayList>; + PaintNestedDisplayList, + PaintScrollBar>; } diff --git a/Userland/Libraries/LibWeb/Painting/DisplayList.cpp b/Userland/Libraries/LibWeb/Painting/DisplayList.cpp index 2146d3c88c9..ed93a344ef1 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayList.cpp +++ b/Userland/Libraries/LibWeb/Painting/DisplayList.cpp @@ -34,6 +34,19 @@ void DisplayListPlayer::execute(DisplayList& display_list) while (next_command_index < commands.size()) { auto scroll_frame_id = commands[next_command_index].scroll_frame_id; auto command = commands[next_command_index++].command; + + if (command.has()) { + auto& paint_scroll_bar = command.get(); + auto const& scroll_offset = scroll_state[paint_scroll_bar.scroll_frame_id]->own_offset; + if (paint_scroll_bar.vertical) { + auto offset = scroll_offset.y() * paint_scroll_bar.scroll_size; + paint_scroll_bar.rect.translate_by(0, -offset.to_int() * device_pixels_per_css_pixel); + } else { + auto offset = scroll_offset.x() * paint_scroll_bar.scroll_size; + paint_scroll_bar.rect.translate_by(-offset.to_int() * device_pixels_per_css_pixel, 0); + } + } + if (scroll_frame_id.has_value()) { auto const& scroll_offset = scroll_state[scroll_frame_id.value()]->cumulative_offset.to_type().scaled(device_pixels_per_css_pixel).to_type(); command.visit( @@ -84,6 +97,7 @@ void DisplayListPlayer::execute(DisplayList& display_list) else HANDLE_COMMAND(DrawTriangleWave, draw_triangle_wave) else HANDLE_COMMAND(AddRoundedRectClip, add_rounded_rect_clip) else HANDLE_COMMAND(AddMask, add_mask) + else HANDLE_COMMAND(PaintScrollBar, paint_scrollbar) else HANDLE_COMMAND(PaintNestedDisplayList, paint_nested_display_list) else VERIFY_NOT_REACHED(); // clang-format on diff --git a/Userland/Libraries/LibWeb/Painting/DisplayList.h b/Userland/Libraries/LibWeb/Painting/DisplayList.h index 60ec3c4832e..3ffbb504961 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayList.h +++ b/Userland/Libraries/LibWeb/Painting/DisplayList.h @@ -72,6 +72,7 @@ private: virtual void add_rounded_rect_clip(AddRoundedRectClip const&) = 0; virtual void add_mask(AddMask const&) = 0; virtual void paint_nested_display_list(PaintNestedDisplayList const&) = 0; + virtual void paint_scrollbar(PaintScrollBar const&) = 0; virtual bool would_be_fully_clipped_by_painter(Gfx::IntRect) const = 0; }; diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp index 53dd0036c21..3e5de5b1e52 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp +++ b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.cpp @@ -1304,6 +1304,27 @@ void DisplayListPlayerSkia::paint_nested_display_list(PaintNestedDisplayList con execute(*command.display_list); } +void DisplayListPlayerSkia::paint_scrollbar(PaintScrollBar const& command) +{ + auto rect = to_skia_rect(command.rect); + auto radius = rect.width() / 2; + auto rrect = SkRRect::MakeRectXY(rect, radius, radius); + + auto& canvas = surface().canvas(); + + auto fill_color = Color(Color::NamedColor::DarkGray).with_alpha(128); + SkPaint fill_paint; + fill_paint.setColor(to_skia_color(fill_color)); + canvas.drawRRect(rrect, fill_paint); + + auto stroke_color = Color(Color::NamedColor::LightGray).with_alpha(128); + SkPaint stroke_paint; + stroke_paint.setStroke(true); + stroke_paint.setStrokeWidth(1); + stroke_paint.setColor(to_skia_color(stroke_color)); + canvas.drawRRect(rrect, stroke_paint); +} + bool DisplayListPlayerSkia::would_be_fully_clipped_by_painter(Gfx::IntRect rect) const { return surface().canvas().quickReject(to_skia_rect(rect)); diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.h b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.h index 18b0d4ef962..e13bafc174f 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.h +++ b/Userland/Libraries/LibWeb/Painting/DisplayListPlayerSkia.h @@ -77,6 +77,7 @@ private: void draw_triangle_wave(DrawTriangleWave const&) override; void add_rounded_rect_clip(AddRoundedRectClip const&) override; void add_mask(AddMask const&) override; + void paint_scrollbar(PaintScrollBar const&) override; void paint_nested_display_list(PaintNestedDisplayList const&) override; bool would_be_fully_clipped_by_painter(Gfx::IntRect) const override; diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp b/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp index 701a029f496..4627ab40339 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp +++ b/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.cpp @@ -408,4 +408,13 @@ void DisplayListRecorder::draw_triangle_wave(Gfx::IntPoint a_p1, Gfx::IntPoint a .thickness = thickness }); } +void DisplayListRecorder::paint_scrollbar(int scroll_frame_id, Gfx::IntRect rect, CSSPixelFraction scroll_size, bool vertical) +{ + append(PaintScrollBar { + .scroll_frame_id = scroll_frame_id, + .rect = rect, + .scroll_size = scroll_size, + .vertical = vertical }); +} + } diff --git a/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.h b/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.h index 91120d2df66..faddf81c545 100644 --- a/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.h +++ b/Userland/Libraries/LibWeb/Painting/DisplayListRecorder.h @@ -143,6 +143,8 @@ public: void draw_triangle_wave(Gfx::IntPoint a_p1, Gfx::IntPoint a_p2, Color color, int amplitude, int thickness); + void paint_scrollbar(int scroll_frame_id, Gfx::IntRect, CSSPixelFraction scroll_size, bool vertical); + DisplayListRecorder(DisplayList&); ~DisplayListRecorder(); diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index 65c39a8efa2..d57a1d64d2f 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -247,40 +247,51 @@ static constexpr CSSPixels scrollbar_thumb_thickness = 8; Optional PaintableBox::scroll_thumb_rect(ScrollDirection direction) const { - if (!is_scrollable(direction)) + auto maybe_scrollbar_data = compute_scrollbar_data(direction); + if (!maybe_scrollbar_data.has_value()) return {}; + auto scroll_offset = direction == ScrollDirection::Horizontal ? -own_scroll_frame_offset().x() : -own_scroll_frame_offset().y(); + auto thumb_offset = scroll_offset * maybe_scrollbar_data->scroll_length; + + CSSPixelRect thumb_rect = maybe_scrollbar_data->thumb_rect; + if (direction == ScrollDirection::Horizontal) { + thumb_rect.translate_by(thumb_offset, 0); + } else { + thumb_rect.translate_by(0, thumb_offset); + } + return thumb_rect; +} + +Optional PaintableBox::compute_scrollbar_data(ScrollDirection direction) const +{ + if (!is_scrollable(direction)) { + return {}; + } + + if (!own_scroll_frame_id().has_value()) { + return {}; + } + auto padding_rect = absolute_padding_box_rect(); auto scrollable_overflow_rect = this->scrollable_overflow_rect().value(); auto scroll_overflow_size = direction == ScrollDirection::Horizontal ? scrollable_overflow_rect.width() : scrollable_overflow_rect.height(); auto scrollport_size = direction == ScrollDirection::Horizontal ? padding_rect.width() : padding_rect.height(); - auto scroll_offset = direction == ScrollDirection::Horizontal ? this->scroll_offset().x() : this->scroll_offset().y(); if (scroll_overflow_size == 0) return {}; - auto thumb_size = scrollport_size * (scrollport_size / scroll_overflow_size); - CSSPixels thumb_position = 0; + auto thumb_length = scrollport_size * (scrollport_size / scroll_overflow_size); + CSSPixelFraction scroll_size = 0; if (scroll_overflow_size > scrollport_size) - thumb_position = scroll_offset * (scrollport_size - thumb_size) / (scroll_overflow_size - scrollport_size); - - CSSPixelRect thumb_rect; - if (direction == ScrollDirection::Horizontal) { - thumb_rect = { - padding_rect.left() + thumb_position, - padding_rect.bottom() - scrollbar_thumb_thickness, - thumb_size, - scrollbar_thumb_thickness - }; + scroll_size = (scrollport_size - thumb_length) / (scroll_overflow_size - scrollport_size); + CSSPixelRect rect; + if (direction == ScrollDirection::Vertical) { + rect = { padding_rect.right() - scrollbar_thumb_thickness, padding_rect.top(), scrollbar_thumb_thickness, thumb_length }; } else { - thumb_rect = { - padding_rect.right() - scrollbar_thumb_thickness, - padding_rect.top() + thumb_position, - scrollbar_thumb_thickness, - thumb_size - }; + rect = { padding_rect.left() - scrollbar_thumb_thickness, padding_rect.bottom() - scrollbar_thumb_thickness, thumb_length, scrollbar_thumb_thickness }; } - return thumb_rect; + return PaintableBox::ScrollbarData { rect, scroll_size }; } void PaintableBox::paint(PaintContext& context, PaintPhase phase) const @@ -332,30 +343,11 @@ void PaintableBox::paint(PaintContext& context, PaintPhase phase) const auto scrollbar_width = computed_values().scrollbar_width(); if (phase == PaintPhase::Overlay && scrollbar_width != CSS::ScrollbarWidth::None) { - auto color = Color(Color::NamedColor::DarkGray).with_alpha(128); - auto border_color = Color(Color::NamedColor::LightGray).with_alpha(128); - auto borders_data = BordersDataDevicePixels { - .top = BorderDataDevicePixels { border_color, CSS::LineStyle::Solid, 1 }, - .right = BorderDataDevicePixels { border_color, CSS::LineStyle::Solid, 1 }, - .bottom = BorderDataDevicePixels { border_color, CSS::LineStyle::Solid, 1 }, - .left = BorderDataDevicePixels { border_color, CSS::LineStyle::Solid, 1 }, - }; - int thumb_corner_radius = static_cast(context.rounded_device_pixels(scrollbar_thumb_thickness / 2)); - CornerRadii corner_radii = { - .top_left = Gfx::AntiAliasingPainter::CornerRadius { thumb_corner_radius, thumb_corner_radius }, - .top_right = Gfx::AntiAliasingPainter::CornerRadius { thumb_corner_radius, thumb_corner_radius }, - .bottom_right = Gfx::AntiAliasingPainter::CornerRadius { thumb_corner_radius, thumb_corner_radius }, - .bottom_left = Gfx::AntiAliasingPainter::CornerRadius { thumb_corner_radius, thumb_corner_radius }, - }; - if (auto thumb_rect = scroll_thumb_rect(ScrollDirection::Horizontal); thumb_rect.has_value()) { - auto thumb_device_rect = context.enclosing_device_rect(thumb_rect.value()); - paint_all_borders(context.display_list_recorder(), thumb_device_rect, corner_radii, borders_data); - context.display_list_recorder().fill_rect_with_rounded_corners(thumb_device_rect.to_type(), color, thumb_corner_radius); + if (auto scrollbar_data = compute_scrollbar_data(ScrollDirection::Vertical); scrollbar_data.has_value()) { + context.display_list_recorder().paint_scrollbar(own_scroll_frame_id().value(), context.rounded_device_rect(scrollbar_data->thumb_rect).to_type(), scrollbar_data->scroll_length, true); } - if (auto thumb_rect = scroll_thumb_rect(ScrollDirection::Vertical); thumb_rect.has_value()) { - auto thumb_device_rect = context.enclosing_device_rect(thumb_rect.value()); - paint_all_borders(context.display_list_recorder(), thumb_device_rect, corner_radii, borders_data); - context.display_list_recorder().fill_rect_with_rounded_corners(thumb_device_rect.to_type(), color, thumb_corner_radius); + if (auto scrollbar_data = compute_scrollbar_data(ScrollDirection::Horizontal); scrollbar_data.has_value()) { + context.display_list_recorder().paint_scrollbar(own_scroll_frame_id().value(), context.rounded_device_rect(scrollbar_data->thumb_rect).to_type(), scrollbar_data->scroll_length, false); } } diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.h b/Userland/Libraries/LibWeb/Painting/PaintableBox.h index bdaed44287a..0dc9143b76f 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.h +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.h @@ -221,10 +221,15 @@ protected: virtual CSSPixelRect compute_absolute_rect() const; virtual CSSPixelRect compute_absolute_paint_rect() const; + struct ScrollbarData { + CSSPixelRect thumb_rect; + CSSPixelFraction scroll_length; + }; enum class ScrollDirection { Horizontal, Vertical, }; + Optional compute_scrollbar_data(ScrollDirection) const; [[nodiscard]] Optional scroll_thumb_rect(ScrollDirection) const; [[nodiscard]] bool is_scrollable(ScrollDirection) const; diff --git a/Userland/Libraries/LibWeb/Painting/ScrollFrame.h b/Userland/Libraries/LibWeb/Painting/ScrollFrame.h index 9a2cb25e045..afdc8b3ebc8 100644 --- a/Userland/Libraries/LibWeb/Painting/ScrollFrame.h +++ b/Userland/Libraries/LibWeb/Painting/ScrollFrame.h @@ -13,6 +13,7 @@ namespace Web::Painting { struct ScrollFrame : public RefCounted { i32 id { -1 }; CSSPixelPoint cumulative_offset; + CSSPixelPoint own_offset; }; } diff --git a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp index b4af10ff3be..b6752156d4c 100644 --- a/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp +++ b/Userland/Libraries/LibWeb/Painting/ViewportPaintable.cpp @@ -170,6 +170,7 @@ void ViewportPaintable::refresh_scroll_state() break; } scroll_frame.cumulative_offset = -cumulative_offset; + scroll_frame.own_offset = -paintable_box.scroll_offset(); } }