mirror of
https://github.com/SerenityOS/serenity
synced 2024-10-15 12:23:15 +00:00
LibWeb: Account for all clipped border radii in containing block chain
With this change, instead of applying only the border-radius clipping from the closest containing block with hidden overflow, we now collect all boxes within the containing block chain and apply the clipping from all of them.
This commit is contained in:
parent
1ae416fa94
commit
d4932196cc
|
@ -0,0 +1,30 @@
|
|||
<!DOCTYPE html>
|
||||
<link rel="match" href="reference/nested-boxes-with-hidden-overflow-and-border-radius-ref.html" />
|
||||
<style>
|
||||
.outer {
|
||||
overflow: hidden;
|
||||
border-radius: 100px;
|
||||
background-color: magenta;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
.middle {
|
||||
overflow: hidden;
|
||||
border-radius: 50px;
|
||||
transform: translate(10px, 10px);
|
||||
background-color: lawngreen;
|
||||
}
|
||||
|
||||
.inner {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background-color: black;
|
||||
transform: translate(10px, 10px);
|
||||
}
|
||||
</style>
|
||||
<div class="outer">
|
||||
<div class="middle">
|
||||
<div class="inner"></div>
|
||||
</div>
|
||||
</div>
|
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,10 @@
|
|||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: white;
|
||||
}
|
||||
</style>
|
||||
<img src="./images/nested-boxes-with-hidden-overflow-and-border-radius-ref.png">
|
|
@ -31,6 +31,12 @@ struct BorderRadiusData {
|
|||
if (vertical_radius != 0)
|
||||
vertical_radius = max(CSSPixels(0), vertical_radius - vertical);
|
||||
}
|
||||
|
||||
inline void union_max_radii(BorderRadiusData const& other)
|
||||
{
|
||||
horizontal_radius = max(horizontal_radius, other.horizontal_radius);
|
||||
vertical_radius = max(vertical_radius, other.vertical_radius);
|
||||
}
|
||||
};
|
||||
|
||||
using CornerRadius = Gfx::AntiAliasingPainter::CornerRadius;
|
||||
|
@ -58,6 +64,14 @@ struct BorderRadiiData {
|
|||
return top_left || top_right || bottom_right || bottom_left;
|
||||
}
|
||||
|
||||
inline void union_max_radii(BorderRadiiData const& other)
|
||||
{
|
||||
top_left.union_max_radii(other.top_left);
|
||||
top_right.union_max_radii(other.top_right);
|
||||
bottom_right.union_max_radii(other.bottom_right);
|
||||
bottom_left.union_max_radii(other.bottom_left);
|
||||
}
|
||||
|
||||
inline void shrink(CSSPixels top, CSSPixels right, CSSPixels bottom, CSSPixels left)
|
||||
{
|
||||
top_left.shrink(left, top);
|
||||
|
|
40
Userland/Libraries/LibWeb/Painting/ClipFrame.h
Normal file
40
Userland/Libraries/LibWeb/Painting/ClipFrame.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/Painting/BorderRadiiData.h>
|
||||
#include <LibWeb/PixelUnits.h>
|
||||
|
||||
namespace Web::Painting {
|
||||
|
||||
struct BorderRadiiClip {
|
||||
CSSPixelRect rect;
|
||||
BorderRadiiData radii;
|
||||
};
|
||||
|
||||
struct ClipFrame : public RefCounted<ClipFrame> {
|
||||
Vector<BorderRadiiClip> const& border_radii_clips() const { return m_border_radii_clips; }
|
||||
void add_border_radii_clip(BorderRadiiClip border_radii_clip)
|
||||
{
|
||||
for (auto& existing_clip : m_border_radii_clips) {
|
||||
if (border_radii_clip.rect == existing_clip.rect) {
|
||||
existing_clip.radii.union_max_radii(border_radii_clip.radii);
|
||||
return;
|
||||
}
|
||||
}
|
||||
m_border_radii_clips.append(border_radii_clip);
|
||||
}
|
||||
|
||||
CSSPixelRect rect() const { return m_rect; }
|
||||
void set_rect(CSSPixelRect rect) { m_rect = rect; }
|
||||
|
||||
private:
|
||||
CSSPixelRect m_rect;
|
||||
Vector<BorderRadiiClip> m_border_radii_clips;
|
||||
};
|
||||
|
||||
}
|
|
@ -44,7 +44,7 @@ Optional<CSSPixelPoint> InlinePaintable::enclosing_scroll_frame_offset() const
|
|||
Optional<CSSPixelRect> InlinePaintable::clip_rect() const
|
||||
{
|
||||
if (m_enclosing_clip_frame)
|
||||
return m_enclosing_clip_frame->rect;
|
||||
return m_enclosing_clip_frame->rect();
|
||||
return {};
|
||||
}
|
||||
|
||||
|
|
|
@ -214,14 +214,14 @@ Optional<CSSPixelPoint> PaintableBox::enclosing_scroll_frame_offset() const
|
|||
Optional<CSSPixelRect> PaintableBox::clip_rect() const
|
||||
{
|
||||
if (m_enclosing_clip_frame)
|
||||
return m_enclosing_clip_frame->rect;
|
||||
return m_enclosing_clip_frame->rect();
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<BorderRadiiData> PaintableBox::corner_clip_radii() const
|
||||
Span<BorderRadiiClip const> PaintableBox::border_radii_clips() const
|
||||
{
|
||||
if (m_enclosing_clip_frame)
|
||||
return m_enclosing_clip_frame->corner_clip_radii;
|
||||
return m_enclosing_clip_frame->border_radii_clips();
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -444,12 +444,17 @@ void PaintableBox::apply_clip_overflow_rect(PaintContext& context, PaintPhase ph
|
|||
m_clipping_overflow = true;
|
||||
context.recording_painter().save();
|
||||
context.recording_painter().add_clip_rect(context.enclosing_device_rect(overflow_clip_rect).to_type<int>());
|
||||
if (corner_clip_radii().has_value()) {
|
||||
VERIFY(!m_corner_clipper_id.has_value());
|
||||
m_corner_clipper_id = context.allocate_corner_clipper_id();
|
||||
auto corner_radii = corner_clip_radii()->as_corners(context);
|
||||
if (corner_radii.has_any_radius())
|
||||
context.recording_painter().sample_under_corners(*m_corner_clipper_id, corner_clip_radii()->as_corners(context), context.rounded_device_rect(overflow_clip_rect).to_type<int>(), CornerClip::Outside);
|
||||
auto const& border_radii_clips = this->border_radii_clips();
|
||||
m_corner_clipper_ids.resize(border_radii_clips.size());
|
||||
for (size_t corner_clip_index = 0; corner_clip_index < border_radii_clips.size(); ++corner_clip_index) {
|
||||
auto const& corner_clip = border_radii_clips[corner_clip_index];
|
||||
auto corners = corner_clip.radii.as_corners(context);
|
||||
if (!corners.has_any_radius())
|
||||
continue;
|
||||
auto corner_clipper_id = context.allocate_corner_clipper_id();
|
||||
m_corner_clipper_ids[corner_clip_index] = corner_clipper_id;
|
||||
auto rect = corner_clip.rect.translated(-combined_transform.translation().to_type<CSSPixels>());
|
||||
context.recording_painter().sample_under_corners(corner_clipper_id, corner_clip.radii.as_corners(context), context.rounded_device_rect(rect).to_type<int>(), CornerClip::Outside);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -461,12 +466,17 @@ void PaintableBox::clear_clip_overflow_rect(PaintContext& context, PaintPhase ph
|
|||
|
||||
if (m_clipping_overflow) {
|
||||
m_clipping_overflow = false;
|
||||
if (corner_clip_radii().has_value()) {
|
||||
VERIFY(m_corner_clipper_id.has_value());
|
||||
auto corner_radii = corner_clip_radii()->as_corners(context);
|
||||
if (corner_radii.has_any_radius())
|
||||
context.recording_painter().blit_corner_clipping(*m_corner_clipper_id, context.rounded_device_rect(*clip_rect()).to_type<int>());
|
||||
m_corner_clipper_id = {};
|
||||
auto combined_transform = compute_combined_css_transform();
|
||||
auto const& border_radii_clips = this->border_radii_clips();
|
||||
for (size_t corner_clip_index = 0; corner_clip_index < border_radii_clips.size(); ++corner_clip_index) {
|
||||
auto const& corner_clip = border_radii_clips[corner_clip_index];
|
||||
auto corners = corner_clip.radii.as_corners(context);
|
||||
if (!corners.has_any_radius())
|
||||
continue;
|
||||
auto corner_clipper_id = m_corner_clipper_ids[corner_clip_index];
|
||||
m_corner_clipper_ids[corner_clip_index] = corner_clipper_id;
|
||||
auto rect = corner_clip.rect.translated(-combined_transform.translation().to_type<CSSPixels>());
|
||||
context.recording_painter().blit_corner_clipping(corner_clipper_id, context.rounded_device_rect(rect).to_type<int>());
|
||||
}
|
||||
context.recording_painter().restore();
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
#include <LibWeb/Painting/BorderPainting.h>
|
||||
#include <LibWeb/Painting/BorderRadiusCornerClipper.h>
|
||||
#include <LibWeb/Painting/ClipFrame.h>
|
||||
#include <LibWeb/Painting/Paintable.h>
|
||||
#include <LibWeb/Painting/PaintableFragment.h>
|
||||
#include <LibWeb/Painting/ShadowPainting.h>
|
||||
|
@ -19,11 +20,6 @@ struct ScrollFrame : public RefCounted<ScrollFrame> {
|
|||
CSSPixelPoint offset;
|
||||
};
|
||||
|
||||
struct ClipFrame : public RefCounted<ClipFrame> {
|
||||
CSSPixelRect rect;
|
||||
Optional<BorderRadiiData> corner_clip_radii;
|
||||
};
|
||||
|
||||
class PaintableBox : public Paintable {
|
||||
JS_CELL(PaintableBox, Paintable);
|
||||
|
||||
|
@ -210,7 +206,7 @@ public:
|
|||
Optional<int> scroll_frame_id() const;
|
||||
Optional<CSSPixelPoint> enclosing_scroll_frame_offset() const;
|
||||
Optional<CSSPixelRect> clip_rect() const;
|
||||
Optional<BorderRadiiData> corner_clip_radii() const;
|
||||
Span<BorderRadiiClip const> border_radii_clips() const;
|
||||
|
||||
protected:
|
||||
explicit PaintableBox(Layout::Box const&);
|
||||
|
@ -235,7 +231,7 @@ private:
|
|||
Optional<CSSPixelRect> mutable m_absolute_paint_rect;
|
||||
|
||||
mutable bool m_clipping_overflow { false };
|
||||
mutable Optional<u32> m_corner_clipper_id;
|
||||
mutable Vector<u32> m_corner_clipper_ids;
|
||||
|
||||
RefPtr<ScrollFrame const> m_enclosing_scroll_frame;
|
||||
RefPtr<ClipFrame const> m_enclosing_clip_frame;
|
||||
|
|
|
@ -149,21 +149,22 @@ void ViewportPaintable::refresh_clip_state()
|
|||
auto const& block_paintable_box = *block->paintable_box();
|
||||
auto block_overflow_x = block_paintable_box.computed_values().overflow_x();
|
||||
auto block_overflow_y = block_paintable_box.computed_values().overflow_y();
|
||||
if (block_overflow_x != CSS::Overflow::Visible && block_overflow_y != CSS::Overflow::Visible)
|
||||
overflow_clip_rect.intersect(block_paintable_box.compute_absolute_padding_rect_with_css_transform_applied());
|
||||
if (block_overflow_x != CSS::Overflow::Visible && block_overflow_y != CSS::Overflow::Visible) {
|
||||
auto rect = block_paintable_box.compute_absolute_padding_rect_with_css_transform_applied();
|
||||
overflow_clip_rect.intersect(rect);
|
||||
auto border_radii_data = block_paintable_box.normalized_border_radii_data(ShrinkRadiiForBorders::Yes);
|
||||
if (border_radii_data.has_any_radius()) {
|
||||
BorderRadiiClip border_radii_clip { .rect = rect, .radii = border_radii_data };
|
||||
clip_frame.add_border_radii_clip(border_radii_clip);
|
||||
}
|
||||
}
|
||||
if (auto css_clip_property_rect = block->paintable_box()->get_clip_rect(); css_clip_property_rect.has_value())
|
||||
overflow_clip_rect.intersect(css_clip_property_rect.value());
|
||||
}
|
||||
clip_rect = overflow_clip_rect;
|
||||
}
|
||||
|
||||
auto border_radii_data = paintable_box.normalized_border_radii_data(ShrinkRadiiForBorders::Yes);
|
||||
if (border_radii_data.has_any_radius()) {
|
||||
// FIXME: Border radii of all boxes in containing block chain should be taken into account.
|
||||
clip_frame.corner_clip_radii = border_radii_data;
|
||||
}
|
||||
|
||||
clip_frame.rect = *clip_rect;
|
||||
clip_frame.set_rect(*clip_rect);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue