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.
This commit is contained in:
Aliaksandr Kalenik 2024-08-17 18:56:56 +02:00 committed by Andreas Kling
commit ab76b99f1e
Notes: github-actions[bot] 2024-08-19 16:58:17 +00:00
12 changed files with 111 additions and 45 deletions

View file

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

View file

@ -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< using Command = Variant<
DrawGlyphRun, DrawGlyphRun,
FillRect, FillRect,
@ -404,6 +416,7 @@ using Command = Variant<
DrawTriangleWave, DrawTriangleWave,
AddRoundedRectClip, AddRoundedRectClip,
AddMask, AddMask,
PaintNestedDisplayList>; PaintNestedDisplayList,
PaintScrollBar>;
} }

View file

@ -34,6 +34,19 @@ void DisplayListPlayer::execute(DisplayList& display_list)
while (next_command_index < commands.size()) { while (next_command_index < commands.size()) {
auto scroll_frame_id = commands[next_command_index].scroll_frame_id; auto scroll_frame_id = commands[next_command_index].scroll_frame_id;
auto command = commands[next_command_index++].command; 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()) { 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>(); 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( command.visit(
@ -84,6 +97,7 @@ void DisplayListPlayer::execute(DisplayList& display_list)
else HANDLE_COMMAND(DrawTriangleWave, draw_triangle_wave) else HANDLE_COMMAND(DrawTriangleWave, draw_triangle_wave)
else HANDLE_COMMAND(AddRoundedRectClip, add_rounded_rect_clip) else HANDLE_COMMAND(AddRoundedRectClip, add_rounded_rect_clip)
else HANDLE_COMMAND(AddMask, add_mask) else HANDLE_COMMAND(AddMask, add_mask)
else HANDLE_COMMAND(PaintScrollBar, paint_scrollbar)
else HANDLE_COMMAND(PaintNestedDisplayList, paint_nested_display_list) else HANDLE_COMMAND(PaintNestedDisplayList, paint_nested_display_list)
else VERIFY_NOT_REACHED(); else VERIFY_NOT_REACHED();
// clang-format on // clang-format on

View file

@ -72,6 +72,7 @@ private:
virtual void add_rounded_rect_clip(AddRoundedRectClip const&) = 0; virtual void add_rounded_rect_clip(AddRoundedRectClip const&) = 0;
virtual void add_mask(AddMask const&) = 0; virtual void add_mask(AddMask const&) = 0;
virtual void paint_nested_display_list(PaintNestedDisplayList 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; virtual bool would_be_fully_clipped_by_painter(Gfx::IntRect) const = 0;
}; };

View file

@ -1304,6 +1304,27 @@ void DisplayListPlayerSkia::paint_nested_display_list(PaintNestedDisplayList con
execute(*command.display_list); 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 bool DisplayListPlayerSkia::would_be_fully_clipped_by_painter(Gfx::IntRect rect) const
{ {
return surface().canvas().quickReject(to_skia_rect(rect)); return surface().canvas().quickReject(to_skia_rect(rect));

View file

@ -77,6 +77,7 @@ private:
void draw_triangle_wave(DrawTriangleWave const&) override; void draw_triangle_wave(DrawTriangleWave const&) override;
void add_rounded_rect_clip(AddRoundedRectClip const&) override; void add_rounded_rect_clip(AddRoundedRectClip const&) override;
void add_mask(AddMask const&) override; void add_mask(AddMask const&) override;
void paint_scrollbar(PaintScrollBar const&) override;
void paint_nested_display_list(PaintNestedDisplayList const&) override; void paint_nested_display_list(PaintNestedDisplayList const&) override;
bool would_be_fully_clipped_by_painter(Gfx::IntRect) const override; bool would_be_fully_clipped_by_painter(Gfx::IntRect) const override;

View file

@ -408,4 +408,13 @@ void DisplayListRecorder::draw_triangle_wave(Gfx::IntPoint a_p1, Gfx::IntPoint a
.thickness = thickness }); .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 });
}
} }

View file

@ -143,6 +143,8 @@ public:
void draw_triangle_wave(Gfx::IntPoint a_p1, Gfx::IntPoint a_p2, Color color, int amplitude, int thickness); 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(DisplayList&);
~DisplayListRecorder(); ~DisplayListRecorder();

View file

