mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-07-28 11:49:44 +00:00
LibWeb+WebContent+UI: Support image cursors
The `cursor` property accepts a list of possible cursors, which behave as a fallback: We use whichever cursor is the first available one. This is a little complicated because initially, any remote images have not loaded, so we need to use the fallback standard cursor, and then switch to another when it loads. So, ComputedValues stores a Vector of cursors, and then in EventHandler we scan down that list until we find a cursor that's ready for use. The spec defines cursors as being `<url>`, but allows for `<image>` instead. That includes functions like `linear-gradient()`. This commit implements image cursors in the Qt UI, but not AppKit.
This commit is contained in:
parent
fd2414ba35
commit
bfd7ac1204
Notes:
github-actions[bot]
2025-02-28 12:51:27 +00:00
Author: https://github.com/AtkinsSJ
Commit: bfd7ac1204
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3644
20 changed files with 297 additions and 170 deletions
|
@ -83,65 +83,81 @@ static bool parent_element_for_event_dispatch(Painting::Paintable& paintable, GC
|
|||
return node && layout_node;
|
||||
}
|
||||
|
||||
static Gfx::StandardCursor cursor_css_to_gfx(CSS::Cursor cursor)
|
||||
static Gfx::Cursor resolve_cursor(Layout::NodeWithStyle const& layout_node, Vector<CSS::CursorData> const& cursor_data, Gfx::StandardCursor auto_cursor)
|
||||
{
|
||||
switch (cursor) {
|
||||
case CSS::Cursor::Crosshair:
|
||||
case CSS::Cursor::Cell:
|
||||
return Gfx::StandardCursor::Crosshair;
|
||||
case CSS::Cursor::Grab:
|
||||
case CSS::Cursor::Grabbing:
|
||||
return Gfx::StandardCursor::Drag;
|
||||
case CSS::Cursor::Pointer:
|
||||
return Gfx::StandardCursor::Hand;
|
||||
case CSS::Cursor::Help:
|
||||
return Gfx::StandardCursor::Help;
|
||||
case CSS::Cursor::None:
|
||||
return Gfx::StandardCursor::Hidden;
|
||||
case CSS::Cursor::NotAllowed:
|
||||
return Gfx::StandardCursor::Disallowed;
|
||||
case CSS::Cursor::Text:
|
||||
case CSS::Cursor::VerticalText:
|
||||
return Gfx::StandardCursor::IBeam;
|
||||
case CSS::Cursor::Move:
|
||||
case CSS::Cursor::AllScroll:
|
||||
return Gfx::StandardCursor::Move;
|
||||
case CSS::Cursor::Progress:
|
||||
case CSS::Cursor::Wait:
|
||||
return Gfx::StandardCursor::Wait;
|
||||
case CSS::Cursor::ColResize:
|
||||
return Gfx::StandardCursor::ResizeColumn;
|
||||
case CSS::Cursor::EResize:
|
||||
case CSS::Cursor::WResize:
|
||||
case CSS::Cursor::EwResize:
|
||||
return Gfx::StandardCursor::ResizeHorizontal;
|
||||
case CSS::Cursor::RowResize:
|
||||
return Gfx::StandardCursor::ResizeRow;
|
||||
case CSS::Cursor::NResize:
|
||||
case CSS::Cursor::SResize:
|
||||
case CSS::Cursor::NsResize:
|
||||
return Gfx::StandardCursor::ResizeVertical;
|
||||
case CSS::Cursor::NeResize:
|
||||
case CSS::Cursor::SwResize:
|
||||
case CSS::Cursor::NeswResize:
|
||||
return Gfx::StandardCursor::ResizeDiagonalBLTR;
|
||||
case CSS::Cursor::NwResize:
|
||||
case CSS::Cursor::SeResize:
|
||||
case CSS::Cursor::NwseResize:
|
||||
return Gfx::StandardCursor::ResizeDiagonalTLBR;
|
||||
case CSS::Cursor::ZoomIn:
|
||||
case CSS::Cursor::ZoomOut:
|
||||
return Gfx::StandardCursor::Zoom;
|
||||
case CSS::Cursor::ContextMenu:
|
||||
case CSS::Cursor::Alias:
|
||||
case CSS::Cursor::Copy:
|
||||
case CSS::Cursor::NoDrop:
|
||||
// FIXME: No corresponding GFX Standard Cursor, fallthrough to None
|
||||
case CSS::Cursor::Auto:
|
||||
case CSS::Cursor::Default:
|
||||
default:
|
||||
return Gfx::StandardCursor::None;
|
||||
for (auto const& cursor : cursor_data) {
|
||||
auto result = cursor.visit(
|
||||
[auto_cursor](CSS::Cursor css_cursor) -> Optional<Gfx::Cursor> {
|
||||
switch (css_cursor) {
|
||||
case CSS::Cursor::Crosshair:
|
||||
case CSS::Cursor::Cell:
|
||||
return Gfx::StandardCursor::Crosshair;
|
||||
case CSS::Cursor::Grab:
|
||||
case CSS::Cursor::Grabbing:
|
||||
return Gfx::StandardCursor::Drag;
|
||||
case CSS::Cursor::Pointer:
|
||||
return Gfx::StandardCursor::Hand;
|
||||
case CSS::Cursor::Help:
|
||||
return Gfx::StandardCursor::Help;
|
||||
case CSS::Cursor::None:
|
||||
return Gfx::StandardCursor::Hidden;
|
||||
case CSS::Cursor::NotAllowed:
|
||||
return Gfx::StandardCursor::Disallowed;
|
||||
case CSS::Cursor::Text:
|
||||
case CSS::Cursor::VerticalText:
|
||||
return Gfx::StandardCursor::IBeam;
|
||||
case CSS::Cursor::Move:
|
||||
case CSS::Cursor::AllScroll:
|
||||
return Gfx::StandardCursor::Move;
|
||||
case CSS::Cursor::Progress:
|
||||
case CSS::Cursor::Wait:
|
||||
return Gfx::StandardCursor::Wait;
|
||||
case CSS::Cursor::ColResize:
|
||||
return Gfx::StandardCursor::ResizeColumn;
|
||||
case CSS::Cursor::EResize:
|
||||
case CSS::Cursor::WResize:
|
||||
case CSS::Cursor::EwResize:
|
||||
return Gfx::StandardCursor::ResizeHorizontal;
|
||||
case CSS::Cursor::RowResize:
|
||||
return Gfx::StandardCursor::ResizeRow;
|
||||
case CSS::Cursor::NResize:
|
||||
case CSS::Cursor::SResize:
|
||||
case CSS::Cursor::NsResize:
|
||||
return Gfx::StandardCursor::ResizeVertical;
|
||||
case CSS::Cursor::NeResize:
|
||||
case CSS::Cursor::SwResize:
|
||||
case CSS::Cursor::NeswResize:
|
||||
return Gfx::StandardCursor::ResizeDiagonalBLTR;
|
||||
case CSS::Cursor::NwResize:
|
||||
case CSS::Cursor::SeResize:
|
||||
case CSS::Cursor::NwseResize:
|
||||
return Gfx::StandardCursor::ResizeDiagonalTLBR;
|
||||
case CSS::Cursor::ZoomIn:
|
||||
case CSS::Cursor::ZoomOut:
|
||||
return Gfx::StandardCursor::Zoom;
|
||||
case CSS::Cursor::Auto:
|
||||
return auto_cursor;
|
||||
case CSS::Cursor::ContextMenu:
|
||||
case CSS::Cursor::Alias:
|
||||
case CSS::Cursor::Copy:
|
||||
case CSS::Cursor::NoDrop:
|
||||
// FIXME: No corresponding GFX Standard Cursor, fallthrough to None
|
||||
case CSS::Cursor::Default:
|
||||
default:
|
||||
return Gfx::StandardCursor::None;
|
||||
}
|
||||
},
|
||||
[&layout_node](NonnullRefPtr<CSS::CursorStyleValue> const& cursor_style_value) -> Optional<Gfx::Cursor> {
|
||||
if (auto image_cursor = cursor_style_value->make_image_cursor(layout_node); image_cursor.has_value())
|
||||
return image_cursor.release_value();
|
||||
return {};
|
||||
});
|
||||
if (result.has_value())
|
||||
return result.release_value();
|
||||
}
|
||||
|
||||
// We should never get here
|
||||
return Gfx::StandardCursor::None;
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/cssom-view/#dom-mouseevent-offsetx
|
||||
|
@ -692,7 +708,7 @@ EventResult EventHandler::handle_mousemove(CSSPixelPoint viewport_position, CSSP
|
|||
|
||||
bool hovered_node_changed = false;
|
||||
bool is_hovering_link = false;
|
||||
Gfx::StandardCursor hovered_node_cursor = Gfx::StandardCursor::None;
|
||||
Gfx::Cursor hovered_node_cursor = Gfx::StandardCursor::None;
|
||||
|
||||
GC::Ptr<Painting::Paintable> paintable;
|
||||
Optional<int> start_index;
|
||||
|
@ -721,7 +737,7 @@ EventResult EventHandler::handle_mousemove(CSSPixelPoint viewport_position, CSSP
|
|||
return EventResult::Dropped;
|
||||
}
|
||||
|
||||
auto const cursor = paintable->computed_values().cursor();
|
||||
auto cursor_data = paintable->computed_values().cursor();
|
||||
auto pointer_events = paintable->computed_values().pointer_events();
|
||||
// FIXME: Handle other values for pointer-events.
|
||||
VERIFY(pointer_events != CSS::PointerEvents::None);
|
||||
|
@ -739,15 +755,9 @@ EventResult EventHandler::handle_mousemove(CSSPixelPoint viewport_position, CSSP
|
|||
is_hovering_link = true;
|
||||
|
||||
if (paintable->layout_node().is_text_node()) {
|
||||
if (cursor == CSS::Cursor::Auto)
|
||||
hovered_node_cursor = Gfx::StandardCursor::IBeam;
|
||||
else
|
||||
hovered_node_cursor = cursor_css_to_gfx(cursor);
|
||||
hovered_node_cursor = resolve_cursor(*paintable->layout_node().parent(), cursor_data, Gfx::StandardCursor::IBeam);
|
||||
} else if (node->is_element()) {
|
||||
if (cursor == CSS::Cursor::Auto)
|
||||
hovered_node_cursor = Gfx::StandardCursor::Arrow;
|
||||
else
|
||||
hovered_node_cursor = cursor_css_to_gfx(cursor);
|
||||
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);
|
||||
|
@ -793,7 +803,10 @@ EventResult EventHandler::handle_mousemove(CSSPixelPoint viewport_position, CSSP
|
|||
|
||||
auto& page = m_navigable->page();
|
||||
|
||||
if (page.current_cursor() != hovered_node_cursor) {
|
||||
// FIXME: This check is only approximate. ImageCursors from the same CursorStyleValue share bitmaps, but may repaint them.
|
||||
// So comparing them does not tell you if they are the same image. Also, the image may change even if the hovered
|
||||
// node does not.
|
||||
if (page.current_cursor() != hovered_node_cursor || hovered_node_changed) {
|
||||
page.set_current_cursor(hovered_node_cursor);
|
||||
page.client().page_did_request_cursor_change(hovered_node_cursor);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue