ladybird/Libraries/LibWeb/Painting/DisplayListRecordingContext.h
Andreas Kling 7445cff5e8 LibWeb: Use retained data for hit testing
Build a hit-test display list while recording paint output. Use it as
source of truth for point hit testing instead of recursively walking the
paintable tree in reverse paint order.

The retained list records target paintables, visual context indices,
border radii, caret rects, and line metadata needed by hit testing. It
also keeps a spatial index so point queries inspect nearby items before
checking containment in paint order.

Refresh scroll state before hit testing so visual context transforms use
current scroll offsets. Add text tests for rounded hit regions and
selection across non-text content.
2026-05-30 13:50:48 +02:00

127 lines
5.5 KiB
C++

/*
* Copyright (c) 2018-2022, Andreas Kling <andreas@ladybird.org>
* Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibGfx/AffineTransform.h>
#include <LibGfx/Forward.h>
#include <LibGfx/Palette.h>
#include <LibGfx/Rect.h>
#include <LibWeb/Export.h>
#include <LibWeb/Forward.h>
#include <LibWeb/Painting/ChromeMetrics.h>
#include <LibWeb/Painting/DevicePixelConverter.h>
#include <LibWeb/PixelUnits.h>
namespace Web::Painting {
class HitTestDisplayList;
class ScrollState;
}
namespace Web {
class WEB_API DisplayListRecordingContext {
public:
DisplayListRecordingContext(Painting::DisplayListRecorder& painter, Palette const& palette, double device_pixels_per_css_pixel, ChromeMetrics const& chrome_metrics, Painting::HitTestDisplayList* = nullptr);
Painting::DisplayListRecorder& display_list_recorder() const { return m_display_list_recorder; }
Painting::HitTestDisplayList* hit_test_display_list() const { return m_hit_test_display_list; }
Palette const& palette() const { return m_palette; }
bool should_show_line_box_borders() const { return m_should_show_line_box_borders; }
void set_should_show_line_box_borders(bool value) { m_should_show_line_box_borders = value; }
bool should_paint_overlay() const { return m_should_paint_overlay; }
void set_should_paint_overlay(bool should_paint_overlay) { m_should_paint_overlay = should_paint_overlay; }
DevicePixelRect device_viewport_rect() const { return m_device_viewport_rect; }
void set_device_viewport_rect(DevicePixelRect const& rect) { m_device_viewport_rect = rect; }
void set_svg_transform(Gfx::AffineTransform transform)
{
m_svg_transform = transform;
}
Gfx::AffineTransform const& svg_transform() const
{
return m_svg_transform;
}
bool draw_svg_geometry_for_clip_path() const
{
return m_draw_svg_geometry_for_clip_path;
}
void set_draw_svg_geometry_for_clip_path(bool draw_svg_geometry_for_clip_path)
{
m_draw_svg_geometry_for_clip_path = draw_svg_geometry_for_clip_path;
}
DevicePixels enclosing_device_pixels(CSSPixels css_pixels) const;
DevicePixels floored_device_pixels(CSSPixels css_pixels) const;
DevicePixels rounded_device_pixels(CSSPixels css_pixels) const;
DevicePixelPoint rounded_device_point(CSSPixelPoint) const;
DevicePixelPoint floored_device_point(CSSPixelPoint) const;
DevicePixelRect enclosing_device_rect(CSSPixelRect) const;
DevicePixelRect rounded_device_rect(CSSPixelRect) const;
DevicePixelSize enclosing_device_size(CSSPixelSize) const;
DevicePixelSize rounded_device_size(CSSPixelSize) const;
DisplayListRecordingContext clone(Painting::DisplayListRecorder& painter) const
{
auto clone = DisplayListRecordingContext(painter, m_palette, m_device_pixel_converter.device_pixels_per_css_pixel(), m_chrome_metrics);
clone.m_device_viewport_rect = m_device_viewport_rect;
clone.m_should_show_line_box_borders = m_should_show_line_box_borders;
clone.m_should_paint_overlay = m_should_paint_overlay;
return clone;
}
Painting::DevicePixelConverter const& device_pixel_converter() const { return m_device_pixel_converter; }
double device_pixels_per_css_pixel() const { return m_device_pixel_converter.device_pixels_per_css_pixel(); }
ChromeMetrics const& chrome_metrics() const { return m_chrome_metrics; }
u64 paint_generation_id() const { return m_paint_generation_id; }
void set_async_scrolling_metadata_context(
UniqueNodeID document_id,
Painting::ScrollState const& scroll_state,
bool has_blocking_wheel_event_listeners,
bool has_blocking_wheel_event_region_covering_viewport)
{
m_async_scrolling_document_id = document_id;
m_async_scrolling_scroll_state = &scroll_state;
m_has_blocking_wheel_event_listeners = has_blocking_wheel_event_listeners;
m_has_blocking_wheel_event_region_covering_viewport = has_blocking_wheel_event_region_covering_viewport;
}
bool is_recording_async_scrolling_metadata() const { return m_async_scrolling_scroll_state != nullptr; }
UniqueNodeID async_scrolling_document_id() const { return m_async_scrolling_document_id; }
Painting::ScrollState const& async_scrolling_scroll_state() const { return *m_async_scrolling_scroll_state; }
bool has_blocking_wheel_event_listeners() const { return m_has_blocking_wheel_event_listeners; }
void set_has_blocking_wheel_event_listeners(bool value) { m_has_blocking_wheel_event_listeners = value; }
bool has_blocking_wheel_event_region_covering_viewport() const { return m_has_blocking_wheel_event_region_covering_viewport; }
private:
Painting::DisplayListRecorder& m_display_list_recorder;
Painting::HitTestDisplayList* m_hit_test_display_list { nullptr };
Palette m_palette;
Painting::DevicePixelConverter m_device_pixel_converter;
ChromeMetrics m_chrome_metrics;
DevicePixelRect m_device_viewport_rect;
bool m_should_show_line_box_borders { false };
bool m_should_paint_overlay { true };
bool m_draw_svg_geometry_for_clip_path { false };
Gfx::AffineTransform m_svg_transform;
u64 m_paint_generation_id { 0 };
UniqueNodeID m_async_scrolling_document_id {};
Painting::ScrollState const* m_async_scrolling_scroll_state { nullptr };
bool m_has_blocking_wheel_event_listeners { false };
bool m_has_blocking_wheel_event_region_covering_viewport { false };
};
}