Pārlūkot izejas kodu

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.
Aliaksandr Kalenik 11 mēneši atpakaļ
vecāks
revīzija
ab76b99f1e

+ 6 - 0
Userland/Libraries/LibWeb/Painting/ClippableAndScrollable.h

@@ -24,6 +24,12 @@ public:
     [[nodiscard]] Optional<CSSPixelRect> clip_rect_for_hit_testing() const;
 
     [[nodiscard]] Optional<int> 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<ScrollFrame> scroll_frame) { m_own_scroll_frame = scroll_frame; }
 
     void apply_clip(PaintContext&) const;

+ 14 - 1
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>;
 
 }

+ 14 - 0
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<PaintScrollBar>()) {
+            auto& paint_scroll_bar = command.get<PaintScrollBar>();
+            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<double>().scaled(device_pixels_per_css_pixel).to_type<int>();
             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

+ 1 - 0
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;
 };
 

+ 21 - 0
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));

+ 1 - 0
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;

+ 9 - 0
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 });
+}
+
 }

+ 2 - 0
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();
 

+ 36 - 44
Userland/Libraries/LibWeb/Painting/PaintableBox.cpp

@@ -247,40 +247,51 @@ static constexpr CSSPixels scrollbar_thumb_thickness = 8;
 
 Optional<CSSPixelRect> 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::ScrollbarData> 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<int>(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<int>(), 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<int>(), 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<int>(), 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<int>(), scrollbar_data->scroll_length, false);
         }
     }
 

+ 5 - 0
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<ScrollbarData> compute_scrollbar_data(ScrollDirection) const;
     [[nodiscard]] Optional<CSSPixelRect> scroll_thumb_rect(ScrollDirection) const;
     [[nodiscard]] bool is_scrollable(ScrollDirection) const;
 

+ 1 - 0
Userland/Libraries/LibWeb/Painting/ScrollFrame.h

@@ -13,6 +13,7 @@ namespace Web::Painting {
 struct ScrollFrame : public RefCounted<ScrollFrame> {
     i32 id { -1 };
     CSSPixelPoint cumulative_offset;
+    CSSPixelPoint own_offset;
 };
 
 }

+ 1 - 0
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();
     }
 }