mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-09-14 05:22:24 +00:00
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:
parent
e2ad568095
commit
ab76b99f1e
Notes:
github-actions[bot]
2024-08-19 16:58:17 +00:00
Author: https://github.com/kalenikaliaksandr
Commit: ab76b99f1e
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/1106
12 changed files with 111 additions and 45 deletions
|
@ -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;
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 });
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue