mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 18:28:12 +00:00

With this change, clip rectangles for boxes with hidden overflow or the clip property are no longer calculated during the recording of painting commands. Instead, it has moved to the "pre-paint" phase, along with the assignment of scrolling offsets, and works in the following way: 1. The paintable tree is traversed to collect all paintable boxes that have hidden overflow or use the CSS clip property. For each of these boxes, the "final" clip rectangle is calculated by intersecting clip rectangles in the containing block chain for a box. 2. The paintable tree is traversed another time, and a clip rectangle is assigned for each paintable box contained by a node with hidden overflow or the clip property. This way, clipping becomes much easier during the painting commands recording phase, as it only concerns the use of already assigned clip rectangles. The same approach is applied to handle scrolling offsets. Also, clip rectangle calculation is now implemented more correctly, as we no longer stop at the stacking context boundary while intersecting clip rectangles in the containing block chain. Fixes: https://github.com/SerenityOS/serenity/issues/22932 https://github.com/SerenityOS/serenity/issues/22883 https://github.com/SerenityOS/serenity/issues/22679 https://github.com/SerenityOS/serenity/issues/22534
292 lines
11 KiB
C++
292 lines
11 KiB
C++
/*
|
|
* Copyright (c) 2022, Andreas Kling <kling@serenityos.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <LibWeb/Painting/BorderPainting.h>
|
|
#include <LibWeb/Painting/BorderRadiusCornerClipper.h>
|
|
#include <LibWeb/Painting/Paintable.h>
|
|
#include <LibWeb/Painting/PaintableFragment.h>
|
|
#include <LibWeb/Painting/ShadowPainting.h>
|
|
|
|
namespace Web::Painting {
|
|
|
|
class PaintableBox : public Paintable {
|
|
JS_CELL(PaintableBox, Paintable);
|
|
|
|
public:
|
|
static JS::NonnullGCPtr<PaintableBox> create(Layout::Box const&);
|
|
virtual ~PaintableBox();
|
|
|
|
virtual void before_paint(PaintContext&, PaintPhase) const override;
|
|
virtual void after_paint(PaintContext&, PaintPhase) const override;
|
|
|
|
virtual void paint(PaintContext&, PaintPhase) const override;
|
|
|
|
virtual Optional<CSSPixelRect> get_masking_area() const { return {}; }
|
|
virtual Optional<Gfx::Bitmap::MaskKind> get_mask_type() const { return {}; }
|
|
virtual RefPtr<Gfx::Bitmap> calculate_mask(PaintContext&, CSSPixelRect const&) const { return {}; }
|
|
|
|
Layout::Box& layout_box() { return static_cast<Layout::Box&>(Paintable::layout_node()); }
|
|
Layout::Box const& layout_box() const { return static_cast<Layout::Box const&>(Paintable::layout_node()); }
|
|
|
|
auto const& box_model() const { return layout_box().box_model(); }
|
|
|
|
struct OverflowData {
|
|
CSSPixelRect scrollable_overflow_rect;
|
|
bool has_scrollable_overflow { false };
|
|
CSSPixelPoint scroll_offset {};
|
|
};
|
|
|
|
CSSPixelRect absolute_rect() const;
|
|
|
|
// Offset from the top left of the containing block's content edge.
|
|
[[nodiscard]] CSSPixelPoint offset() const;
|
|
|
|
CSSPixelPoint scroll_offset() const;
|
|
void set_scroll_offset(CSSPixelPoint);
|
|
void scroll_by(int delta_x, int delta_y);
|
|
|
|
void set_offset(CSSPixelPoint);
|
|
void set_offset(float x, float y) { set_offset({ x, y }); }
|
|
|
|
CSSPixelSize const& content_size() const { return m_content_size; }
|
|
void set_content_size(CSSPixelSize);
|
|
void set_content_size(CSSPixels width, CSSPixels height) { set_content_size({ width, height }); }
|
|
|
|
void set_content_width(CSSPixels width) { set_content_size(width, content_height()); }
|
|
void set_content_height(CSSPixels height) { set_content_size(content_width(), height); }
|
|
CSSPixels content_width() const { return m_content_size.width(); }
|
|
CSSPixels content_height() const { return m_content_size.height(); }
|
|
|
|
CSSPixelRect absolute_padding_box_rect() const
|
|
{
|
|
auto absolute_rect = this->absolute_rect();
|
|
CSSPixelRect rect;
|
|
rect.set_x(absolute_rect.x() - box_model().padding.left);
|
|
rect.set_width(content_width() + box_model().padding.left + box_model().padding.right);
|
|
rect.set_y(absolute_rect.y() - box_model().padding.top);
|
|
rect.set_height(content_height() + box_model().padding.top + box_model().padding.bottom);
|
|
return rect;
|
|
}
|
|
|
|
CSSPixelRect absolute_border_box_rect() const
|
|
{
|
|
auto padded_rect = this->absolute_padding_box_rect();
|
|
CSSPixelRect rect;
|
|
auto use_collapsing_borders_model = override_borders_data().has_value();
|
|
// Implement the collapsing border model https://www.w3.org/TR/CSS22/tables.html#collapsing-borders.
|
|
auto border_top = use_collapsing_borders_model ? round(box_model().border.top / 2) : box_model().border.top;
|
|
auto border_bottom = use_collapsing_borders_model ? round(box_model().border.bottom / 2) : box_model().border.bottom;
|
|
auto border_left = use_collapsing_borders_model ? round(box_model().border.left / 2) : box_model().border.left;
|
|
auto border_right = use_collapsing_borders_model ? round(box_model().border.right / 2) : box_model().border.right;
|
|
rect.set_x(padded_rect.x() - border_left);
|
|
rect.set_width(padded_rect.width() + border_left + border_right);
|
|
rect.set_y(padded_rect.y() - border_top);
|
|
rect.set_height(padded_rect.height() + border_top + border_bottom);
|
|
return rect;
|
|
}
|
|
|
|
CSSPixelRect absolute_paint_rect() const;
|
|
|
|
CSSPixels border_box_width() const
|
|
{
|
|
auto border_box = box_model().border_box();
|
|
return content_width() + border_box.left + border_box.right;
|
|
}
|
|
|
|
CSSPixels border_box_height() const
|
|
{
|
|
auto border_box = box_model().border_box();
|
|
return content_height() + border_box.top + border_box.bottom;
|
|
}
|
|
|
|
CSSPixels absolute_x() const { return absolute_rect().x(); }
|
|
CSSPixels absolute_y() const { return absolute_rect().y(); }
|
|
CSSPixelPoint absolute_position() const { return absolute_rect().location(); }
|
|
|
|
[[nodiscard]] bool has_scrollable_overflow() const { return m_overflow_data->has_scrollable_overflow; }
|
|
|
|
[[nodiscard]] Optional<CSSPixelRect> scrollable_overflow_rect() const
|
|
{
|
|
if (!m_overflow_data.has_value())
|
|
return {};
|
|
return m_overflow_data->scrollable_overflow_rect;
|
|
}
|
|
|
|
void set_overflow_data(OverflowData data) { m_overflow_data = move(data); }
|
|
|
|
DOM::Node const* dom_node() const { return layout_box().dom_node(); }
|
|
DOM::Node* dom_node() { return layout_box().dom_node(); }
|
|
|
|
virtual void set_needs_display() const override;
|
|
|
|
virtual void apply_scroll_offset(PaintContext&, PaintPhase) const override;
|
|
virtual void reset_scroll_offset(PaintContext&, PaintPhase) const override;
|
|
|
|
virtual void apply_clip_overflow_rect(PaintContext&, PaintPhase) const override;
|
|
virtual void clear_clip_overflow_rect(PaintContext&, PaintPhase) const override;
|
|
|
|
virtual Optional<HitTestResult> hit_test(CSSPixelPoint, HitTestType) const override;
|
|
|
|
virtual bool handle_mousewheel(Badge<EventHandler>, CSSPixelPoint, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y) override;
|
|
|
|
enum class ConflictingElementKind {
|
|
Cell,
|
|
Row,
|
|
RowGroup,
|
|
Column,
|
|
ColumnGroup,
|
|
Table,
|
|
};
|
|
|
|
struct BorderDataWithElementKind {
|
|
CSS::BorderData border_data;
|
|
ConflictingElementKind element_kind;
|
|
};
|
|
|
|
struct BordersDataWithElementKind {
|
|
BorderDataWithElementKind top;
|
|
BorderDataWithElementKind right;
|
|
BorderDataWithElementKind bottom;
|
|
BorderDataWithElementKind left;
|
|
};
|
|
|
|
void set_override_borders_data(BordersDataWithElementKind const& override_borders_data) { m_override_borders_data = override_borders_data; }
|
|
Optional<BordersDataWithElementKind> const& override_borders_data() const { return m_override_borders_data; }
|
|
|
|
static BordersData remove_element_kind_from_borders_data(PaintableBox::BordersDataWithElementKind borders_data);
|
|
|
|
struct TableCellCoordinates {
|
|
size_t row_index;
|
|
size_t column_index;
|
|
size_t row_span;
|
|
size_t column_span;
|
|
};
|
|
|
|
void set_table_cell_coordinates(TableCellCoordinates const& table_cell_coordinates) { m_table_cell_coordinates = table_cell_coordinates; }
|
|
auto const& table_cell_coordinates() const { return m_table_cell_coordinates; }
|
|
|
|
enum class ShrinkRadiiForBorders {
|
|
Yes,
|
|
No
|
|
};
|
|
|
|
BorderRadiiData normalized_border_radii_data(ShrinkRadiiForBorders shrink = ShrinkRadiiForBorders::No) const;
|
|
|
|
BorderRadiiData const& border_radii_data() const { return m_border_radii_data; }
|
|
void set_border_radii_data(BorderRadiiData const& border_radii_data) { m_border_radii_data = border_radii_data; }
|
|
|
|
void set_box_shadow_data(Vector<ShadowData> box_shadow_data) { m_box_shadow_data = move(box_shadow_data); }
|
|
Vector<ShadowData> const& box_shadow_data() const { return m_box_shadow_data; }
|
|
|
|
void set_transform(Gfx::FloatMatrix4x4 transform) { m_transform = transform; }
|
|
Gfx::FloatMatrix4x4 const& transform() const { return m_transform; }
|
|
|
|
void set_transform_origin(CSSPixelPoint transform_origin) { m_transform_origin = transform_origin; }
|
|
CSSPixelPoint const& transform_origin() const { return m_transform_origin; }
|
|
|
|
CSSPixelRect compute_absolute_padding_rect_with_css_transform_applied() const;
|
|
|
|
Optional<CSSPixelRect> get_clip_rect() const;
|
|
|
|
void set_clip_rect(Optional<CSSPixelRect> rect) { m_clip_rect = rect; }
|
|
void set_scroll_frame_id(int id) { m_scroll_frame_id = id; }
|
|
void set_corner_clip_radii(CornerRadii const& corner_radii) { m_corner_clip_radii = corner_radii; }
|
|
|
|
protected:
|
|
explicit PaintableBox(Layout::Box const&);
|
|
|
|
virtual void paint_border(PaintContext&) const;
|
|
virtual void paint_backdrop_filter(PaintContext&) const;
|
|
virtual void paint_background(PaintContext&) const;
|
|
virtual void paint_box_shadow(PaintContext&) const;
|
|
|
|
virtual CSSPixelRect compute_absolute_rect() const;
|
|
virtual CSSPixelRect compute_absolute_paint_rect() const;
|
|
|
|
private:
|
|
[[nodiscard]] virtual bool is_paintable_box() const final { return true; }
|
|
|
|
Optional<OverflowData> m_overflow_data;
|
|
|
|
CSSPixelPoint m_offset;
|
|
CSSPixelSize m_content_size;
|
|
|
|
Optional<CSSPixelRect> mutable m_absolute_rect;
|
|
Optional<CSSPixelRect> mutable m_absolute_paint_rect;
|
|
|
|
mutable bool m_clipping_overflow { false };
|
|
mutable Optional<u32> m_corner_clipper_id;
|
|
|
|
Optional<CSSPixelRect> m_clip_rect;
|
|
Optional<int> m_scroll_frame_id;
|
|
Optional<CornerRadii> m_corner_clip_radii;
|
|
|
|
Optional<BordersDataWithElementKind> m_override_borders_data;
|
|
Optional<TableCellCoordinates> m_table_cell_coordinates;
|
|
|
|
BorderRadiiData m_border_radii_data;
|
|
Vector<ShadowData> m_box_shadow_data;
|
|
Gfx::FloatMatrix4x4 m_transform { Gfx::FloatMatrix4x4::identity() };
|
|
CSSPixelPoint m_transform_origin;
|
|
};
|
|
|
|
class PaintableWithLines : public PaintableBox {
|
|
JS_CELL(PaintableWithLines, PaintableBox);
|
|
|
|
public:
|
|
static JS::NonnullGCPtr<PaintableWithLines> create(Layout::BlockContainer const&);
|
|
virtual ~PaintableWithLines() override;
|
|
|
|
Layout::BlockContainer const& layout_box() const;
|
|
Layout::BlockContainer& layout_box();
|
|
|
|
Vector<PaintableFragment> const& fragments() const { return m_fragments; }
|
|
Vector<PaintableFragment>& fragments() { return m_fragments; }
|
|
|
|
void add_fragment(Layout::LineBoxFragment const& fragment)
|
|
{
|
|
m_fragments.append(PaintableFragment { fragment });
|
|
}
|
|
|
|
void set_fragments(Vector<PaintableFragment>&& fragments) { m_fragments = move(fragments); }
|
|
|
|
template<typename Callback>
|
|
void for_each_fragment(Callback callback) const
|
|
{
|
|
for (auto& fragment : m_fragments) {
|
|
if (callback(fragment) == IterationDecision::Break)
|
|
return;
|
|
}
|
|
}
|
|
|
|
virtual void paint(PaintContext&, PaintPhase) const override;
|
|
virtual bool wants_mouse_events() const override { return false; }
|
|
|
|
virtual Optional<HitTestResult> hit_test(CSSPixelPoint, HitTestType) const override;
|
|
|
|
virtual void visit_edges(Cell::Visitor& visitor) override
|
|
{
|
|
Base::visit_edges(visitor);
|
|
for (auto& fragment : m_fragments)
|
|
visitor.visit(JS::NonnullGCPtr { fragment.layout_node() });
|
|
}
|
|
|
|
protected:
|
|
PaintableWithLines(Layout::BlockContainer const&);
|
|
|
|
private:
|
|
[[nodiscard]] virtual bool is_paintable_with_lines() const final { return true; }
|
|
|
|
Vector<PaintableFragment> m_fragments;
|
|
};
|
|
|
|
void paint_text_decoration(PaintContext& context, Layout::Node const& text_node, PaintableFragment const& fragment);
|
|
void paint_cursor_if_needed(PaintContext& context, Layout::TextNode const& text_node, PaintableFragment const& fragment);
|
|
void paint_text_fragment(PaintContext& context, Layout::TextNode const& text_node, PaintableFragment const& fragment, PaintPhase phase);
|
|
|
|
}
|