mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-10-23 00:19:18 +00:00
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.
This commit is contained in:
parent
870b7c79c3
commit
fbf47e57d0
Notes:
github-actions[bot]
2025-09-19 08:18:59 +00:00
Author: https://github.com/InvalidUsernameException
Commit: fbf47e57d0
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6056
Reviewed-by: https://github.com/gmta ✅
7 changed files with 104 additions and 41 deletions
|
@ -6541,6 +6541,10 @@ RefPtr<Painting::DisplayList> Document::record_display_list(HTML::PaintConfig co
|
||||||
|
|
||||||
viewport_paintable.paint_all_phases(context);
|
viewport_paintable.paint_all_phases(context);
|
||||||
|
|
||||||
|
if (highlighted_node() && highlighted_node()->paintable()) {
|
||||||
|
highlighted_node()->paintable()->paint_inspector_overlay(context);
|
||||||
|
}
|
||||||
|
|
||||||
m_cached_display_list = display_list;
|
m_cached_display_list = display_list;
|
||||||
m_cached_display_list_paint_config = config;
|
m_cached_display_list_paint_config = config;
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <LibWeb/DOM/Document.h>
|
#include <LibWeb/DOM/Document.h>
|
||||||
|
#include <LibWeb/Painting/DisplayListRecorder.h>
|
||||||
|
#include <LibWeb/Painting/DisplayListRecordingContext.h>
|
||||||
#include <LibWeb/Painting/Paintable.h>
|
#include <LibWeb/Painting/Paintable.h>
|
||||||
#include <LibWeb/Painting/PaintableBox.h>
|
#include <LibWeb/Painting/PaintableBox.h>
|
||||||
#include <LibWeb/Painting/StackingContext.h>
|
#include <LibWeb/Painting/StackingContext.h>
|
||||||
|
@ -156,6 +158,44 @@ StackingContext* Paintable::enclosing_stacking_context()
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Paintable::paint_inspector_overlay(DisplayListRecordingContext& context) const
|
||||||
|
{
|
||||||
|
Vector<Painting::Paintable const*> self_and_ancestors {};
|
||||||
|
for (Paintable const* paintable = this; paintable; paintable = paintable->parent()) {
|
||||||
|
self_and_ancestors.append(paintable);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto const* paintable : self_and_ancestors.in_reverse()) {
|
||||||
|
if (auto const* box = as_if<PaintableBox>(paintable)) {
|
||||||
|
box->apply_scroll_offset(context);
|
||||||
|
if (box->stacking_context()) {
|
||||||
|
auto to_device_pixels_scale = float(context.device_pixels_per_css_pixel());
|
||||||
|
auto transform_matrix = box->transform();
|
||||||
|
auto transform_origin = box->transform_origin().to_type<float>();
|
||||||
|
// We only want the transform here, everything else undesirable for the inspector overlay
|
||||||
|
DisplayListRecorder::PushStackingContextParams push_stacking_context_params {
|
||||||
|
.opacity = 1.0,
|
||||||
|
.compositing_and_blending_operator = Gfx::CompositingAndBlendingOperator::Normal,
|
||||||
|
.isolate = false,
|
||||||
|
.transform = StackingContextTransform(transform_origin, transform_matrix, to_device_pixels_scale),
|
||||||
|
};
|
||||||
|
context.display_list_recorder().push_stacking_context(push_stacking_context_params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paint_inspector_overlay_internal(context);
|
||||||
|
|
||||||
|
for (auto const* paintable : self_and_ancestors) {
|
||||||
|
if (auto const* box = as_if<PaintableBox>(paintable)) {
|
||||||
|
if (box->stacking_context()) {
|
||||||
|
context.display_list_recorder().pop_stacking_context();
|
||||||
|
}
|
||||||
|
box->reset_scroll_offset(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Paintable::set_needs_display(InvalidateDisplayList should_invalidate_display_list)
|
void Paintable::set_needs_display(InvalidateDisplayList should_invalidate_display_list)
|
||||||
{
|
{
|
||||||
auto& document = const_cast<DOM::Document&>(this->document());
|
auto& document = const_cast<DOM::Document&>(this->document());
|
||||||
|
|
|
@ -75,6 +75,7 @@ public:
|
||||||
virtual void after_paint(DisplayListRecordingContext&, PaintPhase) const { }
|
virtual void after_paint(DisplayListRecordingContext&, PaintPhase) const { }
|
||||||
|
|
||||||
virtual void paint(DisplayListRecordingContext&, PaintPhase) const { }
|
virtual void paint(DisplayListRecordingContext&, PaintPhase) const { }
|
||||||
|
void paint_inspector_overlay(DisplayListRecordingContext&) const;
|
||||||
|
|
||||||
[[nodiscard]] virtual TraversalDecision hit_test(CSSPixelPoint, HitTestType, Function<TraversalDecision(HitTestResult)> const& callback) const;
|
[[nodiscard]] virtual TraversalDecision hit_test(CSSPixelPoint, HitTestType, Function<TraversalDecision(HitTestResult)> const& callback) const;
|
||||||
|
|
||||||
|
@ -154,6 +155,7 @@ public:
|
||||||
protected:
|
protected:
|
||||||
explicit Paintable(Layout::Node const&);
|
explicit Paintable(Layout::Node const&);
|
||||||
|
|
||||||
|
virtual void paint_inspector_overlay_internal(DisplayListRecordingContext&) const { }
|
||||||
virtual void visit_edges(Cell::Visitor&) override;
|
virtual void visit_edges(Cell::Visitor&) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -502,51 +502,52 @@ void PaintableBox::paint(DisplayListRecordingContext& context, PaintPhase phase)
|
||||||
context.display_list_recorder().paint_scrollbar(own_scroll_frame_id().value(), gutter_rect, thumb_rect, scrollbar_data->scroll_length, scrollbar_colors.thumb_color, scrollbar_colors.track_color, false);
|
context.display_list_recorder().paint_scrollbar(own_scroll_frame_id().value(), gutter_rect, thumb_rect, scrollbar_data->scroll_length, scrollbar_colors.thumb_color, scrollbar_colors.track_color, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (phase == PaintPhase::Overlay && layout_node().document().highlighted_layout_node() == &layout_node_with_style_and_box_metrics()) {
|
void PaintableBox::paint_inspector_overlay_internal(DisplayListRecordingContext& context) const
|
||||||
auto content_rect = absolute_united_content_rect();
|
{
|
||||||
auto margin_rect = united_rect_for_continuation_chain(*this, [](PaintableBox const& box) {
|
auto content_rect = absolute_united_content_rect();
|
||||||
auto margin_box = box.box_model().margin_box();
|
auto margin_rect = united_rect_for_continuation_chain(*this, [](PaintableBox const& box) {
|
||||||
return CSSPixelRect {
|
auto margin_box = box.box_model().margin_box();
|
||||||
box.absolute_x() - margin_box.left,
|
return CSSPixelRect {
|
||||||
box.absolute_y() - margin_box.top,
|
box.absolute_x() - margin_box.left,
|
||||||
box.content_width() + margin_box.left + margin_box.right,
|
box.absolute_y() - margin_box.top,
|
||||||
box.content_height() + margin_box.top + margin_box.bottom,
|
box.content_width() + margin_box.left + margin_box.right,
|
||||||
};
|
box.content_height() + margin_box.top + margin_box.bottom,
|
||||||
});
|
|
||||||
auto border_rect = absolute_united_border_box_rect();
|
|
||||||
auto padding_rect = absolute_united_padding_box_rect();
|
|
||||||
|
|
||||||
auto paint_inspector_rect = [&](CSSPixelRect const& rect, Color color) {
|
|
||||||
auto device_rect = context.enclosing_device_rect(rect).to_type<int>();
|
|
||||||
context.display_list_recorder().fill_rect(device_rect, color.with_alpha(100));
|
|
||||||
context.display_list_recorder().draw_rect(device_rect, color);
|
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
auto border_rect = absolute_united_border_box_rect();
|
||||||
|
auto padding_rect = absolute_united_padding_box_rect();
|
||||||
|
|
||||||
paint_inspector_rect(margin_rect, Color::Yellow);
|
auto paint_inspector_rect = [&](CSSPixelRect const& rect, Color color) {
|
||||||
paint_inspector_rect(padding_rect, Color::Cyan);
|
auto device_rect = context.enclosing_device_rect(rect).to_type<int>();
|
||||||
paint_inspector_rect(border_rect, Color::Green);
|
context.display_list_recorder().fill_rect(device_rect, color.with_alpha(100));
|
||||||
paint_inspector_rect(content_rect, Color::Magenta);
|
context.display_list_recorder().draw_rect(device_rect, color);
|
||||||
|
};
|
||||||
|
|
||||||
auto font = Platform::FontPlugin::the().default_font(12);
|
paint_inspector_rect(margin_rect, Color::Yellow);
|
||||||
|
paint_inspector_rect(padding_rect, Color::Cyan);
|
||||||
|
paint_inspector_rect(border_rect, Color::Green);
|
||||||
|
paint_inspector_rect(content_rect, Color::Magenta);
|
||||||
|
|
||||||
StringBuilder builder;
|
auto font = Platform::FontPlugin::the().default_font(12);
|
||||||
if (layout_node_with_style_and_box_metrics().dom_node())
|
|
||||||
builder.append(layout_node_with_style_and_box_metrics().dom_node()->debug_description());
|
StringBuilder builder;
|
||||||
else
|
if (layout_node_with_style_and_box_metrics().dom_node())
|
||||||
builder.append(layout_node_with_style_and_box_metrics().debug_description());
|
builder.append(layout_node_with_style_and_box_metrics().dom_node()->debug_description());
|
||||||
builder.appendff(" {}x{} @ {},{}", border_rect.width(), border_rect.height(), border_rect.x(), border_rect.y());
|
else
|
||||||
auto size_text = MUST(builder.to_string());
|
builder.append(layout_node_with_style_and_box_metrics().debug_description());
|
||||||
auto size_text_rect = border_rect;
|
builder.appendff(" {}x{} @ {},{}", border_rect.width(), border_rect.height(), border_rect.x(), border_rect.y());
|
||||||
size_text_rect.set_y(border_rect.y() + border_rect.height());
|
auto size_text = MUST(builder.to_string());
|
||||||
size_text_rect.set_top(size_text_rect.top());
|
auto size_text_rect = border_rect;
|
||||||
size_text_rect.set_width(CSSPixels::nearest_value_for(font->width(size_text)) + 4);
|
size_text_rect.set_y(border_rect.y() + border_rect.height());
|
||||||
size_text_rect.set_height(CSSPixels::nearest_value_for(font->pixel_size()) + 4);
|
size_text_rect.set_top(size_text_rect.top());
|
||||||
auto size_text_device_rect = context.enclosing_device_rect(size_text_rect).to_type<int>();
|
size_text_rect.set_width(CSSPixels::nearest_value_for(font->width(size_text)) + 4);
|
||||||
context.display_list_recorder().fill_rect(size_text_device_rect, context.palette().color(Gfx::ColorRole::Tooltip));
|
size_text_rect.set_height(CSSPixels::nearest_value_for(font->pixel_size()) + 4);
|
||||||
context.display_list_recorder().draw_rect(size_text_device_rect, context.palette().threed_shadow1());
|
auto size_text_device_rect = context.enclosing_device_rect(size_text_rect).to_type<int>();
|
||||||
context.display_list_recorder().draw_text(size_text_device_rect, size_text, font->with_size(font->point_size() * context.device_pixels_per_css_pixel()), Gfx::TextAlignment::Center, context.palette().color(Gfx::ColorRole::TooltipText));
|
context.display_list_recorder().fill_rect(size_text_device_rect, context.palette().color(Gfx::ColorRole::Tooltip));
|
||||||
}
|
context.display_list_recorder().draw_rect(size_text_device_rect, context.palette().threed_shadow1());
|
||||||
|
context.display_list_recorder().draw_text(size_text_device_rect, size_text, font->with_size(font->point_size() * context.device_pixels_per_css_pixel()), Gfx::TextAlignment::Center, context.palette().color(Gfx::ColorRole::TooltipText));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PaintableBox::set_stacking_context(NonnullOwnPtr<StackingContext> stacking_context)
|
void PaintableBox::set_stacking_context(NonnullOwnPtr<StackingContext> stacking_context)
|
||||||
|
@ -976,7 +977,7 @@ void paint_text_fragment(DisplayListRecordingContext& context, TextPaintable con
|
||||||
auto fragment_absolute_rect = fragment.absolute_rect();
|
auto fragment_absolute_rect = fragment.absolute_rect();
|
||||||
auto fragment_enclosing_device_rect = context.enclosing_device_rect(fragment_absolute_rect).to_type<int>();
|
auto fragment_enclosing_device_rect = context.enclosing_device_rect(fragment_absolute_rect).to_type<int>();
|
||||||
|
|
||||||
if (paintable.document().highlighted_layout_node() == &paintable.layout_node() || context.should_show_line_box_borders())
|
if (context.should_show_line_box_borders())
|
||||||
paint_text_fragment_debug_highlight(context, fragment);
|
paint_text_fragment_debug_highlight(context, fragment);
|
||||||
|
|
||||||
auto glyph_run = fragment.glyph_run();
|
auto glyph_run = fragment.glyph_run();
|
||||||
|
|
|
@ -274,6 +274,8 @@ protected:
|
||||||
virtual void paint_background(DisplayListRecordingContext&) const;
|
virtual void paint_background(DisplayListRecordingContext&) const;
|
||||||
virtual void paint_box_shadow(DisplayListRecordingContext&) const;
|
virtual void paint_box_shadow(DisplayListRecordingContext&) const;
|
||||||
|
|
||||||
|
virtual void paint_inspector_overlay_internal(DisplayListRecordingContext&) const override;
|
||||||
|
|
||||||
virtual CSSPixelRect compute_absolute_rect() const;
|
virtual CSSPixelRect compute_absolute_rect() const;
|
||||||
virtual CSSPixelRect compute_absolute_paint_rect() const;
|
virtual CSSPixelRect compute_absolute_paint_rect() const;
|
||||||
|
|
||||||
|
|
|
@ -59,4 +59,15 @@ TextPaintable::DispatchEventOfSameName TextPaintable::handle_mousemove(Badge<Eve
|
||||||
return DispatchEventOfSameName::Yes;
|
return DispatchEventOfSameName::Yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextPaintable::paint_inspector_overlay_internal(DisplayListRecordingContext& context) const
|
||||||
|
{
|
||||||
|
if (auto const* parent_paintable = as_if<PaintableWithLines>(parent())) {
|
||||||
|
for (auto const& fragment : parent_paintable->fragments()) {
|
||||||
|
if (&fragment.paintable() == this) {
|
||||||
|
paint_text_fragment_debug_highlight(context, fragment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,9 @@ public:
|
||||||
virtual DispatchEventOfSameName handle_mouseup(Badge<EventHandler>, CSSPixelPoint, unsigned button, unsigned modifiers) override;
|
virtual DispatchEventOfSameName handle_mouseup(Badge<EventHandler>, CSSPixelPoint, unsigned button, unsigned modifiers) override;
|
||||||
virtual DispatchEventOfSameName handle_mousemove(Badge<EventHandler>, CSSPixelPoint, unsigned button, unsigned modifiers) override;
|
virtual DispatchEventOfSameName handle_mousemove(Badge<EventHandler>, CSSPixelPoint, unsigned button, unsigned modifiers) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void paint_inspector_overlay_internal(DisplayListRecordingContext&) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
virtual bool is_text_paintable() const override { return true; }
|
virtual bool is_text_paintable() const override { return true; }
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue