mirror of
https://github.com/SerenityOS/serenity
synced 2024-10-15 12:23:15 +00:00
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:
parent
8d9e20cb03
commit
b821f7b283
|
@ -6,6 +6,7 @@
|
||||||
height: 200px;
|
height: 200px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
.content {
|
.content {
|
||||||
height: 600px;
|
height: 600px;
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<link rel="match" href="reference/button-inside-scroll-container-ref.html" />
|
<link rel="match" href="reference/button-inside-scroll-container-ref.html" />
|
||||||
<style>
|
<style>
|
||||||
|
* {
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
#scrollable {
|
#scrollable {
|
||||||
height: 300px;
|
height: 300px;
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
padding: 5px 10px 15px 20px;
|
padding: 5px 10px 15px 20px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.force-scroll {
|
.force-scroll {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
overflow-x: visible;
|
overflow-x: visible;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inner {
|
.inner {
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
<style>
|
<style>
|
||||||
|
* {
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
#container {
|
#container {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 500px;
|
height: 500px;
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<style>
|
<style>
|
||||||
|
* {
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
html {
|
html {
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
<!DOCTYPE html><style>
|
<!DOCTYPE html><style>
|
||||||
|
* {
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
html {
|
html {
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<style>
|
<style>
|
||||||
|
* {
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
html {
|
html {
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
|
scrollbar-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
<link rel="match" href="reference/scrollable-box-with-nested-stacking-context-ref.html" />
|
<link rel="match" href="reference/scrollable-box-with-nested-stacking-context-ref.html" />
|
||||||
<style>
|
<style>
|
||||||
|
* {
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
#scrollable-box {
|
#scrollable-box {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 500px;
|
height: 500px;
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<link rel="match" href="reference/scrollable-contains-boxes-with-hidden-overflow-1-ref.html" />
|
<link rel="match" href="reference/scrollable-contains-boxes-with-hidden-overflow-1-ref.html" />
|
||||||
<style>
|
<style>
|
||||||
|
* {
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
html {
|
html {
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<link rel="match" href="reference/scrollable-contains-boxes-with-hidden-overflow-2-ref.html" />
|
<link rel="match" href="reference/scrollable-contains-boxes-with-hidden-overflow-2-ref.html" />
|
||||||
<style>
|
<style>
|
||||||
|
* {
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
html {
|
html {
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<link rel="match" href="reference/scrollable-contains-boxes-with-hidden-overflow-and-opacity-ref.html" />
|
<link rel="match" href="reference/scrollable-contains-boxes-with-hidden-overflow-and-opacity-ref.html" />
|
||||||
<style>
|
<style>
|
||||||
|
* {
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<link rel="match" href="reference/svg-inside-scroll-container-ref.html" />
|
<link rel="match" href="reference/svg-inside-scroll-container-ref.html" />
|
||||||
<style>
|
<style>
|
||||||
|
* {
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
#scrollable {
|
#scrollable {
|
||||||
height: 300px;
|
height: 300px;
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
|
|
|
@ -253,6 +253,50 @@ void PaintableBox::after_paint(PaintContext& context, [[maybe_unused]] PaintPhas
|
||||||
clear_clip_overflow_rect(context, phase);
|
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
|
void PaintableBox::paint(PaintContext& context, PaintPhase phase) const
|
||||||
{
|
{
|
||||||
if (!is_visible())
|
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()) {
|
if (phase == PaintPhase::Overlay && layout_box().document().inspected_layout_node() == &layout_box()) {
|
||||||
auto content_rect = absolute_rect();
|
auto content_rect = absolute_rect();
|
||||||
|
|
||||||
|
|
|
@ -229,6 +229,13 @@ protected:
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] virtual bool is_paintable_box() const final { return true; }
|
[[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;
|
Optional<OverflowData> m_overflow_data;
|
||||||
|
|
||||||
CSSPixelPoint m_offset;
|
CSSPixelPoint m_offset;
|
||||||
|
|
Loading…
Reference in a new issue