@ -247,40 +247,51 @@ static constexpr CSSPixels scrollbar_thumb_thickness = 8;
Optional<CSSPixelRect> PaintableBox::scroll_thumb_rect(ScrollDirection direction) const 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 {}; 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 padding_rect = absolute_padding_box_rect();
auto scrollable_overflow_rect = this->scrollable_overflow_rect().value(); 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 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 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) if (scroll_overflow_size == 0)
return {}; return {};
auto thumb_size = scrollport_size * (scrollport_size / scroll_overflow_size); auto thumb_length = scrollport_size * (scrollport_size / scroll_overflow_size);
CSSPixels thumb_position = 0; CSSPixelFraction scroll_size = 0;
if (scroll_overflow_size > scrollport_size) if (scroll_overflow_size > scrollport_size)
thumb_position = scroll_offset * (scrollport_size - thumb_size) / (scroll_overflow_size - scrollport_size); scroll_size = (scrollport_size - thumb_length) / (scroll_overflow_size - scrollport_size);
CSSPixelRect rect;
CSSPixelRect thumb_rect; if (direction == ScrollDirection::Vertical) {
if (direction == ScrollDirection::Horizontal) { rect = { padding_rect.right() - scrollbar_thumb_thickness, padding_rect.top(), scrollbar_thumb_thickness, thumb_length };
thumb_rect = {
padding_rect.left() + thumb_position,
padding_rect.bottom() - scrollbar_thumb_thickness,
thumb_size,
scrollbar_thumb_thickness
};
} else { } else {
thumb_rect = { rect = { padding_rect.left() - scrollbar_thumb_thickness, padding_rect.bottom() - scrollbar_thumb_thickness, thumb_length, scrollbar_thumb_thickness };
padding_rect.right() - scrollbar_thumb_thickness,
padding_rect.top() + thumb_position,
scrollbar_thumb_thickness,
thumb_size
};
} }
return thumb_rect; return PaintableBox::ScrollbarData { rect, scroll_size };
} }
void PaintableBox::paint(PaintContext& context, PaintPhase phase) const 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(); auto scrollbar_width = computed_values().scrollbar_width();
if (phase == PaintPhase::Overlay && scrollbar_width != CSS::ScrollbarWidth::None) { if (phase == PaintPhase::Overlay && scrollbar_width != CSS::ScrollbarWidth::None) {
auto color = Color(Color::NamedColor::DarkGray).with_alpha(128); if (auto scrollbar_data = compute_scrollbar_data(ScrollDirection::Vertical); scrollbar_data.has_value()) {
auto border_color = Color(Color::NamedColor::LightGray).with_alpha(128); 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);
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 thumb_rect = scroll_thumb_rect(ScrollDirection::Vertical); thumb_rect.has_value()) { if (auto scrollbar_data = compute_scrollbar_data(ScrollDirection::Horizontal); scrollbar_data.has_value()) {
auto thumb_device_rect = context.enclosing_device_rect(thumb_rect.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);
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);
} }
} }

View file

@ -221,10 +221,15 @@ protected:
virtual CSSPixelRect compute_absolute_rect() const; virtual CSSPixelRect compute_absolute_rect() const;
virtual CSSPixelRect compute_absolute_paint_rect() const; virtual CSSPixelRect compute_absolute_paint_rect() const;
struct ScrollbarData {
CSSPixelRect thumb_rect;
CSSPixelFraction scroll_length;
};
enum class ScrollDirection { enum class ScrollDirection {
Horizontal, Horizontal,
Vertical, Vertical,
}; };
Optional<ScrollbarData> compute_scrollbar_data(ScrollDirection) const;
[[nodiscard]] Optional<CSSPixelRect> scroll_thumb_rect(ScrollDirection) const; [[nodiscard]] Optional<CSSPixelRect> scroll_thumb_rect(ScrollDirection) const;
[[nodiscard]] bool is_scrollable(ScrollDirection) const; [[nodiscard]] bool is_scrollable(ScrollDirection) const;

View file

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

View file

@ -170,6 +170,7 @@ void ViewportPaintable::refresh_scroll_state()
break; break;
} }
scroll_frame.cumulative_offset = -cumulative_offset; scroll_frame.cumulative_offset = -cumulative_offset;
scroll_frame.own_offset = -paintable_box.scroll_offset();
} }
} }