浏览代码

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.
Aliaksandr Kalenik 1 年之前
父节点
当前提交
b821f7b283

+ 1 - 0
Tests/LibWeb/Ref/abspos-escapes-scroll-container.html

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

+ 4 - 0
Tests/LibWeb/Ref/button-inside-scroll-container.html

@@ -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;

+ 1 - 0
Tests/LibWeb/Ref/css-backgrounds.html

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

+ 1 - 0
Tests/LibWeb/Ref/overflow-hidden-7.html

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

+ 4 - 0
Tests/LibWeb/Ref/reference/scrollable-box-with-nested-stacking-context-ref.html

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

+ 3 - 0
Tests/LibWeb/Ref/reference/scrollable-contains-boxes-with-hidden-overflow-1-ref.html

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

+ 3 - 0
Tests/LibWeb/Ref/reference/scrollable-contains-boxes-with-hidden-overflow-2-ref.html

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

+ 3 - 0
Tests/LibWeb/Ref/reference/scrollable-contains-boxes-with-hidden-overflow-and-opacity-ref.html

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

+ 1 - 0
Tests/LibWeb/Ref/scroll-using-mousewheel-event.html

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

+ 4 - 0
Tests/LibWeb/Ref/scrollable-box-with-nested-stacking-context.html

@@ -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;

+ 3 - 0
Tests/LibWeb/Ref/scrollable-contains-boxes-with-hidden-overflow-1.html

@@ -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;
     }

+ 3 - 0
Tests/LibWeb/Ref/scrollable-contains-boxes-with-hidden-overflow-2.html

@@ -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;
     }

+ 4 - 0
Tests/LibWeb/Ref/scrollable-contains-boxes-with-hidden-overflow-and-opacity.html

@@ -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;
     }

+ 4 - 0
Tests/LibWeb/Ref/svg-inside-scroll-container.html

@@ -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;

+ 58 - 0
Userland/Libraries/LibWeb/Painting/PaintableBox.cpp

@@ -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();
 

+ 7 - 0
Userland/Libraries/LibWeb/Painting/PaintableBox.h

@@ -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;