ladybird/Libraries/LibWeb/Painting/Paintable.h

202 lines
7 KiB
C
Raw Normal View History

/*
* Copyright (c) 2022-2023, Andreas Kling <andreas@ladybird.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGC/Root.h>
#include <LibWeb/CSS/ComputedValues.h>
2025-07-19 19:35:33 -07:00
#include <LibWeb/Export.h>
#include <LibWeb/Forward.h>
#include <LibWeb/InvalidateDisplayList.h>
#include <LibWeb/PixelUnits.h>
#include <LibWeb/TraversalDecision.h>
#include <LibWeb/TreeNode.h>
namespace Web::Painting {
enum class PaintPhase {
Background,
Border,
TableCollapsedBorder,
Foreground,
Outline,
Overlay,
};
struct HitTestResult {
GC::Root<Paintable> paintable;
size_t index_in_node { 0 };
Optional<CSSPixels> vertical_distance {};
Optional<CSSPixels> horizontal_distance {};
enum InternalPosition {
None,
Before,
Inside,
After,
};
InternalPosition internal_position { None };
DOM::Node* dom_node();
DOM::Node const* dom_node() const;
};
enum class HitTestType {
Exact, // Exact matches only
TextCursor, // Clicking past the right/bottom edge of text will still hit the text
};
2025-07-19 19:35:33 -07:00
class WEB_API Paintable
: public JS::Cell
, public TreeNode<Paintable> {
GC_CELL(Paintable, JS::Cell);
public:
virtual ~Paintable();
void detach_from_layout_node();
[[nodiscard]] bool is_visible() const;
[[nodiscard]] bool is_positioned() const { return m_positioned; }
[[nodiscard]] bool is_fixed_position() const { return m_fixed_position; }
[[nodiscard]] bool is_sticky_position() const { return m_sticky_position; }
[[nodiscard]] bool is_absolutely_positioned() const { return m_absolutely_positioned; }
[[nodiscard]] bool is_floating() const { return m_floating; }
[[nodiscard]] bool is_inline() const { return m_inline; }
[[nodiscard]] CSS::Display display() const;
bool has_stacking_context() const;
StackingContext* enclosing_stacking_context();
virtual void before_paint(DisplayListRecordingContext&, PaintPhase) const { }
virtual void after_paint(DisplayListRecordingContext&, PaintPhase) const { }
virtual void paint(DisplayListRecordingContext&, PaintPhase) const { }
LibWeb: Paint inspector overlays as a separate pass The overlay shown for the node hovered in the inspector is painted as part of the normal tree traversal of all paintables. This works well in most cases, but falls short in specific scenarios: * If the hovered node or one of its ancestors establishes a stacking context and there is another element that establishes a stacking context close by or overlapping it, the overlay and especially the tooltip can become partially hidden behind the second element. Ditto for elements that act as if they established a stacking context. * If the hovered node or one of its ancestors involves clipping, the clip is applied to the overlay and espicially the tooltip. This can cause them to be partially invisible. * Similarly, if the hovered node or one of its ancestors has a defined mask, the mask is applied to the overlay, often making it mostly invisible. * No overlays are shown for SVG nodes because they are painted differently from HTML documents. Some of these problems may be fixable with the current system. But some seem like they fundamentally cannot work fully when the overlays are painted as part of the regular tree traversal. Instead we pull out painting the overlay as a separate pass executed after the tree traversal. This way we ensure that the overlays are always painted last and therefore on top of everything else. This also makes sure that the overlays are unaffected by clips and masks. And since overlay painting is independent from painting the actual elements, it just works as well. However we need to be careful, because we still need to apply some of the steps of the tree traversal to get the correct result. Namely we need to apply scroll offsets and transforms. To do so, we collect all ancestors of the hovered node and apply those as if we were in the normal tree traversal.
2025-09-02 22:57:41 +02:00
void paint_inspector_overlay(DisplayListRecordingContext&) const;
[[nodiscard]] virtual TraversalDecision hit_test(CSSPixelPoint, HitTestType, Function<TraversalDecision(HitTestResult)> const& callback) const;
virtual bool wants_mouse_events() const { return false; }
virtual bool forms_unconnected_subtree() const { return false; }
enum class DispatchEventOfSameName {
Yes,
No,
};
// When these methods return true, the DOM event with the same name will be
// dispatch at the mouse_event_target if it returns a valid DOM::Node, or
// the layout node's associated DOM node if it doesn't.
virtual DispatchEventOfSameName handle_mousedown(Badge<EventHandler>, CSSPixelPoint, unsigned button, unsigned modifiers);
virtual DispatchEventOfSameName handle_mouseup(Badge<EventHandler>, CSSPixelPoint, unsigned button, unsigned modifiers);
virtual DispatchEventOfSameName handle_mousemove(Badge<EventHandler>, CSSPixelPoint, unsigned buttons, unsigned modifiers);
virtual void handle_mouseleave(Badge<EventHandler>) { }
virtual bool handle_mousewheel(Badge<EventHandler>, CSSPixelPoint, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y);
Layout::Node const& layout_node() const { return m_layout_node; }
Layout::Node& layout_node() { return const_cast<Layout::Node&>(*m_layout_node); }
[[nodiscard]] GC::Ptr<DOM::Node> dom_node();
[[nodiscard]] GC::Ptr<DOM::Node const> dom_node() const;
void set_dom_node(GC::Ptr<DOM::Node>);
CSS::ImmutableComputedValues const& computed_values() const;
bool visible_for_hit_testing() const;
GC::Ptr<HTML::Navigable> navigable() const;
virtual void set_needs_display(InvalidateDisplayList = InvalidateDisplayList::Yes);
PaintableBox* containing_block() const;
template<typename T>
bool fast_is() const = delete;
[[nodiscard]] virtual bool is_navigable_container_viewport_paintable() const { return false; }
[[nodiscard]] virtual bool is_viewport_paintable() const { return false; }
[[nodiscard]] virtual bool is_paintable_box() const { return false; }
[[nodiscard]] virtual bool is_paintable_with_lines() const { return false; }
[[nodiscard]] virtual bool is_svg_paintable() const { return false; }
[[nodiscard]] virtual bool is_svg_svg_paintable() const { return false; }
[[nodiscard]] virtual bool is_svg_path_paintable() const { return false; }
[[nodiscard]] virtual bool is_svg_graphics_paintable() const { return false; }
2024-03-18 10:25:07 +01:00
[[nodiscard]] virtual bool is_text_paintable() const { return false; }
DOM::Document const& document() const;
DOM::Document& document();
CSSPixelPoint box_type_agnostic_position() const;
enum class SelectionState : u8 {
None, // No selection
Start, // Selection starts in this Node
End, // Selection ends in this Node
StartAndEnd, // Selection starts and ends in this Node
Full, // Selection starts before and ends after this Node
};
SelectionState selection_state() const { return m_selection_state; }
void set_selection_state(SelectionState state) { m_selection_state = state; }
Gfx::AffineTransform compute_combined_css_transform() const;
virtual void resolve_paint_properties() { }
virtual void finalize() override
{
if (m_list_node.is_in_list())
m_list_node.remove();
}
friend class Layout::Node;
protected:
explicit Paintable(Layout::Node const&);
LibWeb: Paint inspector overlays as a separate pass The overlay shown for the node hovered in the inspector is painted as part of the normal tree traversal of all paintables. This works well in most cases, but falls short in specific scenarios: * If the hovered node or one of its ancestors establishes a stacking context and there is another element that establishes a stacking context close by or overlapping it, the overlay and especially the tooltip can become partially hidden behind the second element. Ditto for elements that act as if they established a stacking context. * If the hovered node or one of its ancestors involves clipping, the clip is applied to the overlay and espicially the tooltip. This can cause them to be partially invisible. * Similarly, if the hovered node or one of its ancestors has a defined mask, the mask is applied to the overlay, often making it mostly invisible. * No overlays are shown for SVG nodes because they are painted differently from HTML documents. Some of these problems may be fixable with the current system. But some seem like they fundamentally cannot work fully when the overlays are painted as part of the regular tree traversal. Instead we pull out painting the overlay as a separate pass executed after the tree traversal. This way we ensure that the overlays are always painted last and therefore on top of everything else. This also makes sure that the overlays are unaffected by clips and masks. And since overlay painting is independent from painting the actual elements, it just works as well. However we need to be careful, because we still need to apply some of the steps of the tree traversal to get the correct result. Namely we need to apply scroll offsets and transforms. To do so, we collect all ancestors of the hovered node and apply those as if we were in the normal tree traversal.
2025-09-02 22:57:41 +02:00
virtual void paint_inspector_overlay_internal(DisplayListRecordingContext&) const { }
virtual void visit_edges(Cell::Visitor&) override;
private:
IntrusiveListNode<Paintable> m_list_node;
GC::Ptr<DOM::Node> m_dom_node;
GC::Ref<Layout::Node const> m_layout_node;
Optional<GC::Ptr<PaintableBox>> mutable m_containing_block;
SelectionState m_selection_state { SelectionState::None };
bool m_positioned : 1 { false };
bool m_fixed_position : 1 { false };
bool m_sticky_position : 1 { false };
bool m_absolutely_positioned : 1 { false };
bool m_floating : 1 { false };
bool m_inline : 1 { false };
};
inline DOM::Node* HitTestResult::dom_node()
{
return paintable->dom_node();
}
inline DOM::Node const* HitTestResult::dom_node() const
{
return paintable->dom_node();
}
template<>
inline bool Paintable::fast_is<PaintableBox>() const { return is_paintable_box(); }
template<>
inline bool Paintable::fast_is<PaintableWithLines>() const { return is_paintable_with_lines(); }
2024-03-18 10:25:07 +01:00
template<>
inline bool Paintable::fast_is<TextPaintable>() const { return is_text_paintable(); }
2025-07-19 19:35:33 -07:00
WEB_API Painting::BorderRadiiData normalize_border_radii_data(Layout::Node const& node, CSSPixelRect const& rect, CSS::BorderRadiusData const& top_left_radius, CSS::BorderRadiusData const& top_right_radius, CSS::BorderRadiusData const& bottom_right_radius, CSS::BorderRadiusData const& bottom_left_radius);
}