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<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;
|
||||
|
|
|
@ -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>;
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace Web::Painting {
|
|||
struct ScrollFrame : public RefCounted<ScrollFrame> {
|
||||
i32 id { -1 };
|
||||
CSSPixelPoint cumulative_offset;
|
||||
CSSPixelPoint own_offset;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -170,6 +170,7 @@ void ViewportPaintable::refresh_scroll_state()
|
|||
break;
|
||||
}
|
||||
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