LibWeb: Implement resizing for eligible elements and update scrollbars
Add ElementResizeAction to Page (maybe there's a better place). It's just a mousemove delegate that updates styles on the target element. Add ChromeMetrics for zoom-invariant chrome like scrollbar thumb thickness, resize gripper size, paddings, etc. It's not user-stylable but separates basic concerns in a way that a visually gifted designer unlike myself can adjust to taste. These values are pre-divided by zoom factor so that PaintableBox can continue using device_pixels_per_css_pixel calls as normal. The adjusted metrics are computed on demand from Page multiple times per paint cycle, which is not ideal but avoids lifetime management and atomics. Maybe someone with more surety about the painting flow control can improve this, but it won't be a huge win. If profiling shows this slowing paints, then Ladybird is in good shape. Update PaintableBox to draw the resize gripper and deconflict the scrollbars. Set apropriate cursors for scrollbars and gripper in mousemove. We override EventHandler's cursor handling because nothing should ever come between a man and his resize gripper. Chrome metrics use the CSSPixels class. This is good because it's broadly compatible but bad because they're actually different units when zoom is not 1.0. If that's a problem, we could make a new type or just use double.
This commit is contained in:
parent
fc22c9ea38
commit
8f1cb4cbb0
18 changed files with 605 additions and 191 deletions
|
|
@ -334,6 +334,8 @@ public:
|
|||
ALWAYS_INLINE void set_primary_offset_for_orientation(Orientation orientation, T value) { m_location.set_primary_offset_for_orientation(orientation, value); }
|
||||
[[nodiscard]] ALWAYS_INLINE T secondary_offset_for_orientation(Orientation orientation) const { return m_location.secondary_offset_for_orientation(orientation); }
|
||||
ALWAYS_INLINE void set_secondary_offset_for_orientation(Orientation orientation, T value) { m_location.set_secondary_offset_for_orientation(orientation, value); }
|
||||
ALWAYS_INLINE void translate_primary_offset_for_orientation(Orientation orientation, T delta) { m_location.set_primary_offset_for_orientation(orientation, m_location.primary_offset_for_orientation(orientation) + delta); }
|
||||
ALWAYS_INLINE void translate_secondary_offset_for_orientation(Orientation orientation, T delta) { m_location.set_secondary_offset_for_orientation(orientation, m_location.secondary_offset_for_orientation(orientation) + delta); }
|
||||
|
||||
[[nodiscard]] ALWAYS_INLINE T primary_size_for_orientation(Orientation orientation) const { return m_size.primary_size_for_orientation(orientation); }
|
||||
[[nodiscard]] ALWAYS_INLINE T secondary_size_for_orientation(Orientation orientation) const { return m_size.secondary_size_for_orientation(orientation); }
|
||||
|
|
|
|||
|
|
@ -797,6 +797,7 @@ set(SOURCES
|
|||
NavigationTiming/PerformanceTiming.cpp
|
||||
NotificationsAPI/Notification.cpp
|
||||
Page/DragAndDropEventHandler.cpp
|
||||
Page/ElementResizeAction.cpp
|
||||
Page/EventHandler.cpp
|
||||
Page/InputEvent.cpp
|
||||
Page/Page.cpp
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ Optional<Gfx::ImageCursor> CursorStyleValue::make_image_cursor(Layout::NodeWithS
|
|||
// Paint the cursor into a bitmap.
|
||||
auto display_list = Painting::DisplayList::create(document.page().client().device_pixels_per_css_pixel());
|
||||
Painting::DisplayListRecorder display_list_recorder(display_list);
|
||||
DisplayListRecordingContext paint_context { display_list_recorder, document.page().palette(), document.page().client().device_pixels_per_css_pixel() };
|
||||
DisplayListRecordingContext paint_context { display_list_recorder, document.page().palette(), document.page().client().device_pixels_per_css_pixel(), document.page().chrome_metrics() };
|
||||
|
||||
image.resolve_for_size(layout_node, CSSPixelSize { bitmap.size() });
|
||||
image.paint(paint_context, DevicePixelRect { bitmap.rect() }, ImageRendering::Auto);
|
||||
|
|
|
|||
|
|
@ -6623,7 +6623,7 @@ RefPtr<Painting::DisplayList> Document::record_display_list(HTML::PaintConfig co
|
|||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
Web::DisplayListRecordingContext context(display_list_recorder, page().palette(), page().client().device_pixels_per_css_pixel());
|
||||
Web::DisplayListRecordingContext context(display_list_recorder, page().palette(), page().client().device_pixels_per_css_pixel(), page().chrome_metrics());
|
||||
context.set_device_viewport_rect(viewport_rect);
|
||||
context.set_should_show_line_box_borders(config.should_show_line_box_borders);
|
||||
context.set_should_paint_overlay(config.paint_overlay);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ namespace Web {
|
|||
class CSSPixels;
|
||||
class DisplayListRecordingContext;
|
||||
class DragAndDropEventHandler;
|
||||
class ElementResizeAction;
|
||||
class EventHandler;
|
||||
class InputEventsTarget;
|
||||
class LoadRequest;
|
||||
|
|
|
|||
104
Libraries/LibWeb/Page/ElementResizeAction.cpp
Normal file
104
Libraries/LibWeb/Page/ElementResizeAction.cpp
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Jonathan Gamble <gamblej@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/CSS/PropertyID.h>
|
||||
#include <LibWeb/DOM/Element.h>
|
||||
#include <LibWeb/Page/ElementResizeAction.h>
|
||||
#include <LibWeb/Painting/ChromeMetrics.h>
|
||||
#include <LibWeb/Painting/PaintableBox.h>
|
||||
|
||||
// https://drafts.csswg.org/css-ui#resize
|
||||
|
||||
namespace Web {
|
||||
|
||||
static Optional<CSSPixelSize> containing_block_padding_box_size(Layout::Node const& layout_node)
|
||||
{
|
||||
auto parent_box = layout_node.containing_block();
|
||||
if (!parent_box)
|
||||
return {};
|
||||
if (auto const* paintable_box = as_if<Painting::PaintableBox>(parent_box->first_paintable()))
|
||||
return paintable_box->absolute_padding_box_rect().size();
|
||||
return {};
|
||||
}
|
||||
|
||||
ElementResizeAction::ElementResizeAction(GC::Ref<DOM::Element> element, CSSPixelPoint pointer_down_origin)
|
||||
: m_element(element)
|
||||
, m_pointer_down_origin(pointer_down_origin)
|
||||
{
|
||||
auto const* paintable_box = m_element->paintable_box();
|
||||
if (paintable_box)
|
||||
m_initial_border_box_size = paintable_box->absolute_border_box_rect().size();
|
||||
}
|
||||
|
||||
void ElementResizeAction::handle_pointer_move(CSSPixelPoint pointer_position)
|
||||
{
|
||||
auto const* paintable_box = m_element->paintable_box();
|
||||
if (!paintable_box)
|
||||
return;
|
||||
auto const& layout_node = paintable_box->layout_node();
|
||||
auto const& computed = layout_node.computed_values();
|
||||
auto resize = computed.resize();
|
||||
if (resize == CSS::Resize::None)
|
||||
return;
|
||||
|
||||
bool horizontal_writing_mode = computed.writing_mode() == CSS::WritingMode::HorizontalTb;
|
||||
bool resize_x = computed.resize() == CSS::Resize::Both
|
||||
|| computed.resize() == CSS::Resize::Horizontal
|
||||
|| (computed.resize() == CSS::Resize::Inline && horizontal_writing_mode)
|
||||
|| (computed.resize() == CSS::Resize::Block && !horizontal_writing_mode);
|
||||
|
||||
bool resize_y = computed.resize() == CSS::Resize::Both
|
||||
|| computed.resize() == CSS::Resize::Vertical
|
||||
|| (computed.resize() == CSS::Resize::Inline && !horizontal_writing_mode)
|
||||
|| (computed.resize() == CSS::Resize::Block && horizontal_writing_mode);
|
||||
|
||||
CSSPixels dx = resize_x ? pointer_position.x() - m_pointer_down_origin.x() : 0;
|
||||
CSSPixels dy = resize_y ? pointer_position.y() - m_pointer_down_origin.y() : 0;
|
||||
auto writing_mode = computed.writing_mode();
|
||||
if ((writing_mode == CSS::WritingMode::HorizontalTb && computed.direction() == CSS::Direction::Rtl)
|
||||
|| writing_mode == CSS::WritingMode::VerticalRl
|
||||
|| writing_mode == CSS::WritingMode::SidewaysRl) {
|
||||
dx = -dx;
|
||||
}
|
||||
CSSPixels css_width = max(ChromeMetrics::ZOOM_INVARIANT_RESIZE_GRIPPER_SIZE, m_initial_border_box_size.width() + dx);
|
||||
CSSPixels css_height = max(ChromeMetrics::ZOOM_INVARIANT_RESIZE_GRIPPER_SIZE, m_initial_border_box_size.height() + dy);
|
||||
|
||||
auto reference_basis = containing_block_padding_box_size(layout_node);
|
||||
|
||||
if (reference_basis.has_value()) {
|
||||
if (auto const& min_width = computed.min_width(); !min_width.is_auto()) {
|
||||
css_width = max(css_width, min_width.to_px(layout_node, reference_basis->width()));
|
||||
}
|
||||
if (auto const& max_width = computed.max_width(); !max_width.is_none()) {
|
||||
css_width = min(css_width, max_width.to_px(layout_node, reference_basis->width()));
|
||||
}
|
||||
if (auto const& min_height = computed.min_height(); !min_height.is_auto()) {
|
||||
css_height = max(css_height, min_height.to_px(layout_node, reference_basis->height()));
|
||||
}
|
||||
if (auto const& max_height = computed.max_height(); !max_height.is_none()) {
|
||||
css_height = min(css_height, max_height.to_px(layout_node, reference_basis->height()));
|
||||
}
|
||||
}
|
||||
if (computed.box_sizing() == CSS::BoxSizing::ContentBox) {
|
||||
auto const& metrics = paintable_box->box_model();
|
||||
css_width -= metrics.padding.left + metrics.padding.right + computed.border_left().width + computed.border_right().width;
|
||||
css_height -= metrics.padding.top + metrics.padding.bottom + computed.border_top().width + computed.border_bottom().width;
|
||||
}
|
||||
|
||||
auto style = m_element->style_for_bindings();
|
||||
auto width_str = MUST(String::formatted("{:.2f}px", max(0.0, css_width.to_double())));
|
||||
auto height_str = MUST(String::formatted("{:.2f}px", max(0.0, css_height.to_double())));
|
||||
|
||||
MUST(style->set_property(CSS::PropertyID::Width, width_str));
|
||||
MUST(style->set_property(CSS::PropertyID::Height, height_str));
|
||||
}
|
||||
|
||||
void ElementResizeAction::visit_edges(GC::Cell::Visitor& visitor) const
|
||||
{
|
||||
visitor.visit(m_element);
|
||||
}
|
||||
|
||||
}
|
||||
31
Libraries/LibWeb/Page/ElementResizeAction.h
Normal file
31
Libraries/LibWeb/Page/ElementResizeAction.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Jonathan Gamble <gamblej@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibGC/Cell.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/PixelUnits.h>
|
||||
|
||||
// https://drafts.csswg.org/css-ui#resize
|
||||
|
||||
namespace Web {
|
||||
|
||||
class ElementResizeAction {
|
||||
public:
|
||||
ElementResizeAction(GC::Ref<DOM::Element> element, CSSPixelPoint pointer_down_origin);
|
||||
|
||||
void handle_pointer_move(CSSPixelPoint pointer_position);
|
||||
|
||||
void visit_edges(GC::Cell::Visitor&) const;
|
||||
|
||||
private:
|
||||
GC::Ref<DOM::Element> m_element;
|
||||
CSSPixelPoint m_pointer_down_origin;
|
||||
CSSPixelSize m_initial_border_box_size;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -29,6 +29,7 @@
|
|||
#include <LibWeb/Layout/Label.h>
|
||||
#include <LibWeb/Layout/Viewport.h>
|
||||
#include <LibWeb/Page/DragAndDropEventHandler.h>
|
||||
#include <LibWeb/Page/ElementResizeAction.h>
|
||||
#include <LibWeb/Page/EventHandler.h>
|
||||
#include <LibWeb/Page/Page.h>
|
||||
#include <LibWeb/Painting/PaintableBox.h>
|
||||
|
|
@ -103,70 +104,77 @@ static bool parent_element_for_event_dispatch(Painting::Paintable& paintable, GC
|
|||
return node && layout_node;
|
||||
}
|
||||
|
||||
static Gfx::Cursor css_to_gfx_cursor(CSS::CursorPredefined css_cursor)
|
||||
{
|
||||
switch (css_cursor) {
|
||||
case CSS::CursorPredefined::Crosshair:
|
||||
case CSS::CursorPredefined::Cell:
|
||||
return Gfx::StandardCursor::Crosshair;
|
||||
case CSS::CursorPredefined::Grab:
|
||||
return Gfx::StandardCursor::OpenHand;
|
||||
case CSS::CursorPredefined::Grabbing:
|
||||
return Gfx::StandardCursor::Drag;
|
||||
case CSS::CursorPredefined::Pointer:
|
||||
return Gfx::StandardCursor::Hand;
|
||||
case CSS::CursorPredefined::Help:
|
||||
return Gfx::StandardCursor::Help;
|
||||
case CSS::CursorPredefined::None:
|
||||
return Gfx::StandardCursor::Hidden;
|
||||
case CSS::CursorPredefined::NotAllowed:
|
||||
return Gfx::StandardCursor::Disallowed;
|
||||
case CSS::CursorPredefined::Text:
|
||||
case CSS::CursorPredefined::VerticalText:
|
||||
return Gfx::StandardCursor::IBeam;
|
||||
case CSS::CursorPredefined::Move:
|
||||
case CSS::CursorPredefined::AllScroll:
|
||||
return Gfx::StandardCursor::Move;
|
||||
case CSS::CursorPredefined::Progress:
|
||||
case CSS::CursorPredefined::Wait:
|
||||
return Gfx::StandardCursor::Wait;
|
||||
case CSS::CursorPredefined::ColResize:
|
||||
return Gfx::StandardCursor::ResizeColumn;
|
||||
case CSS::CursorPredefined::EResize:
|
||||
case CSS::CursorPredefined::WResize:
|
||||
case CSS::CursorPredefined::EwResize:
|
||||
return Gfx::StandardCursor::ResizeHorizontal;
|
||||
case CSS::CursorPredefined::RowResize:
|
||||
return Gfx::StandardCursor::ResizeRow;
|
||||
case CSS::CursorPredefined::NResize:
|
||||
case CSS::CursorPredefined::SResize:
|
||||
case CSS::CursorPredefined::NsResize:
|
||||
return Gfx::StandardCursor::ResizeVertical;
|
||||
case CSS::CursorPredefined::NeResize:
|
||||
case CSS::CursorPredefined::SwResize:
|
||||
case CSS::CursorPredefined::NeswResize:
|
||||
return Gfx::StandardCursor::ResizeDiagonalBLTR;
|
||||
case CSS::CursorPredefined::NwResize:
|
||||
case CSS::CursorPredefined::SeResize:
|
||||
case CSS::CursorPredefined::NwseResize:
|
||||
return Gfx::StandardCursor::ResizeDiagonalTLBR;
|
||||
case CSS::CursorPredefined::ZoomIn:
|
||||
case CSS::CursorPredefined::ZoomOut:
|
||||
return Gfx::StandardCursor::Zoom;
|
||||
case CSS::CursorPredefined::Default:
|
||||
return Gfx::StandardCursor::Arrow;
|
||||
case CSS::CursorPredefined::ContextMenu:
|
||||
case CSS::CursorPredefined::Alias:
|
||||
case CSS::CursorPredefined::Copy:
|
||||
case CSS::CursorPredefined::NoDrop:
|
||||
// FIXME: No corresponding GFX Standard Cursor, fallthrough to None
|
||||
case CSS::CursorPredefined::Auto:
|
||||
default:
|
||||
return Gfx::StandardCursor::None;
|
||||
}
|
||||
}
|
||||
|
||||
static Gfx::Cursor resolve_cursor(Layout::NodeWithStyle const& layout_node, Vector<CSS::CursorData> const& cursor_data, Gfx::StandardCursor auto_cursor)
|
||||
{
|
||||
for (auto const& cursor : cursor_data) {
|
||||
auto result = cursor.visit(
|
||||
[auto_cursor](CSS::CursorPredefined css_cursor) -> Optional<Gfx::Cursor> {
|
||||
switch (css_cursor) {
|
||||
case CSS::CursorPredefined::Crosshair:
|
||||
case CSS::CursorPredefined::Cell:
|
||||
return Gfx::StandardCursor::Crosshair;
|
||||
case CSS::CursorPredefined::Grab:
|
||||
return Gfx::StandardCursor::OpenHand;
|
||||
case CSS::CursorPredefined::Grabbing:
|
||||
return Gfx::StandardCursor::Drag;
|
||||
case CSS::CursorPredefined::Pointer:
|
||||
return Gfx::StandardCursor::Hand;
|
||||
case CSS::CursorPredefined::Help:
|
||||
return Gfx::StandardCursor::Help;
|
||||
case CSS::CursorPredefined::None:
|
||||
return Gfx::StandardCursor::Hidden;
|
||||
case CSS::CursorPredefined::NotAllowed:
|
||||
return Gfx::StandardCursor::Disallowed;
|
||||
case CSS::CursorPredefined::Text:
|
||||
case CSS::CursorPredefined::VerticalText:
|
||||
return Gfx::StandardCursor::IBeam;
|
||||
case CSS::CursorPredefined::Move:
|
||||
case CSS::CursorPredefined::AllScroll:
|
||||
return Gfx::StandardCursor::Move;
|
||||
case CSS::CursorPredefined::Progress:
|
||||
case CSS::CursorPredefined::Wait:
|
||||
return Gfx::StandardCursor::Wait;
|
||||
case CSS::CursorPredefined::ColResize:
|
||||
return Gfx::StandardCursor::ResizeColumn;
|
||||
case CSS::CursorPredefined::EResize:
|
||||
case CSS::CursorPredefined::WResize:
|
||||
case CSS::CursorPredefined::EwResize:
|
||||
return Gfx::StandardCursor::ResizeHorizontal;
|
||||
case CSS::CursorPredefined::RowResize:
|
||||
return Gfx::StandardCursor::ResizeRow;
|
||||
case CSS::CursorPredefined::NResize:
|
||||
case CSS::CursorPredefined::SResize:
|
||||
case CSS::CursorPredefined::NsResize:
|
||||
return Gfx::StandardCursor::ResizeVertical;
|
||||
case CSS::CursorPredefined::NeResize:
|
||||
case CSS::CursorPredefined::SwResize:
|
||||
case CSS::CursorPredefined::NeswResize:
|
||||
return Gfx::StandardCursor::ResizeDiagonalBLTR;
|
||||
case CSS::CursorPredefined::NwResize:
|
||||
case CSS::CursorPredefined::SeResize:
|
||||
case CSS::CursorPredefined::NwseResize:
|
||||
return Gfx::StandardCursor::ResizeDiagonalTLBR;
|
||||
case CSS::CursorPredefined::ZoomIn:
|
||||
case CSS::CursorPredefined::ZoomOut:
|
||||
return Gfx::StandardCursor::Zoom;
|
||||
case CSS::CursorPredefined::Auto:
|
||||
if (css_cursor == CSS::CursorPredefined::Auto)
|
||||
return auto_cursor;
|
||||
case CSS::CursorPredefined::ContextMenu:
|
||||
case CSS::CursorPredefined::Alias:
|
||||
case CSS::CursorPredefined::Copy:
|
||||
case CSS::CursorPredefined::NoDrop:
|
||||
// FIXME: No corresponding GFX Standard Cursor, fallthrough to None
|
||||
case CSS::CursorPredefined::Default:
|
||||
default:
|
||||
return Gfx::StandardCursor::None;
|
||||
}
|
||||
return css_to_gfx_cursor(css_cursor);
|
||||
},
|
||||
[&layout_node](NonnullRefPtr<CSS::CursorStyleValue const> const& cursor_style_value) -> Optional<Gfx::Cursor> {
|
||||
if (auto image_cursor = cursor_style_value->make_image_cursor(layout_node); image_cursor.has_value())
|
||||
|
|
@ -474,6 +482,12 @@ EventResult EventHandler::handle_mouseup(CSSPixelPoint visual_viewport_position,
|
|||
if (!paint_root())
|
||||
return EventResult::Dropped;
|
||||
|
||||
if (m_element_resize_in_progress) {
|
||||
set_mouse_event_tracking_paintable(nullptr);
|
||||
m_element_resize_in_progress = nullptr;
|
||||
return EventResult::Handled;
|
||||
}
|
||||
|
||||
GC::Ptr<Painting::Paintable> paintable;
|
||||
if (auto result = target_for_mouse_position(viewport_position); result.has_value())
|
||||
paintable = result->paintable;
|
||||
|
|
@ -771,6 +785,11 @@ EventResult EventHandler::handle_mousemove(CSSPixelPoint visual_viewport_positio
|
|||
if (!paint_root())
|
||||
return EventResult::Dropped;
|
||||
|
||||
if (m_element_resize_in_progress) {
|
||||
m_element_resize_in_progress->handle_pointer_move(viewport_position);
|
||||
return EventResult::Handled;
|
||||
}
|
||||
|
||||
bool hovered_node_changed = false;
|
||||
Gfx::Cursor hovered_node_cursor = Gfx::StandardCursor::None;
|
||||
GC::Ptr<HTML::HTMLAnchorElement const> hovered_link_element;
|
||||
|
|
@ -781,6 +800,8 @@ EventResult EventHandler::handle_mousemove(CSSPixelPoint visual_viewport_positio
|
|||
if (auto result = target_for_mouse_position(viewport_position); result.has_value()) {
|
||||
paintable = result->paintable;
|
||||
start_index = result->index_in_node;
|
||||
if (auto override = result->cursor_override; override.has_value())
|
||||
hovered_node_cursor = css_to_gfx_cursor(override.value());
|
||||
}
|
||||
|
||||
GC::Ptr<DOM::Node> node;
|
||||
|
|
@ -827,9 +848,6 @@ EventResult EventHandler::handle_mousemove(CSSPixelPoint visual_viewport_positio
|
|||
node = paintable->dom_node();
|
||||
return EventResult::Cancelled;
|
||||
}
|
||||
|
||||
// FIXME: It feels a bit aggressive to always update the cursor like this.
|
||||
page.client().page_did_request_cursor_change(Gfx::StandardCursor::None);
|
||||
}
|
||||
|
||||
node = dom_node_for_event_dispatch(*paintable);
|
||||
|
|
@ -854,11 +872,12 @@ EventResult EventHandler::handle_mousemove(CSSPixelPoint visual_viewport_positio
|
|||
|
||||
if (found_parent_element) {
|
||||
hovered_link_element = node->enclosing_link_element();
|
||||
|
||||
if (paintable->layout_node().is_text_node()) {
|
||||
hovered_node_cursor = resolve_cursor(*paintable->layout_node().parent(), cursor_data, Gfx::StandardCursor::IBeam);
|
||||
} else if (node->is_element()) {
|
||||
hovered_node_cursor = resolve_cursor(static_cast<Layout::NodeWithStyle&>(*layout_node), cursor_data, Gfx::StandardCursor::Arrow);
|
||||
if (hovered_node_cursor == Gfx::StandardCursor::None) {
|
||||
if (paintable->layout_node().is_text_node()) {
|
||||
hovered_node_cursor = resolve_cursor(*paintable->layout_node().parent(), cursor_data, Gfx::StandardCursor::IBeam);
|
||||
} else if (node->is_element()) {
|
||||
hovered_node_cursor = resolve_cursor(static_cast<Layout::NodeWithStyle&>(*layout_node), cursor_data, Gfx::StandardCursor::Arrow);
|
||||
}
|
||||
}
|
||||
|
||||
auto page_offset = compute_mouse_event_page_offset(viewport_position);
|
||||
|
|
@ -1568,6 +1587,11 @@ void EventHandler::set_mouse_event_tracking_paintable(GC::Ptr<Painting::Paintabl
|
|||
m_mouse_event_tracking_paintable = paintable;
|
||||
}
|
||||
|
||||
void EventHandler::set_element_resize_in_progress(DOM::Element& element, CSSPixelPoint viewport_position)
|
||||
{
|
||||
m_element_resize_in_progress = make<ElementResizeAction>(element, viewport_position);
|
||||
}
|
||||
|
||||
CSSPixelPoint EventHandler::compute_mouse_event_page_offset(CSSPixelPoint event_client_offset) const
|
||||
{
|
||||
// https://w3c.github.io/csswg-drafts/cssom-view/#dom-mouseevent-pagex
|
||||
|
|
@ -1600,13 +1624,13 @@ Optional<EventHandler::Target> EventHandler::target_for_mouse_position(CSSPixelP
|
|||
{
|
||||
if (m_mouse_event_tracking_paintable) {
|
||||
if (m_mouse_event_tracking_paintable->wants_mouse_events())
|
||||
return Target { m_mouse_event_tracking_paintable, {} };
|
||||
return Target { m_mouse_event_tracking_paintable, {}, {} };
|
||||
|
||||
m_mouse_event_tracking_paintable = nullptr;
|
||||
}
|
||||
|
||||
if (auto result = paint_root()->hit_test(position, Painting::HitTestType::Exact); result.has_value())
|
||||
return Target { result->paintable.ptr(), result->index_in_node };
|
||||
return Target { result->paintable.ptr(), result->index_in_node, result->cursor_override };
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
@ -1622,7 +1646,8 @@ void EventHandler::visit_edges(JS::Cell::Visitor& visitor) const
|
|||
{
|
||||
m_drag_and_drop_event_handler->visit_edges(visitor);
|
||||
visitor.visit(m_mouse_event_tracking_paintable);
|
||||
|
||||
if (m_element_resize_in_progress)
|
||||
m_element_resize_in_progress->visit_edges(visitor);
|
||||
if (m_mouse_selection_target)
|
||||
visitor.visit(m_mouse_selection_target->as_cell());
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <LibGfx/Forward.h>
|
||||
#include <LibJS/Heap/Cell.h>
|
||||
#include <LibUnicode/Forward.h>
|
||||
#include <LibWeb/CSS/ComputedValues.h>
|
||||
#include <LibWeb/Export.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/Gamepad/SDLGamepadForward.h>
|
||||
|
|
@ -43,6 +44,7 @@ public:
|
|||
EventResult handle_keyup(UIEvents::KeyCode, unsigned modifiers, u32 code_point, bool repeat);
|
||||
|
||||
void set_mouse_event_tracking_paintable(GC::Ptr<Painting::Paintable>);
|
||||
void set_element_resize_in_progress(DOM::Element& element, CSSPixelPoint viewport_position);
|
||||
|
||||
EventResult handle_paste(Utf16String const& text);
|
||||
|
||||
|
|
@ -65,6 +67,7 @@ private:
|
|||
struct Target {
|
||||
GC::Ptr<Painting::Paintable> paintable;
|
||||
Optional<int> index_in_node;
|
||||
Optional<CSS::CursorPredefined> cursor_override;
|
||||
};
|
||||
Optional<Target> target_for_mouse_position(CSSPixelPoint position);
|
||||
|
||||
|
|
@ -85,6 +88,7 @@ private:
|
|||
GC::Ptr<Painting::Paintable> m_mouse_event_tracking_paintable;
|
||||
|
||||
NonnullOwnPtr<DragAndDropEventHandler> m_drag_and_drop_event_handler;
|
||||
OwnPtr<ElementResizeAction> m_element_resize_in_progress;
|
||||
|
||||
GC::Weak<DOM::EventTarget> m_mousedown_target;
|
||||
|
||||
|
|
|
|||
|
|
@ -229,6 +229,11 @@ DevicePixelRect Page::rounded_device_rect(CSSPixelRect rect) const
|
|||
};
|
||||
}
|
||||
|
||||
ChromeMetrics Page::chrome_metrics() const
|
||||
{
|
||||
return ChromeMetrics { m_client->zoom_level() };
|
||||
}
|
||||
|
||||
EventResult Page::handle_mouseup(DevicePixelPoint position, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers)
|
||||
{
|
||||
return top_level_traversable()->event_handler().handle_mouseup(device_to_css_point(position), device_to_css_point(screen_position), button, buttons, modifiers);
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
#include <LibWeb/Loader/FileRequest.h>
|
||||
#include <LibWeb/Page/EventResult.h>
|
||||
#include <LibWeb/Page/InputEvent.h>
|
||||
#include <LibWeb/Painting/ChromeMetrics.h>
|
||||
#include <LibWeb/PixelUnits.h>
|
||||
#include <LibWeb/StorageAPI/StorageEndpoint.h>
|
||||
#include <LibWeb/UIEvents/KeyCode.h>
|
||||
|
|
@ -90,6 +91,7 @@ public:
|
|||
CSSPixelSize device_to_css_size(DevicePixelSize) const;
|
||||
DevicePixelRect enclosing_device_rect(CSSPixelRect) const;
|
||||
DevicePixelRect rounded_device_rect(CSSPixelRect) const;
|
||||
ChromeMetrics chrome_metrics() const;
|
||||
|
||||
EventResult handle_mouseup(DevicePixelPoint, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers);
|
||||
EventResult handle_mousedown(DevicePixelPoint, DevicePixelPoint screen_position, unsigned button, unsigned buttons, unsigned modifiers);
|
||||
|
|
|
|||
45
Libraries/LibWeb/Painting/ChromeMetrics.h
Normal file
45
Libraries/LibWeb/Painting/ChromeMetrics.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (c) 2025, Jonathan Gamble <gamblej@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <LibWeb/PixelUnits.h>
|
||||
|
||||
namespace Web {
|
||||
|
||||
struct ChromeMetrics {
|
||||
// Chrome sizing constants independent of page zoom.
|
||||
static constexpr CSSPixels ZOOM_INVARIANT_SCROLL_THUMB_MIN_LENGTH { 24 };
|
||||
static constexpr CSSPixels ZOOM_INVARIANT_SCROLL_THUMB_PADDING_THIN { 2 };
|
||||
static constexpr CSSPixels ZOOM_INVARIANT_SCROLL_THUMB_THICKNESS_THIN { 6 };
|
||||
static constexpr CSSPixels ZOOM_INVARIANT_SCROLL_THUMB_THICKNESS { 8 };
|
||||
static constexpr CSSPixels ZOOM_INVARIANT_SCROLL_GUTTER_THICKNESS { 12 };
|
||||
static constexpr CSSPixels ZOOM_INVARIANT_RESIZE_GRIPPER_SIZE { 12 };
|
||||
static constexpr CSSPixels ZOOM_INVARIANT_RESIZE_GRIPPER_PADDING { 2 };
|
||||
|
||||
explicit ChromeMetrics(double zoom_factor)
|
||||
{
|
||||
VERIFY(zoom_factor > 0);
|
||||
// So we can still use device_pixels_per_css_pixel transforms at paint time.
|
||||
CSSPixels const inverse_zoom { 1.0 / zoom_factor };
|
||||
scroll_thumb_min_length = ZOOM_INVARIANT_SCROLL_THUMB_MIN_LENGTH * inverse_zoom;
|
||||
scroll_thumb_padding_thin = ZOOM_INVARIANT_SCROLL_THUMB_PADDING_THIN * inverse_zoom;
|
||||
scroll_thumb_thickness_thin = ZOOM_INVARIANT_SCROLL_THUMB_THICKNESS_THIN * inverse_zoom;
|
||||
scroll_thumb_thickness = ZOOM_INVARIANT_SCROLL_THUMB_THICKNESS * inverse_zoom;
|
||||
scroll_gutter_thickness = ZOOM_INVARIANT_SCROLL_GUTTER_THICKNESS * inverse_zoom;
|
||||
resize_gripper_size = ZOOM_INVARIANT_RESIZE_GRIPPER_SIZE * inverse_zoom;
|
||||
resize_gripper_padding = ZOOM_INVARIANT_RESIZE_GRIPPER_PADDING * inverse_zoom;
|
||||
}
|
||||
CSSPixels scroll_thumb_min_length;
|
||||
CSSPixels scroll_thumb_padding_thin;
|
||||
CSSPixels scroll_thumb_thickness_thin;
|
||||
CSSPixels scroll_thumb_thickness;
|
||||
CSSPixels scroll_gutter_thickness;
|
||||
CSSPixels resize_gripper_size;
|
||||
CSSPixels resize_gripper_padding;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -11,10 +11,11 @@ namespace Web {
|
|||
|
||||
static u64 s_next_paint_generation_id = 0;
|
||||
|
||||
DisplayListRecordingContext::DisplayListRecordingContext(Painting::DisplayListRecorder& display_list_recorder, Palette const& palette, double device_pixels_per_css_pixel)
|
||||
DisplayListRecordingContext::DisplayListRecordingContext(Painting::DisplayListRecorder& display_list_recorder, Palette const& palette, double device_pixels_per_css_pixel, ChromeMetrics const& chrome_metrics)
|
||||
: m_display_list_recorder(display_list_recorder)
|
||||
, m_palette(palette)
|
||||
, m_device_pixel_converter(device_pixels_per_css_pixel)
|
||||
, m_chrome_metrics(chrome_metrics)
|
||||
, m_paint_generation_id(s_next_paint_generation_id++)
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#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>
|
||||
|
||||
|
|
@ -19,7 +20,7 @@ namespace Web {
|
|||
|
||||
class WEB_API DisplayListRecordingContext {
|
||||
public:
|
||||
DisplayListRecordingContext(Painting::DisplayListRecorder& painter, Palette const& palette, double device_pixels_per_css_pixel);
|
||||
DisplayListRecordingContext(Painting::DisplayListRecorder& painter, Palette const& palette, double device_pixels_per_css_pixel, ChromeMetrics const& chrome_metrics);
|
||||
|
||||
Painting::DisplayListRecorder& display_list_recorder() const { return m_display_list_recorder; }
|
||||
Palette const& palette() const { return m_palette; }
|
||||
|
|
@ -70,7 +71,7 @@ public:
|
|||
|
||||
DisplayListRecordingContext clone(Painting::DisplayListRecorder& painter) const
|
||||
{
|
||||
auto clone = DisplayListRecordingContext(painter, m_palette, m_device_pixel_converter.device_pixels_per_css_pixel());
|
||||
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;
|
||||
|
|
@ -79,13 +80,14 @@ public:
|
|||
|
||||
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; }
|
||||
|
||||
private:
|
||||
Painting::DisplayListRecorder& m_display_list_recorder;
|
||||
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 };
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ struct HitTestResult {
|
|||
size_t index_in_node { 0 };
|
||||
Optional<CSSPixels> vertical_distance {};
|
||||
Optional<CSSPixels> horizontal_distance {};
|
||||
|
||||
Optional<CSS::CursorPredefined> cursor_override {};
|
||||
enum InternalPosition {
|
||||
None,
|
||||
Before,
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
*/
|
||||
|
||||
#include <AK/GenericShorthands.h>
|
||||
#include <AK/TemporaryChange.h>
|
||||
#include <LibGfx/Font/Font.h>
|
||||
#include <LibGfx/ImmutableBitmap.h>
|
||||
#include <LibWeb/CSS/PropertyID.h>
|
||||
|
|
@ -17,6 +16,7 @@
|
|||
#include <LibWeb/HTML/Navigable.h>
|
||||
#include <LibWeb/Layout/InlineNode.h>
|
||||
#include <LibWeb/Painting/BackgroundPainting.h>
|
||||
#include <LibWeb/Painting/ChromeMetrics.h>
|
||||
#include <LibWeb/Painting/DisplayListRecorder.h>
|
||||
#include <LibWeb/Painting/PaintableBox.h>
|
||||
#include <LibWeb/Painting/SVGPaintable.h>
|
||||
|
|
@ -30,7 +30,18 @@
|
|||
|
||||
namespace Web::Painting {
|
||||
|
||||
bool g_paint_viewport_scrollbars = true;
|
||||
static bool g_paint_viewport_scrollbars = true;
|
||||
|
||||
namespace {
|
||||
|
||||
struct PhysicalResizeAxes {
|
||||
bool horizontal;
|
||||
bool vertical;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
static PhysicalResizeAxes compute_physical_resize_axes(CSS::ComputedValues const& computed);
|
||||
|
||||
void set_paint_viewport_scrollbars(bool const enabled)
|
||||
{
|
||||
|
|
@ -205,6 +216,16 @@ CSSPixelRect PaintableBox::absolute_padding_box_rect() const
|
|||
return rect;
|
||||
}
|
||||
|
||||
Optional<CSSPixelRect> PaintableBox::absolute_resizer_rect(ChromeMetrics const& metrics) const
|
||||
{
|
||||
if (!has_resizer())
|
||||
return {};
|
||||
auto padding_rect = absolute_padding_box_rect();
|
||||
CSSPixels x = is_chrome_mirrored() ? padding_rect.x() : padding_rect.right() - metrics.resize_gripper_size;
|
||||
CSSPixels y = padding_rect.bottom() - metrics.resize_gripper_size;
|
||||
return CSSPixelRect { x, y, metrics.resize_gripper_size, metrics.resize_gripper_size };
|
||||
}
|
||||
|
||||
CSSPixelRect PaintableBox::absolute_border_box_rect() const
|
||||
{
|
||||
auto padded_rect = this->absolute_padding_box_rect();
|
||||
|
|
@ -293,11 +314,7 @@ Optional<CSSPixelRect> PaintableBox::get_clip_rect() const
|
|||
|
||||
bool PaintableBox::wants_mouse_events() const
|
||||
{
|
||||
if (compute_scrollbar_data(ScrollDirection::Vertical).has_value())
|
||||
return true;
|
||||
if (compute_scrollbar_data(ScrollDirection::Horizontal).has_value())
|
||||
return true;
|
||||
return false;
|
||||
return (m_own_scroll_frame && could_be_scrolled_by_wheel_event()) || has_resizer();
|
||||
}
|
||||
|
||||
void PaintableBox::before_paint(DisplayListRecordingContext& context, PaintPhase phase) const
|
||||
|
|
@ -309,7 +326,7 @@ void PaintableBox::before_paint(DisplayListRecordingContext& context, PaintPhase
|
|||
bool apply_own_clip_frame = [&] {
|
||||
if (phase == PaintPhase::Background)
|
||||
return own_clip_frame && own_clip_frame->includes_rect_from_clip_property;
|
||||
if (phase == PaintPhase::Foreground)
|
||||
if (phase == PaintPhase::Foreground || phase == PaintPhase::Overlay)
|
||||
return !own_clip_frame.is_null();
|
||||
return false;
|
||||
}();
|
||||
|
|
@ -333,7 +350,7 @@ void PaintableBox::after_paint(DisplayListRecordingContext& context, PaintPhase
|
|||
bool reset_own_clip_frame = [&] {
|
||||
if (phase == PaintPhase::Background)
|
||||
return own_clip_frame && own_clip_frame->includes_rect_from_clip_property;
|
||||
if (phase == PaintPhase::Foreground)
|
||||
if (phase == PaintPhase::Foreground || phase == PaintPhase::Overlay)
|
||||
return !own_clip_frame.is_null();
|
||||
return false;
|
||||
}();
|
||||
|
|
@ -346,15 +363,21 @@ void PaintableBox::after_paint(DisplayListRecordingContext& context, PaintPhase
|
|||
|
||||
bool PaintableBox::could_be_scrolled_by_wheel_event(ScrollDirection direction) const
|
||||
{
|
||||
auto overflow = direction == ScrollDirection::Horizontal ? computed_values().overflow_x() : computed_values().overflow_y();
|
||||
bool is_horizontal = direction == ScrollDirection::Horizontal;
|
||||
Gfx::Orientation orientation = is_horizontal ? Gfx::Orientation::Horizontal : Gfx::Orientation::Vertical;
|
||||
auto overflow = is_horizontal ? computed_values().overflow_x() : computed_values().overflow_y();
|
||||
|
||||
auto scrollable_overflow_rect = this->scrollable_overflow_rect();
|
||||
if (!scrollable_overflow_rect.has_value())
|
||||
return false;
|
||||
auto scrollable_overflow_size = direction == ScrollDirection::Horizontal ? scrollable_overflow_rect->width() : scrollable_overflow_rect->height();
|
||||
auto scrollport_size = direction == ScrollDirection::Horizontal ? absolute_padding_box_rect().width() : absolute_padding_box_rect().height();
|
||||
auto overflow_value_allows_scrolling = overflow == CSS::Overflow::Auto || overflow == CSS::Overflow::Scroll;
|
||||
|
||||
CSSPixels scrollable_overflow_size = scrollable_overflow_rect->primary_size_for_orientation(orientation);
|
||||
CSSPixels scrollport_size = absolute_padding_box_rect().primary_size_for_orientation(orientation);
|
||||
|
||||
bool overflow_value_allows_scrolling = overflow == CSS::Overflow::Auto || overflow == CSS::Overflow::Scroll;
|
||||
if ((is_viewport_paintable() && overflow != CSS::Overflow::Hidden) || overflow_value_allows_scrolling)
|
||||
return scrollable_overflow_size > scrollport_size;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -363,67 +386,109 @@ bool PaintableBox::could_be_scrolled_by_wheel_event() const
|
|||
return could_be_scrolled_by_wheel_event(ScrollDirection::Horizontal) || could_be_scrolled_by_wheel_event(ScrollDirection::Vertical);
|
||||
}
|
||||
|
||||
static constexpr CSSPixels SCROLLBAR_THUMB_NORMAL_THICKNESS = 5;
|
||||
static constexpr CSSPixels SCROLLBAR_THUMB_WIDENED_THICKNESS = 10;
|
||||
CSSPixels PaintableBox::available_scrollbar_length(ScrollDirection direction, ChromeMetrics const& metrics) const
|
||||
|
||||
Optional<PaintableBox::ScrollbarData> PaintableBox::compute_scrollbar_data(ScrollDirection direction, AdjustThumbRectForScrollOffset adjust_thumb_rect_for_scroll_offset) const
|
||||
{
|
||||
bool is_horizontal = direction == ScrollDirection::Horizontal;
|
||||
bool display_scrollbar = could_be_scrolled_by_wheel_event(direction);
|
||||
if (is_horizontal) {
|
||||
display_scrollbar |= computed_values().overflow_x() == CSS::Overflow::Scroll;
|
||||
} else {
|
||||
display_scrollbar |= computed_values().overflow_y() == CSS::Overflow::Scroll;
|
||||
}
|
||||
if (!display_scrollbar) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!own_scroll_frame_id().has_value()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto padding_rect = absolute_padding_box_rect();
|
||||
auto scrollable_overflow_rect = this->scrollable_overflow_rect().value();
|
||||
auto scroll_overflow_size = is_horizontal ? scrollable_overflow_rect.width() : scrollable_overflow_rect.height();
|
||||
auto scrollport_size = is_horizontal ? padding_rect.width() : padding_rect.height();
|
||||
if (scroll_overflow_size == 0)
|
||||
CSSPixels full_scrollport_length = is_horizontal ? padding_rect.width() : padding_rect.height();
|
||||
if (has_resizer())
|
||||
full_scrollport_length -= metrics.resize_gripper_size;
|
||||
else {
|
||||
if (is_horizontal && could_be_scrolled_by_wheel_event(ScrollDirection::Vertical))
|
||||
full_scrollport_length -= metrics.scroll_gutter_thickness;
|
||||
if (!is_horizontal && could_be_scrolled_by_wheel_event(ScrollDirection::Horizontal))
|
||||
full_scrollport_length -= metrics.scroll_gutter_thickness;
|
||||
}
|
||||
return full_scrollport_length;
|
||||
}
|
||||
|
||||
Optional<CSSPixelRect> PaintableBox::absolute_scrollbar_rect(ScrollDirection direction, bool with_gutter, ChromeMetrics const& metrics) const
|
||||
{
|
||||
if (!could_be_scrolled_by_wheel_event(direction))
|
||||
return {};
|
||||
|
||||
auto thickness = [&]() {
|
||||
if (is_horizontal)
|
||||
return m_draw_enlarged_horizontal_scrollbar ? SCROLLBAR_THUMB_WIDENED_THICKNESS : SCROLLBAR_THUMB_NORMAL_THICKNESS;
|
||||
return m_draw_enlarged_vertical_scrollbar ? SCROLLBAR_THUMB_WIDENED_THICKNESS : SCROLLBAR_THUMB_NORMAL_THICKNESS;
|
||||
}();
|
||||
bool is_horizontal = direction == ScrollDirection::Horizontal;
|
||||
bool adjusting_for_resizer = has_resizer();
|
||||
|
||||
auto scrollbar_rect_length = is_horizontal ? scrollport_size - thickness : scrollport_size;
|
||||
|
||||
auto min_thumb_length = min(scrollbar_rect_length, 24);
|
||||
auto thumb_length = max(scrollbar_rect_length * (scrollport_size / scroll_overflow_size), min_thumb_length);
|
||||
|
||||
ScrollbarData scrollbar_data;
|
||||
|
||||
if (scroll_overflow_size > scrollport_size)
|
||||
scrollbar_data.scroll_length = (scrollbar_rect_length - thumb_length) / (scroll_overflow_size - scrollport_size);
|
||||
CSSPixels rect_thickness = with_gutter
|
||||
? metrics.scroll_gutter_thickness
|
||||
: metrics.scroll_thumb_thickness_thin + metrics.scroll_thumb_padding_thin;
|
||||
CSSPixelRect scrollbar_rect = absolute_padding_box_rect();
|
||||
|
||||
if (is_horizontal) {
|
||||
if (m_draw_enlarged_horizontal_scrollbar)
|
||||
scrollbar_data.gutter_rect = { padding_rect.left(), padding_rect.bottom() - thickness, padding_rect.width(), thickness };
|
||||
scrollbar_data.thumb_rect = { padding_rect.left(), padding_rect.bottom() - thickness, thumb_length, thickness };
|
||||
if (!adjusting_for_resizer && could_be_scrolled_by_wheel_event(ScrollDirection::Vertical)) {
|
||||
scrollbar_rect.set_width(max(CSSPixels { 0 }, scrollbar_rect.width() - metrics.scroll_gutter_thickness));
|
||||
if (is_chrome_mirrored())
|
||||
scrollbar_rect.set_x(scrollbar_rect.x() + metrics.scroll_gutter_thickness);
|
||||
} else if (adjusting_for_resizer) {
|
||||
scrollbar_rect.set_width(available_scrollbar_length(ScrollDirection::Horizontal, metrics));
|
||||
if (is_chrome_mirrored())
|
||||
scrollbar_rect.set_x(scrollbar_rect.x() + metrics.resize_gripper_size);
|
||||
}
|
||||
scrollbar_rect.set_y(max(CSSPixels { 0 }, scrollbar_rect.bottom() - rect_thickness));
|
||||
scrollbar_rect.set_height(rect_thickness);
|
||||
} else {
|
||||
if (m_draw_enlarged_vertical_scrollbar)
|
||||
scrollbar_data.gutter_rect = { padding_rect.right() - thickness, padding_rect.top(), thickness, padding_rect.height() };
|
||||
scrollbar_data.thumb_rect = { padding_rect.right() - thickness, padding_rect.top(), thickness, thumb_length };
|
||||
if (adjusting_for_resizer)
|
||||
scrollbar_rect.set_height(available_scrollbar_length(ScrollDirection::Vertical, metrics));
|
||||
if (!is_chrome_mirrored())
|
||||
scrollbar_rect.set_x(max(CSSPixels { 0 }, scrollbar_rect.right() - rect_thickness));
|
||||
scrollbar_rect.set_width(rect_thickness);
|
||||
}
|
||||
return scrollbar_rect;
|
||||
}
|
||||
|
||||
Optional<PaintableBox::ScrollbarData> PaintableBox::compute_scrollbar_data(ScrollDirection direction, ChromeMetrics const& metrics, AdjustThumbRectForScrollOffset adjust_thumb_rect_for_scroll_offset) const
|
||||
{
|
||||
bool is_horizontal = direction == ScrollDirection::Horizontal;
|
||||
auto orientation = is_horizontal ? Gfx::Orientation::Horizontal : Gfx::Orientation::Vertical;
|
||||
auto overflow = is_horizontal ? computed_values().overflow_x() : computed_values().overflow_y();
|
||||
|
||||
if (overflow != CSS::Overflow::Scroll && !could_be_scrolled_by_wheel_event(direction))
|
||||
return {};
|
||||
|
||||
if (!own_scroll_frame_id().has_value())
|
||||
return {};
|
||||
|
||||
CSSPixelRect scrollable_overflow_rect = this->scrollable_overflow_rect().value();
|
||||
CSSPixels scrollable_overflow_length = scrollable_overflow_rect.primary_size_for_orientation(orientation);
|
||||
if (scrollable_overflow_length == 0)
|
||||
return {};
|
||||
|
||||
bool with_gutter = is_horizontal ? m_draw_enlarged_horizontal_scrollbar : m_draw_enlarged_vertical_scrollbar;
|
||||
auto scrollbar_rect = absolute_scrollbar_rect(direction, with_gutter, metrics);
|
||||
if (!scrollbar_rect.has_value())
|
||||
return {};
|
||||
|
||||
CSSPixels thumb_thickness = metrics.scroll_thumb_thickness_thin;
|
||||
CSSPixels thumb_margin = metrics.scroll_thumb_padding_thin;
|
||||
if (with_gutter) {
|
||||
thumb_thickness = metrics.scroll_thumb_thickness;
|
||||
thumb_margin = CSSPixels { (metrics.scroll_gutter_thickness - metrics.scroll_thumb_thickness) / 2.0 };
|
||||
}
|
||||
CSSPixels scrollbar_length = scrollbar_rect->primary_size_for_orientation(orientation);
|
||||
CSSPixels usable_scrollbar_length = max(CSSPixels { 0 }, scrollbar_length - (2 * thumb_margin));
|
||||
CSSPixels scrollport_size = absolute_padding_box_rect().primary_size_for_orientation(orientation);
|
||||
CSSPixels min_thumb_length = min(usable_scrollbar_length, metrics.scroll_thumb_min_length);
|
||||
CSSPixels thumb_length = max(usable_scrollbar_length * (scrollport_size / scrollable_overflow_length), min_thumb_length);
|
||||
|
||||
ScrollbarData scrollbar_data = { .gutter_rect = {}, .thumb_rect = scrollbar_rect.value(), .thumb_travel_to_scroll_ratio = 0 };
|
||||
|
||||
scrollbar_data.thumb_rect.set_primary_size_for_orientation(orientation, thumb_length);
|
||||
scrollbar_data.thumb_rect.set_secondary_size_for_orientation(orientation, thumb_thickness);
|
||||
scrollbar_data.thumb_rect.translate_primary_offset_for_orientation(orientation, thumb_margin);
|
||||
if (with_gutter || (!is_horizontal && is_chrome_mirrored()))
|
||||
scrollbar_data.thumb_rect.translate_secondary_offset_for_orientation(orientation, thumb_margin);
|
||||
if (with_gutter)
|
||||
scrollbar_data.gutter_rect = scrollbar_rect.value();
|
||||
if (scrollable_overflow_length > scrollport_size)
|
||||
scrollbar_data.thumb_travel_to_scroll_ratio = (usable_scrollbar_length - thumb_length) / (scrollable_overflow_length - scrollport_size);
|
||||
|
||||
if (adjust_thumb_rect_for_scroll_offset == AdjustThumbRectForScrollOffset::Yes) {
|
||||
auto scroll_offset = is_horizontal ? -own_scroll_frame_offset().x() : -own_scroll_frame_offset().y();
|
||||
auto thumb_offset = scroll_offset * scrollbar_data.scroll_length;
|
||||
CSSPixels scroll_offset = is_horizontal ? -own_scroll_frame_offset().x() : -own_scroll_frame_offset().y();
|
||||
CSSPixels thumb_offset = scroll_offset * scrollbar_data.thumb_travel_to_scroll_ratio;
|
||||
|
||||
if (is_horizontal)
|
||||
scrollbar_data.thumb_rect.translate_by(thumb_offset, 0);
|
||||
else
|
||||
scrollbar_data.thumb_rect.translate_by(0, thumb_offset);
|
||||
scrollbar_data.thumb_rect.translate_primary_offset_for_orientation(orientation, thumb_offset);
|
||||
}
|
||||
|
||||
return scrollbar_data;
|
||||
|
|
@ -480,17 +545,46 @@ void PaintableBox::paint(DisplayListRecordingContext& context, PaintPhase phase)
|
|||
}
|
||||
}
|
||||
|
||||
if (phase == PaintPhase::Overlay && (g_paint_viewport_scrollbars || !is_viewport_paintable()) && computed_values().scrollbar_width() != CSS::ScrollbarWidth::None) {
|
||||
auto scrollbar_colors = computed_values().scrollbar_color();
|
||||
if (auto scrollbar_data = compute_scrollbar_data(ScrollDirection::Vertical); scrollbar_data.has_value()) {
|
||||
auto gutter_rect = context.rounded_device_rect(scrollbar_data->gutter_rect).to_type<int>();
|
||||
auto thumb_rect = context.rounded_device_rect(scrollbar_data->thumb_rect).to_type<int>();
|
||||
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, true);
|
||||
if (phase == PaintPhase::Overlay) {
|
||||
ChromeMetrics const& metrics = context.chrome_metrics();
|
||||
|
||||
if ((g_paint_viewport_scrollbars || !is_viewport_paintable())
|
||||
&& computed_values().scrollbar_width() != CSS::ScrollbarWidth::None) {
|
||||
auto scrollbar_colors = computed_values().scrollbar_color();
|
||||
|
||||
for (auto direction : { ScrollDirection::Vertical, ScrollDirection::Horizontal }) {
|
||||
auto scrollbar_data = compute_scrollbar_data(direction, metrics);
|
||||
if (!scrollbar_data.has_value())
|
||||
continue;
|
||||
context.display_list_recorder().paint_scrollbar(
|
||||
own_scroll_frame_id().value(),
|
||||
context.rounded_device_rect(scrollbar_data->gutter_rect).to_type<int>(),
|
||||
context.rounded_device_rect(scrollbar_data->thumb_rect).to_type<int>(),
|
||||
scrollbar_data->thumb_travel_to_scroll_ratio,
|
||||
scrollbar_colors.thumb_color,
|
||||
scrollbar_colors.track_color,
|
||||
direction == ScrollDirection::Vertical);
|
||||
}
|
||||
}
|
||||
if (auto scrollbar_data = compute_scrollbar_data(ScrollDirection::Horizontal); scrollbar_data.has_value()) {
|
||||
auto gutter_rect = context.rounded_device_rect(scrollbar_data->gutter_rect).to_type<int>();
|
||||
auto thumb_rect = context.rounded_device_rect(scrollbar_data->thumb_rect).to_type<int>();
|
||||
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 (auto resizer_rect = absolute_resizer_rect(metrics); resizer_rect.has_value()) {
|
||||
bool bottom_left_resizer = is_chrome_mirrored();
|
||||
CSSPixels padding = metrics.resize_gripper_padding;
|
||||
CSSPixelRect css_rect = resizer_rect.value()
|
||||
.shrunken(padding, padding)
|
||||
.translated(bottom_left_resizer ? padding / 2 : -padding / 2, -padding / 2);
|
||||
Gfx::IntRect rect = context.rounded_device_rect(css_rect).to_type<int>();
|
||||
Gfx::Color dark { 0, 0, 0, 100 };
|
||||
Gfx::Color light { 255, 255, 255, 100 };
|
||||
auto& recorder = context.display_list_recorder();
|
||||
auto paint_resizer_line = [&](int step, Gfx::Color color) {
|
||||
Gfx::IntPoint from = { bottom_left_resizer ? rect.left() + step : rect.right() - step, rect.bottom() };
|
||||
Gfx::IntPoint to = { bottom_left_resizer ? rect.left() : rect.right(), rect.bottom() - step };
|
||||
recorder.draw_line(from, to, color, 1, Gfx::LineStyle::Solid);
|
||||
};
|
||||
for (int step = (rect.width() / 3) - 1; step < rect.width(); step += rect.width() / 3) {
|
||||
paint_resizer_line(step, light);
|
||||
paint_resizer_line(step + 1, dark);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -679,12 +773,44 @@ void PaintableBox::clear_clip_overflow_rect(DisplayListRecordingContext& context
|
|||
context.display_list_recorder().pop_clip_frame();
|
||||
}
|
||||
|
||||
bool PaintableBox::has_resizer() const
|
||||
{
|
||||
// https://drafts.csswg.org/css-ui#resize
|
||||
if (is_viewport_paintable())
|
||||
return false;
|
||||
|
||||
// The effect of the resize property on generated content is undefined.
|
||||
// Implementations should not apply the resize property to generated content.
|
||||
|
||||
if (layout_node().generated_for_pseudo_element().has_value())
|
||||
return false;
|
||||
|
||||
auto axes = compute_physical_resize_axes(computed_values());
|
||||
return axes.horizontal || axes.vertical;
|
||||
}
|
||||
|
||||
bool PaintableBox::is_chrome_mirrored() const
|
||||
{
|
||||
auto const& writing_mode = computed_values().writing_mode();
|
||||
return (writing_mode == CSS::WritingMode::HorizontalTb && computed_values().direction() == CSS::Direction::Rtl)
|
||||
|| writing_mode == CSS::WritingMode::VerticalRl
|
||||
|| writing_mode == CSS::WritingMode::SidewaysRl;
|
||||
}
|
||||
|
||||
Paintable::DispatchEventOfSameName PaintableBox::handle_mousedown(Badge<EventHandler>, CSSPixelPoint position, unsigned, unsigned)
|
||||
{
|
||||
position = adjust_position_for_cumulative_scroll_offset(position);
|
||||
ChromeMetrics metrics = document().page().chrome_metrics();
|
||||
|
||||
if (resizer_contains(position, metrics)) {
|
||||
if (auto* element = as_if<DOM::Element>(dom_node().ptr())) {
|
||||
navigable()->event_handler().set_element_resize_in_progress(*element, position);
|
||||
return Paintable::DispatchEventOfSameName::No;
|
||||
}
|
||||
}
|
||||
|
||||
auto handle_scrollbar = [&](auto direction) {
|
||||
auto scrollbar_data = compute_scrollbar_data(direction);
|
||||
auto scrollbar_data = compute_scrollbar_data(direction, metrics);
|
||||
if (!scrollbar_data.has_value())
|
||||
return false;
|
||||
|
||||
|
|
@ -692,7 +818,7 @@ Paintable::DispatchEventOfSameName PaintableBox::handle_mousedown(Badge<EventHan
|
|||
m_scroll_thumb_dragging_direction = direction;
|
||||
|
||||
navigable()->event_handler().set_mouse_event_tracking_paintable(this);
|
||||
scroll_to_mouse_position(position);
|
||||
scroll_to_mouse_position(position, metrics);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -720,19 +846,20 @@ Paintable::DispatchEventOfSameName PaintableBox::handle_mouseup(Badge<EventHandl
|
|||
Paintable::DispatchEventOfSameName PaintableBox::handle_mousemove(Badge<EventHandler>, CSSPixelPoint position, unsigned, unsigned)
|
||||
{
|
||||
position = adjust_position_for_cumulative_scroll_offset(position);
|
||||
ChromeMetrics metrics = document().page().chrome_metrics();
|
||||
|
||||
if (m_scroll_thumb_grab_position.has_value()) {
|
||||
scroll_to_mouse_position(position);
|
||||
scroll_to_mouse_position(position, metrics);
|
||||
return Paintable::DispatchEventOfSameName::No;
|
||||
}
|
||||
|
||||
auto previous_draw_enlarged_horizontal_scrollbar = m_draw_enlarged_horizontal_scrollbar;
|
||||
m_draw_enlarged_horizontal_scrollbar = scrollbar_contains_mouse_position(ScrollDirection::Horizontal, position);
|
||||
m_draw_enlarged_horizontal_scrollbar = scrollbar_contains(ScrollDirection::Horizontal, position, metrics);
|
||||
if (previous_draw_enlarged_horizontal_scrollbar != m_draw_enlarged_horizontal_scrollbar)
|
||||
set_needs_display();
|
||||
|
||||
auto previous_draw_enlarged_vertical_scrollbar = m_draw_enlarged_vertical_scrollbar;
|
||||
m_draw_enlarged_vertical_scrollbar = scrollbar_contains_mouse_position(ScrollDirection::Vertical, position);
|
||||
m_draw_enlarged_vertical_scrollbar = scrollbar_contains(ScrollDirection::Vertical, position, metrics);
|
||||
if (previous_draw_enlarged_vertical_scrollbar != m_draw_enlarged_vertical_scrollbar)
|
||||
set_needs_display();
|
||||
|
||||
|
|
@ -760,23 +887,19 @@ void PaintableBox::handle_mouseleave(Badge<EventHandler>)
|
|||
set_needs_display();
|
||||
}
|
||||
|
||||
bool PaintableBox::scrollbar_contains_mouse_position(ScrollDirection direction, CSSPixelPoint position)
|
||||
bool PaintableBox::scrollbar_contains(ScrollDirection direction, CSSPixelPoint adjusted_position, ChromeMetrics const& metrics) const
|
||||
{
|
||||
TemporaryChange force_enlarged_horizontal_scrollbar { m_draw_enlarged_horizontal_scrollbar, true };
|
||||
TemporaryChange force_enlarged_vertical_scrollbar { m_draw_enlarged_vertical_scrollbar, true };
|
||||
|
||||
auto scrollbar_data = compute_scrollbar_data(direction);
|
||||
if (!scrollbar_data.has_value())
|
||||
return false;
|
||||
|
||||
return scrollbar_data->gutter_rect.contains(position);
|
||||
bool with_gutter = direction == ScrollDirection::Horizontal ? m_draw_enlarged_horizontal_scrollbar : m_draw_enlarged_vertical_scrollbar;
|
||||
if (auto rect = absolute_scrollbar_rect(direction, with_gutter, metrics); rect.has_value())
|
||||
return rect->contains(adjusted_position);
|
||||
return false;
|
||||
}
|
||||
|
||||
void PaintableBox::scroll_to_mouse_position(CSSPixelPoint position)
|
||||
void PaintableBox::scroll_to_mouse_position(CSSPixelPoint position, ChromeMetrics const& metrics)
|
||||
{
|
||||
VERIFY(m_scroll_thumb_dragging_direction.has_value());
|
||||
|
||||
auto scrollbar_data = compute_scrollbar_data(m_scroll_thumb_dragging_direction.value(), AdjustThumbRectForScrollOffset::Yes);
|
||||
auto scrollbar_data = compute_scrollbar_data(m_scroll_thumb_dragging_direction.value(), metrics, AdjustThumbRectForScrollOffset::Yes);
|
||||
VERIFY(scrollbar_data.has_value());
|
||||
|
||||
auto orientation = m_scroll_thumb_dragging_direction == ScrollDirection::Horizontal ? Orientation::Horizontal : Orientation::Vertical;
|
||||
|
|
@ -823,25 +946,42 @@ bool PaintableBox::handle_mousewheel(Badge<EventHandler>, CSSPixelPoint, unsigne
|
|||
return scroll_handled == ScrollHandled::Yes;
|
||||
}
|
||||
|
||||
TraversalDecision PaintableBox::hit_test_scrollbars(CSSPixelPoint position, Function<TraversalDecision(HitTestResult)> const& callback) const
|
||||
TraversalDecision PaintableBox::hit_test_chrome(CSSPixelPoint adjusted_position, Function<TraversalDecision(HitTestResult)> const& callback) const
|
||||
{
|
||||
// FIXME: This const_cast is not great, but this method is invoked from overrides of virtual const methods.
|
||||
auto& self = const_cast<PaintableBox&>(*this);
|
||||
HitTestResult result { const_cast<PaintableBox&>(*this), 0, {}, {}, CSS::CursorPredefined::Default };
|
||||
ChromeMetrics metrics = document().page().chrome_metrics();
|
||||
|
||||
if (self.scrollbar_contains_mouse_position(ScrollDirection::Horizontal, position))
|
||||
return callback(HitTestResult { const_cast<PaintableBox&>(*this) });
|
||||
if (resizer_contains(adjusted_position, metrics)) {
|
||||
auto axes = compute_physical_resize_axes(computed_values());
|
||||
|
||||
if (axes.vertical) {
|
||||
if (axes.horizontal) {
|
||||
if (is_chrome_mirrored())
|
||||
result.cursor_override = CSS::CursorPredefined::SwResize;
|
||||
else
|
||||
result.cursor_override = CSS::CursorPredefined::SeResize;
|
||||
} else {
|
||||
result.cursor_override = CSS::CursorPredefined::NsResize;
|
||||
}
|
||||
} else {
|
||||
result.cursor_override = CSS::CursorPredefined::EwResize;
|
||||
}
|
||||
return callback(result);
|
||||
}
|
||||
if (scrollbar_contains(ScrollDirection::Horizontal, adjusted_position, metrics))
|
||||
return callback(result);
|
||||
|
||||
if (m_draw_enlarged_horizontal_scrollbar) {
|
||||
self.m_draw_enlarged_horizontal_scrollbar = false;
|
||||
self.set_needs_display();
|
||||
m_draw_enlarged_horizontal_scrollbar = false;
|
||||
result.paintable->set_needs_display();
|
||||
}
|
||||
|
||||
if (self.scrollbar_contains_mouse_position(ScrollDirection::Vertical, position))
|
||||
return callback(HitTestResult { const_cast<PaintableBox&>(*this) });
|
||||
if (scrollbar_contains(ScrollDirection::Vertical, adjusted_position, metrics))
|
||||
return callback(result);
|
||||
|
||||
if (m_draw_enlarged_vertical_scrollbar) {
|
||||
self.m_draw_enlarged_vertical_scrollbar = false;
|
||||
self.set_needs_display();
|
||||
m_draw_enlarged_vertical_scrollbar = false;
|
||||
result.paintable->set_needs_display();
|
||||
}
|
||||
|
||||
return TraversalDecision::Continue;
|
||||
|
|
@ -852,6 +992,17 @@ CSSPixelPoint PaintableBox::adjust_position_for_cumulative_scroll_offset(CSSPixe
|
|||
return position.translated(-cumulative_offset_of_enclosing_scroll_frame());
|
||||
}
|
||||
|
||||
bool PaintableBox::resizer_contains(CSSPixelPoint adjusted_position, ChromeMetrics const& metrics) const
|
||||
{
|
||||
auto handle_rect = absolute_resizer_rect(metrics);
|
||||
if (!handle_rect.has_value())
|
||||
return false;
|
||||
bool bottom_left_resizer = is_chrome_mirrored();
|
||||
handle_rect->inflate(0, bottom_left_resizer ? 0 : box_model().border.right, box_model().border.bottom, bottom_left_resizer ? box_model().border.left : 0);
|
||||
|
||||
return handle_rect->contains(adjusted_position);
|
||||
}
|
||||
|
||||
TraversalDecision PaintableBox::hit_test(CSSPixelPoint position, HitTestType type, Function<TraversalDecision(HitTestResult)> const& callback) const
|
||||
{
|
||||
if (clip_rect_for_hit_testing().has_value() && !clip_rect_for_hit_testing()->contains(position))
|
||||
|
|
@ -860,7 +1011,9 @@ TraversalDecision PaintableBox::hit_test(CSSPixelPoint position, HitTestType typ
|
|||
if (computed_values().visibility() != CSS::Visibility::Visible)
|
||||
return TraversalDecision::Continue;
|
||||
|
||||
if (hit_test_scrollbars(position, callback) == TraversalDecision::Break)
|
||||
auto const offset_position_adjusted_by_scroll_offset = adjust_position_for_cumulative_scroll_offset(position);
|
||||
|
||||
if (hit_test_chrome(offset_position_adjusted_by_scroll_offset, callback) == TraversalDecision::Break)
|
||||
return TraversalDecision::Break;
|
||||
|
||||
if (is_viewport_paintable()) {
|
||||
|
|
@ -880,8 +1033,6 @@ TraversalDecision PaintableBox::hit_test(CSSPixelPoint position, HitTestType typ
|
|||
if (!visible_for_hit_testing())
|
||||
return TraversalDecision::Continue;
|
||||
|
||||
auto const offset_position_adjusted_by_scroll_offset = adjust_position_for_cumulative_scroll_offset(position);
|
||||
|
||||
if (!absolute_border_box_rect().contains(offset_position_adjusted_by_scroll_offset))
|
||||
return TraversalDecision::Continue;
|
||||
|
||||
|
|
@ -1264,4 +1415,35 @@ Optional<Gfx::Filter> PaintableBox::resolve_filter(DisplayListRecordingContext&
|
|||
return resolved_filter;
|
||||
}
|
||||
|
||||
static PhysicalResizeAxes compute_physical_resize_axes(CSS::ComputedValues const& computed)
|
||||
{
|
||||
// https://drafts.csswg.org/css-ui/#resize
|
||||
if (computed.resize() == CSS::Resize::None)
|
||||
return {};
|
||||
|
||||
// 4.1. ... The resize property applies to elements that are scroll containers. UAs may also apply it,
|
||||
// regardless of the value of the overflow property, to:
|
||||
// - Replaced elements representing images or videos, such as img, video, picture, svg, object, or canvas.
|
||||
// - The <iframe> element.
|
||||
if (computed.display().is_inline_outside() && computed.display().is_flow_inside())
|
||||
return {};
|
||||
|
||||
bool horizontal_writing_mode = computed.writing_mode() == CSS::WritingMode::HorizontalTb;
|
||||
|
||||
return {
|
||||
.horizontal = computed.overflow_x() != CSS::Overflow::Visible
|
||||
&& computed.overflow_x() != CSS::Overflow::Clip
|
||||
&& (computed.resize() == CSS::Resize::Both
|
||||
|| computed.resize() == CSS::Resize::Horizontal
|
||||
|| (computed.resize() == CSS::Resize::Inline && horizontal_writing_mode)
|
||||
|| (computed.resize() == CSS::Resize::Block && !horizontal_writing_mode)),
|
||||
.vertical = computed.overflow_y() != CSS::Overflow::Visible
|
||||
&& computed.overflow_y() != CSS::Overflow::Clip
|
||||
&& (computed.resize() == CSS::Resize::Both
|
||||
|| computed.resize() == CSS::Resize::Vertical
|
||||
|| (computed.resize() == CSS::Resize::Inline && !horizontal_writing_mode)
|
||||
|| (computed.resize() == CSS::Resize::Block && horizontal_writing_mode))
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <LibWeb/Layout/Box.h>
|
||||
#include <LibWeb/Painting/BackgroundPainting.h>
|
||||
#include <LibWeb/Painting/BoxModelMetrics.h>
|
||||
#include <LibWeb/Painting/ChromeMetrics.h>
|
||||
#include <LibWeb/Painting/ClipFrame.h>
|
||||
#include <LibWeb/Painting/Paintable.h>
|
||||
#include <LibWeb/Painting/PaintableFragment.h>
|
||||
|
|
@ -139,8 +140,6 @@ public:
|
|||
|
||||
[[nodiscard]] virtual TraversalDecision hit_test(CSSPixelPoint position, HitTestType type, Function<TraversalDecision(HitTestResult)> const& callback) const override;
|
||||
Optional<HitTestResult> hit_test(CSSPixelPoint, HitTestType) const;
|
||||
[[nodiscard]] TraversalDecision hit_test_children(CSSPixelPoint, HitTestType, Function<TraversalDecision(HitTestResult)> const&) const;
|
||||
[[nodiscard]] TraversalDecision hit_test_continuation(Function<TraversalDecision(HitTestResult)> const& callback) const;
|
||||
|
||||
virtual bool handle_mousewheel(Badge<EventHandler>, CSSPixelPoint, unsigned buttons, unsigned modifiers, int wheel_delta_x, int wheel_delta_y) override;
|
||||
|
||||
|
|
@ -280,7 +279,7 @@ protected:
|
|||
struct ScrollbarData {
|
||||
CSSPixelRect gutter_rect;
|
||||
CSSPixelRect thumb_rect;
|
||||
CSSPixelFraction scroll_length { 0 };
|
||||
CSSPixelFraction thumb_travel_to_scroll_ratio { 0 };
|
||||
};
|
||||
enum class ScrollDirection {
|
||||
Horizontal,
|
||||
|
|
@ -290,11 +289,22 @@ protected:
|
|||
No,
|
||||
Yes,
|
||||
};
|
||||
Optional<ScrollbarData> compute_scrollbar_data(ScrollDirection, AdjustThumbRectForScrollOffset = AdjustThumbRectForScrollOffset::No) const;
|
||||
[[nodiscard]] bool could_be_scrolled_by_wheel_event(ScrollDirection) const;
|
||||
[[nodiscard]] TraversalDecision hit_test_children(CSSPixelPoint position, HitTestType type, Function<TraversalDecision(HitTestResult)> const& callback) const;
|
||||
[[nodiscard]] TraversalDecision hit_test_continuation(Function<TraversalDecision(HitTestResult)> const& callback) const;
|
||||
[[nodiscard]] TraversalDecision hit_test_chrome(CSSPixelPoint adjusted_position, Function<TraversalDecision(HitTestResult)> const& callback) const;
|
||||
|
||||
TraversalDecision hit_test_scrollbars(CSSPixelPoint position, Function<TraversalDecision(HitTestResult)> const& callback) const;
|
||||
CSSPixelPoint adjust_position_for_cumulative_scroll_offset(CSSPixelPoint) const;
|
||||
Optional<ScrollbarData> compute_scrollbar_data(
|
||||
ScrollDirection direction,
|
||||
ChromeMetrics const& chrome_metrics,
|
||||
AdjustThumbRectForScrollOffset = AdjustThumbRectForScrollOffset::No) const;
|
||||
CSSPixelPoint adjust_position_for_cumulative_scroll_offset(CSSPixelPoint position) const;
|
||||
CSSPixels available_scrollbar_length(ScrollDirection direction, ChromeMetrics const& chrome_metrics) const;
|
||||
Optional<CSSPixelRect> absolute_scrollbar_rect(ScrollDirection direction, bool with_gutter, ChromeMetrics const& chrome_metrics) const;
|
||||
Optional<CSSPixelRect> absolute_resizer_rect(ChromeMetrics const& chrome_metrics) const;
|
||||
bool could_be_scrolled_by_wheel_event(ScrollDirection direction) const;
|
||||
bool resizer_contains(CSSPixelPoint adjusted_position, ChromeMetrics const& chrome_metrics) const;
|
||||
bool is_chrome_mirrored() const;
|
||||
bool has_resizer() const;
|
||||
|
||||
private:
|
||||
[[nodiscard]] virtual bool is_paintable_box() const final { return true; }
|
||||
|
|
@ -304,8 +314,8 @@ private:
|
|||
virtual DispatchEventOfSameName handle_mousemove(Badge<EventHandler>, CSSPixelPoint, unsigned buttons, unsigned modifiers) override;
|
||||
virtual void handle_mouseleave(Badge<EventHandler>) override;
|
||||
|
||||
bool scrollbar_contains_mouse_position(ScrollDirection, CSSPixelPoint);
|
||||
void scroll_to_mouse_position(CSSPixelPoint);
|
||||
bool scrollbar_contains(ScrollDirection, CSSPixelPoint adjusted_position, ChromeMetrics const& chrome_metrics) const;
|
||||
void scroll_to_mouse_position(CSSPixelPoint, ChromeMetrics const& chrome_metrics);
|
||||
|
||||
GC::Ptr<StackingContext> m_stacking_context;
|
||||
|
||||
|
|
@ -336,8 +346,8 @@ private:
|
|||
|
||||
Optional<CSSPixels> m_scroll_thumb_grab_position;
|
||||
Optional<ScrollDirection> m_scroll_thumb_dragging_direction;
|
||||
bool m_draw_enlarged_horizontal_scrollbar { false };
|
||||
bool m_draw_enlarged_vertical_scrollbar { false };
|
||||
mutable bool m_draw_enlarged_horizontal_scrollbar { false };
|
||||
mutable bool m_draw_enlarged_vertical_scrollbar { false };
|
||||
|
||||
ResolvedBackground m_resolved_background;
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
#include <LibWeb/Layout/BlockContainer.h>
|
||||
#include <LibWeb/Layout/InlineNode.h>
|
||||
#include <LibWeb/Painting/DisplayListRecorder.h>
|
||||
#include <LibWeb/Painting/PaintableBox.h>
|
||||
#include <LibWeb/Painting/PaintableWithLines.h>
|
||||
#include <LibWeb/Painting/ShadowPainting.h>
|
||||
#include <LibWeb/Painting/TextPaintable.h>
|
||||
|
|
@ -95,7 +94,9 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
|
|||
if (!layout_node().children_are_inline())
|
||||
return PaintableBox::hit_test(position, type, callback);
|
||||
|
||||
if (hit_test_scrollbars(position, callback) == TraversalDecision::Break)
|
||||
auto const offset_position_adjusted_by_scroll_offset = adjust_position_for_cumulative_scroll_offset(position);
|
||||
|
||||
if (hit_test_chrome(offset_position_adjusted_by_scroll_offset, callback) == TraversalDecision::Break)
|
||||
return TraversalDecision::Break;
|
||||
|
||||
if (hit_test_children(position, type, callback) == TraversalDecision::Break)
|
||||
|
|
@ -104,8 +105,6 @@ TraversalDecision PaintableWithLines::hit_test(CSSPixelPoint position, HitTestTy
|
|||
if (!visible_for_hit_testing())
|
||||
return TraversalDecision::Continue;
|
||||
|
||||
auto const offset_position_adjusted_by_scroll_offset = adjust_position_for_cumulative_scroll_offset(position);
|
||||
|
||||
for (auto const& fragment : fragments()) {
|
||||
if (fragment.paintable().has_stacking_context() || !fragment.paintable().visible_for_hit_testing())
|
||||
continue;
|
||||
|
|
|
|||
Loading…
Reference in a new issue