LibWeb: Implement scrollbar painting

Introduces the rendering of scroll thumbs in vertical and horizontal
directions. Currently, the thumbs are purely graphical elements that
do not respond to mouse events. Nevertheless, this is beneficial as it
makes it easier to identify elements that should respond to scrolling
events.

Painting of scrollbars uncovers numerous bugs in the calculation of
scrollable overflow rectangles highlighting all the places where
elements are made scrollable whey they shouldn't be. Positively, this
issue might motivate us to pay more attention to this problem to
eliminate unnecessary scrollbars.

Currently, the scrollbar style is uniform across all platforms: a
semi-transparent gray rectangle with rounded corners.

Also here we add `scrollbar-width: none` to all existing scrolling
ref-tests, so they keep working with this change.
This commit is contained in:
Aliaksandr Kalenik 2024-02-15 21:12:50 +01:00 committed by Tim Flynn
parent 8d9e20cb03
commit b821f7b283
16 changed files with 104 additions and 0 deletions

View file

@ -6,6 +6,7 @@
height: 200px;
overflow: auto;
border: 1px solid black;
scrollbar-width: none;
}
.content {
height: 600px;

View file

@ -1,6 +1,10 @@
<!DOCTYPE html>
<link rel="match" href="reference/button-inside-scroll-container-ref.html" />
<style>
* {
scrollbar-width: none;
}
#scrollable {
height: 300px;
overflow: scroll;

View file

@ -12,6 +12,7 @@
padding: 5px 10px 15px 20px;
overflow: auto;
margin-bottom: 10px;
scrollbar-width: none;
}
.force-scroll {

View file

@ -6,6 +6,7 @@
overflow-x: visible;
width: 200px;
height: 200px;
scrollbar-width: none;
}
.inner {

View file

@ -1,4 +1,8 @@
<style>
* {
scrollbar-width: none;
}
#container {
width: 300px;
height: 500px;

View file

@ -1,5 +1,8 @@
<!DOCTYPE html>
<style>
* {
scrollbar-width: none;
}
html {
background: white;
}

View file

@ -1,4 +1,7 @@
<!DOCTYPE html><style>
* {
scrollbar-width: none;
}
html {
background: white;
}

View file

@ -1,5 +1,8 @@
<!DOCTYPE html>
<style>
* {
scrollbar-width: none;
}
html {
background: white;
}

View file

@ -5,6 +5,7 @@
overflow-y: scroll;
width: 100px;
height: 300px;
scrollbar-width: none;
}
.item {

View file

@ -1,5 +1,9 @@
<link rel="match" href="reference/scrollable-box-with-nested-stacking-context-ref.html" />
<style>
* {
scrollbar-width: none;
}
#scrollable-box {
width: 300px;
height: 500px;

View file

@ -1,6 +1,9 @@
<!DOCTYPE html>
<link rel="match" href="reference/scrollable-contains-boxes-with-hidden-overflow-1-ref.html" />
<style>
* {
scrollbar-width: none;
}
html {
background: white;
}

View file

@ -1,6 +1,9 @@
<!DOCTYPE html>
<link rel="match" href="reference/scrollable-contains-boxes-with-hidden-overflow-2-ref.html" />
<style>
* {
scrollbar-width: none;
}
html {
background: white;
}

View file

@ -1,6 +1,10 @@
<!DOCTYPE html>
<link rel="match" href="reference/scrollable-contains-boxes-with-hidden-overflow-and-opacity-ref.html" />
<style>
* {
scrollbar-width: none;
}
html {
background: white;
}

View file

@ -1,6 +1,10 @@
<!DOCTYPE html>
<link rel="match" href="reference/svg-inside-scroll-container-ref.html" />
<style>
* {
scrollbar-width: none;
}
#scrollable {
height: 300px;
overflow: scroll;

View file

@ -253,6 +253,50 @@ void PaintableBox::after_paint(PaintContext& context, [[maybe_unused]] PaintPhas
clear_clip_overflow_rect(context, phase);
}
bool PaintableBox::is_scrollable(ScrollDirection direction) const
{
auto overflow = direction == ScrollDirection::Horizontal ? computed_values().overflow_x() : computed_values().overflow_y();
auto scrollable_overflow_size = direction == ScrollDirection::Horizontal ? scrollable_overflow_rect()->width() : scrollable_overflow_rect()->height();
auto scrollport_size = direction == ScrollDirection::Horizontal ? absolute_padding_box_rect().width() : absolute_padding_box_rect().height();
if (overflow == CSS::Overflow::Auto)
return scrollable_overflow_size > scrollport_size;
return overflow == CSS::Overflow::Scroll;
}
static constexpr CSSPixels scrollbar_thumb_thickness = 8;
Optional<CSSPixelRect> PaintableBox::scroll_thumb_rect(ScrollDirection direction) const
{
if (!is_scrollable(direction))
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();
auto thumb_size = scrollport_size * (scrollport_size / scroll_overflow_size);
CSSPixels thumb_position = 0;
if (scroll_overflow_size > scrollport_size)
thumb_position = scroll_offset * (scrollport_size - thumb_size) / (scroll_overflow_size - scrollport_size);
if (direction == ScrollDirection::Horizontal) {
return CSSPixelRect {
padding_rect.left() + thumb_position,
padding_rect.bottom() - scrollbar_thumb_thickness,
thumb_size,
scrollbar_thumb_thickness
};
}
return CSSPixelRect {
padding_rect.right() - scrollbar_thumb_thickness,
padding_rect.top() + thumb_position,
scrollbar_thumb_thickness,
thumb_size
};
}
void PaintableBox::paint(PaintContext& context, PaintPhase phase) const
{
if (!is_visible())
@ -295,6 +339,20 @@ 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);
int thumb_corner_radius = static_cast<int>(context.rounded_device_pixels(scrollbar_thumb_thickness / 2));
if (auto thumb_rect = scroll_thumb_rect(ScrollDirection::Horizontal); thumb_rect.has_value()) {
auto thumb_device_rect = context.enclosing_device_rect(thumb_rect.value());
context.recording_painter().fill_rect_with_rounded_corners(thumb_device_rect.to_type<int>(), color, thumb_corner_radius, thumb_corner_radius, thumb_corner_radius, thumb_corner_radius);
}
if (auto thumb_rect = scroll_thumb_rect(ScrollDirection::Vertical); thumb_rect.has_value()) {
auto thumb_device_rect = context.enclosing_device_rect(thumb_rect.value());
context.recording_painter().fill_rect_with_rounded_corners(thumb_device_rect.to_type<int>(), color, thumb_corner_radius, thumb_corner_radius, thumb_corner_radius, thumb_corner_radius);
}
}
if (phase == PaintPhase::Overlay && layout_box().document().inspected_layout_node() == &layout_box()) {
auto content_rect = absolute_rect();

View file

@ -229,6 +229,13 @@ protected:
private:
[[nodiscard]] virtual bool is_paintable_box() const final { return true; }
enum class ScrollDirection {
Horizontal,
Vertical,
};
[[nodiscard]] Optional<CSSPixelRect> scroll_thumb_rect(ScrollDirection) const;
[[nodiscard]] bool is_scrollable(ScrollDirection) const;
Optional<OverflowData> m_overflow_data;
CSSPixelPoint m_offset;