LibWeb/Painting: Paint flexbox inspector overlays

This commit is contained in:
Sam Atkins 2026-05-27 17:07:50 +01:00
parent d465a5f32f
commit 5b2c649eda
5 changed files with 149 additions and 0 deletions

View file

@ -674,6 +674,8 @@ void Document::visit_edges(Cell::Visitor& visitor)
visitor.visit(m_hovered_node);
visitor.visit(m_inspected_node);
visitor.visit(m_highlighted_node);
for (auto const& flexbox_highlight : m_flexbox_highlights)
visitor.visit(flexbox_highlight.node);
for (auto const& grid_highlight : m_grid_highlights)
visitor.visit(grid_highlight.node);
visitor.visit(m_active_favicon);
@ -2671,6 +2673,44 @@ void Document::set_grid_highlighted_node(GC::Ptr<Node> node, Painting::GridInspe
node->set_needs_repaint();
}
void Document::set_flexbox_highlighted_node(GC::Ptr<Node> node, Painting::FlexboxInspectorOverlayOptions options)
{
if (!node)
return;
for (auto& flexbox_highlight : m_flexbox_highlights) {
if (flexbox_highlight.node != node)
continue;
flexbox_highlight.options = options;
node->set_needs_repaint();
return;
}
m_flexbox_highlights.append({ node, options });
node->set_needs_repaint();
}
void Document::clear_flexbox_highlighted_node(GC::Ptr<Node> node)
{
if (!node) {
for (auto const& flexbox_highlight : m_flexbox_highlights) {
if (flexbox_highlight.node)
flexbox_highlight.node->set_needs_repaint();
}
m_flexbox_highlights.clear();
return;
}
auto old_size = m_flexbox_highlights.size();
m_flexbox_highlights.remove_all_matching([&](auto const& flexbox_highlight) {
return flexbox_highlight.node == node;
});
if (m_flexbox_highlights.size() != old_size)
node->set_needs_repaint();
}
void Document::clear_grid_highlighted_node(GC::Ptr<Node> node)
{
if (!node) {
@ -8375,6 +8415,16 @@ RefPtr<Painting::DisplayList> Document::record_display_list(HTML::PaintConfig co
highlighted_node()->paintable()->paint_inspector_overlay(context);
}
for (auto const& flexbox_highlight : m_flexbox_highlights) {
if (!flexbox_highlight.node)
continue;
auto paintable = flexbox_highlight.node->paintable();
auto const* paintable_box = as_if<Painting::PaintableBox>(paintable.ptr());
if (!paintable_box)
continue;
paintable_box->paint_flexbox_inspector_overlay(context, flexbox_highlight.options);
}
for (auto const& grid_highlight : m_grid_highlights) {
if (!grid_highlight.node)
continue;

View file

@ -47,6 +47,7 @@
#include <LibWeb/HTML/SessionHistoryEntry.h>
#include <LibWeb/HTML/VisibilityState.h>
#include <LibWeb/InvalidateDisplayList.h>
#include <LibWeb/Painting/FlexboxInspectorOverlay.h>
#include <LibWeb/Painting/GridInspectorOverlay.h>
#include <LibWeb/ResizeObserver/ResizeObserver.h>
#include <LibWeb/TrustedTypes/InjectionSink.h>
@ -308,6 +309,8 @@ public:
GC::Ptr<Node const> highlighted_node() const { return m_highlighted_node; }
GC::Ptr<Layout::Node> highlighted_layout_node();
GC::Ptr<Layout::Node const> highlighted_layout_node() const { return const_cast<Document*>(this)->highlighted_layout_node(); }
void set_flexbox_highlighted_node(GC::Ptr<Node>, Painting::FlexboxInspectorOverlayOptions);
void clear_flexbox_highlighted_node(GC::Ptr<Node>);
void set_grid_highlighted_node(GC::Ptr<Node>, Painting::GridInspectorOverlayOptions);
void clear_grid_highlighted_node(GC::Ptr<Node>);
@ -1214,6 +1217,13 @@ private:
GC::Ptr<Node> node;
Painting::GridInspectorOverlayOptions options;
};
struct FlexboxHighlight {
GC::Ptr<Node> node;
Painting::FlexboxInspectorOverlayOptions options;
};
Vector<FlexboxHighlight> m_flexbox_highlights;
Vector<GridHighlight> m_grid_highlights;
Optional<Color> m_normal_link_color;

View file

@ -0,0 +1,17 @@
/*
* Copyright (c) 2026, Ladybird contributors
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGfx/Color.h>
namespace Web::Painting {
struct FlexboxInspectorOverlayOptions {
Gfx::Color color { 148, 0, 255 };
};
}

View file

@ -31,6 +31,7 @@
#include <LibWeb/Painting/ChromeMetrics.h>
#include <LibWeb/Painting/DisplayListRecorder.h>
#include <LibWeb/Painting/DisplayListRecordingContext.h>
#include <LibWeb/Painting/FlexboxInspectorOverlay.h>
#include <LibWeb/Painting/GridInspectorOverlay.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Painting/ResizeHandle.h>
@ -1320,6 +1321,75 @@ void PaintableBox::paint_grid_inspector_overlay(DisplayListRecordingContext& con
});
}
void PaintableBox::paint_flexbox_inspector_overlay(DisplayListRecordingContext& context, FlexboxInspectorOverlayOptions const& options) const
{
if (!m_flex_layout_data)
return;
paint_with_inspector_overlay_context(context, [&] {
auto content_rect = absolute_united_content_rect();
auto const origin = content_rect.location();
auto const viewport_rect = document().viewport_rect();
auto const& color = options.color;
auto line_color = color.with_alpha(220);
auto container_fill_color = color.with_alpha(28);
auto line_fill_color = color.with_alpha(18);
auto item_fill_color = color.with_alpha(32);
auto line_thickness = CSSPixels(1);
auto main_axis_is_horizontal = m_flex_layout_data->flex_direction == CSS::FlexDirection::Row
|| m_flex_layout_data->flex_direction == CSS::FlexDirection::RowReverse;
auto paint_rect = [&](CSSPixelRect const& rect, Gfx::Color rect_color) {
auto visible_rect = rect.intersected(viewport_rect);
if (visible_rect.is_empty())
return;
context.display_list_recorder().fill_rect(context.enclosing_device_rect(visible_rect).to_type<int>(), rect_color);
};
auto paint_outline = [&](CSSPixelRect const& rect, Gfx::Color rect_color) {
auto visible_rect = rect.intersected(viewport_rect);
if (visible_rect.is_empty())
return;
context.display_list_recorder().draw_rect(context.enclosing_device_rect(visible_rect).to_type<int>(), rect_color);
};
paint_rect(content_rect, container_fill_color);
paint_outline(content_rect, line_color);
for (auto const& line : m_flex_layout_data->lines) {
auto line_rect = main_axis_is_horizontal
? CSSPixelRect { content_rect.x(), origin.y() + line.cross_start, content_rect.width(), line.cross_size }
: CSSPixelRect { origin.x() + line.cross_start, content_rect.y(), line.cross_size, content_rect.height() };
paint_rect(line_rect, line_fill_color);
paint_outline(line_rect, line_color);
for (auto const& item : line.items) {
auto item_rect = item.rect.translated(origin);
paint_rect(item_rect, item_fill_color);
paint_outline(item_rect, line_color);
}
}
// Repaint the container border last so adjacent flex lines and items do not obscure it.
paint_outline(content_rect, line_color);
if (main_axis_is_horizontal) {
for (auto const& line : m_flex_layout_data->lines) {
auto y = origin.y() + line.cross_start;
paint_rect({ content_rect.x(), y, content_rect.width(), line_thickness }, line_color);
paint_rect({ content_rect.x(), y + line.cross_size, content_rect.width(), line_thickness }, line_color);
}
} else {
for (auto const& line : m_flex_layout_data->lines) {
auto x = origin.x() + line.cross_start;
paint_rect({ x, content_rect.y(), line_thickness, content_rect.height() }, line_color);
paint_rect({ x + line.cross_size, content_rect.y(), line_thickness, content_rect.height() }, line_color);
}
}
});
}
void PaintableBox::set_stacking_context(NonnullRefPtr<StackingContext> stacking_context)
{
m_stacking_context = move(stacking_context);

View file

@ -30,6 +30,7 @@
namespace Web::Painting {
struct FlexboxInspectorOverlayOptions;
struct GridInspectorOverlayOptions;
class ResizeHandle;
class Scrollbar;
@ -283,6 +284,7 @@ public:
Layout::GridLayoutData const* grid_layout_data() const { return m_grid_layout_data.ptr(); }
void set_flex_layout_data(OwnPtr<Layout::FlexLayoutData> flex_layout_data) { m_flex_layout_data = move(flex_layout_data); }
Layout::FlexLayoutData const* flex_layout_data() const { return m_flex_layout_data.ptr(); }
void paint_flexbox_inspector_overlay(DisplayListRecordingContext&, FlexboxInspectorOverlayOptions const&) const;
void paint_grid_inspector_overlay(DisplayListRecordingContext&, GridInspectorOverlayOptions const&) const;
void set_enclosing_scroll_frame_index(ScrollFrameIndex index) { m_enclosing_scroll_frame_index = index; }