mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-20 03:25:13 +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: https://github.com/LadybirdBrowser/ladybird/commit/bfd7ac12047 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/3644
20 changed files with 297 additions and 170 deletions
|
@ -11,7 +11,6 @@
|
|||
#include <LibGC/CellAllocator.h>
|
||||
#include <LibWeb/CSS/Clip.h>
|
||||
#include <LibWeb/CSS/ComputedProperties.h>
|
||||
#include <LibWeb/CSS/StyleValues/AngleStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CSSKeywordValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/ColorSchemeStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
|
||||
|
@ -32,7 +31,6 @@
|
|||
#include <LibWeb/CSS/StyleValues/PercentageStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/RectStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/ScrollbarGutterStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/ShadowStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/StringStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/StyleValueList.h>
|
||||
|
@ -954,13 +952,30 @@ ContentVisibility ComputedProperties::content_visibility() const
|
|||
return keyword_to_content_visibility(value.to_keyword()).release_value();
|
||||
}
|
||||
|
||||
Cursor ComputedProperties::cursor() const
|
||||
Vector<CursorData> ComputedProperties::cursor() const
|
||||
{
|
||||
// Return the first available cursor.
|
||||
auto const& value = property(PropertyID::Cursor);
|
||||
// FIXME: We don't currently support custom cursors.
|
||||
if (value.is_url())
|
||||
return Cursor::Auto;
|
||||
return keyword_to_cursor(value.to_keyword()).release_value();
|
||||
Vector<CursorData> cursors;
|
||||
if (value.is_value_list()) {
|
||||
for (auto const& item : value.as_value_list().values()) {
|
||||
if (item->is_cursor()) {
|
||||
cursors.append({ item->as_cursor() });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto keyword = keyword_to_cursor(item->to_keyword()); keyword.has_value())
|
||||
cursors.append(keyword.release_value());
|
||||
}
|
||||
} else if (value.is_keyword()) {
|
||||
if (auto keyword = keyword_to_cursor(value.to_keyword()); keyword.has_value())
|
||||
cursors.append(keyword.release_value());
|
||||
}
|
||||
|
||||
if (cursors.is_empty())
|
||||
cursors.append(Cursor::Auto);
|
||||
|
||||
return cursors;
|
||||
}
|
||||
|
||||
Visibility ComputedProperties::visibility() const
|
||||
|
|
|
@ -94,7 +94,7 @@ public:
|
|||
};
|
||||
ContentDataAndQuoteNestingLevel content(DOM::Element&, u32 initial_quote_nesting_level) const;
|
||||
ContentVisibility content_visibility() const;
|
||||
Cursor cursor() const;
|
||||
Vector<CursorData> cursor() const;
|
||||
Variant<LengthOrCalculated, NumberOrCalculated> tab_size() const;
|
||||
WhiteSpace white_space() const;
|
||||
WordBreak word_break() const;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2023, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2023-2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -28,7 +29,7 @@
|
|||
#include <LibWeb/CSS/Size.h>
|
||||
#include <LibWeb/CSS/StyleValues/AbstractImageStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/BasicShapeStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/PositionStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CursorStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/ShadowStyleValue.h>
|
||||
#include <LibWeb/CSS/Transformation.h>
|
||||
|
||||
|
@ -79,6 +80,8 @@ struct Containment {
|
|||
bool is_empty() const { return !(size_containment || inline_size_containment || layout_containment || style_containment || paint_containment); }
|
||||
};
|
||||
|
||||
using CursorData = Variant<NonnullRefPtr<CursorStyleValue>, Cursor>;
|
||||
|
||||
using ListStyleType = Variant<CounterStyleNameKeyword, String>;
|
||||
|
||||
class InitialValues {
|
||||
|
@ -94,7 +97,7 @@ public:
|
|||
static CSS::Clip clip() { return CSS::Clip::make_auto(); }
|
||||
static CSS::PreferredColorScheme color_scheme() { return CSS::PreferredColorScheme::Auto; }
|
||||
static CSS::ContentVisibility content_visibility() { return CSS::ContentVisibility::Visible; }
|
||||
static CSS::Cursor cursor() { return CSS::Cursor::Auto; }
|
||||
static CursorData cursor() { return { CSS::Cursor::Auto }; }
|
||||
static CSS::WhiteSpace white_space() { return CSS::WhiteSpace::Normal; }
|
||||
static CSS::WordBreak word_break() { return CSS::WordBreak::Normal; }
|
||||
static CSS::LengthOrCalculated word_spacing() { return CSS::Length::make_px(0); }
|
||||
|
@ -375,7 +378,7 @@ public:
|
|||
CSS::Clip clip() const { return m_noninherited.clip; }
|
||||
CSS::PreferredColorScheme color_scheme() const { return m_inherited.color_scheme; }
|
||||
CSS::ContentVisibility content_visibility() const { return m_inherited.content_visibility; }
|
||||
CSS::Cursor cursor() const { return m_inherited.cursor; }
|
||||
Vector<CursorData> const& cursor() const { return m_inherited.cursor; }
|
||||
CSS::ContentData content() const { return m_noninherited.content; }
|
||||
CSS::PointerEvents pointer_events() const { return m_inherited.pointer_events; }
|
||||
CSS::Display display() const { return m_noninherited.display; }
|
||||
|
@ -575,7 +578,7 @@ protected:
|
|||
Optional<Color> accent_color {};
|
||||
Color webkit_text_fill_color { InitialValues::color() };
|
||||
CSS::ContentVisibility content_visibility { InitialValues::content_visibility() };
|
||||
CSS::Cursor cursor { InitialValues::cursor() };
|
||||
Vector<CursorData> cursor { InitialValues::cursor() };
|
||||
CSS::ImageRendering image_rendering { InitialValues::image_rendering() };
|
||||
CSS::PointerEvents pointer_events { InitialValues::pointer_events() };
|
||||
Variant<LengthOrCalculated, NumberOrCalculated> tab_size { InitialValues::tab_size() };
|
||||
|
@ -763,7 +766,7 @@ public:
|
|||
void set_clip(CSS::Clip const& clip) { m_noninherited.clip = clip; }
|
||||
void set_content(ContentData const& content) { m_noninherited.content = content; }
|
||||
void set_content_visibility(CSS::ContentVisibility content_visibility) { m_inherited.content_visibility = content_visibility; }
|
||||
void set_cursor(CSS::Cursor cursor) { m_inherited.cursor = cursor; }
|
||||
void set_cursor(Vector<CursorData> cursor) { m_inherited.cursor = move(cursor); }
|
||||
void set_image_rendering(CSS::ImageRendering value) { m_inherited.image_rendering = value; }
|
||||
void set_pointer_events(CSS::PointerEvents value) { m_inherited.pointer_events = value; }
|
||||
void set_background_color(Color color) { m_noninherited.background_color = color; }
|
||||
|
|
|
@ -368,6 +368,7 @@ private:
|
|||
RefPtr<CSSStyleValue> parse_counter_increment_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_counter_reset_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_counter_set_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_cursor_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_display_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_flex_shorthand_value(TokenStream<ComponentValue>&);
|
||||
RefPtr<CSSStyleValue> parse_flex_flow_value(TokenStream<ComponentValue>&);
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include <LibWeb/CSS/StyleValues/ColorSchemeStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/ContentStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CounterDefinitionsStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CursorStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/CustomIdentStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/DisplayStyleValue.h>
|
||||
#include <LibWeb/CSS/StyleValues/EasingStyleValue.h>
|
||||
|
@ -537,6 +538,10 @@ Parser::ParseErrorOr<NonnullRefPtr<CSSStyleValue>> Parser::parse_css_value(Prope
|
|||
if (auto parsed_value = parse_counter_set_value(tokens); parsed_value && !tokens.has_next_token())
|
||||
return parsed_value.release_nonnull();
|
||||
return ParseError::SyntaxError;
|
||||
case PropertyID::Cursor:
|
||||
if (auto parsed_value = parse_cursor_value(tokens); parsed_value && !tokens.has_next_token())
|
||||
return parsed_value.release_nonnull();
|
||||
return ParseError::SyntaxError;
|
||||
case PropertyID::Display:
|
||||
if (auto parsed_value = parse_display_value(tokens); parsed_value && !tokens.has_next_token())
|
||||
return parsed_value.release_nonnull();
|
||||
|
@ -958,6 +963,72 @@ RefPtr<CSSStyleValue> Parser::parse_counter_set_value(TokenStream<ComponentValue
|
|||
return parse_counter_definitions_value(tokens, AllowReversed::No, 0);
|
||||
}
|
||||
|
||||
// https://drafts.csswg.org/css-ui-3/#cursor
|
||||
RefPtr<CSSStyleValue> Parser::parse_cursor_value(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
// [ [<url> [<x> <y>]?,]* <built-in-cursor> ]
|
||||
// So, any number of custom cursor definitions, and then a mandatory cursor name keyword, all comma-separated.
|
||||
|
||||
auto transaction = tokens.begin_transaction();
|
||||
|
||||
StyleValueVector cursors;
|
||||
|
||||
auto parts = parse_a_comma_separated_list_of_component_values(tokens);
|
||||
for (auto i = 0u; i < parts.size(); ++i) {
|
||||
auto& part = parts[i];
|
||||
TokenStream part_tokens { part };
|
||||
|
||||
if (i == parts.size() - 1) {
|
||||
// Cursor keyword
|
||||
part_tokens.discard_whitespace();
|
||||
auto keyword_value = parse_keyword_value(part_tokens);
|
||||
if (!keyword_value || !keyword_to_cursor(keyword_value->to_keyword()).has_value())
|
||||
return {};
|
||||
|
||||
part_tokens.discard_whitespace();
|
||||
if (part_tokens.has_next_token())
|
||||
return {};
|
||||
|
||||
cursors.append(keyword_value.release_nonnull());
|
||||
} else {
|
||||
// Custom cursor definition
|
||||
// <url> [<x> <y>]?
|
||||
// "Conforming user agents may, instead of <url>, support <image> which is a superset."
|
||||
|
||||
part_tokens.discard_whitespace();
|
||||
auto image_value = parse_image_value(part_tokens);
|
||||
if (!image_value)
|
||||
return {};
|
||||
|
||||
part_tokens.discard_whitespace();
|
||||
|
||||
if (part_tokens.has_next_token()) {
|
||||
// x and y, which are both <number>
|
||||
auto x = parse_number(part_tokens);
|
||||
part_tokens.discard_whitespace();
|
||||
auto y = parse_number(part_tokens);
|
||||
part_tokens.discard_whitespace();
|
||||
if (!x.has_value() || !y.has_value() || part_tokens.has_next_token())
|
||||
return nullptr;
|
||||
|
||||
cursors.append(CursorStyleValue::create(image_value.release_nonnull(), x.release_value(), y.release_value()));
|
||||
continue;
|
||||
}
|
||||
|
||||
cursors.append(CursorStyleValue::create(image_value.release_nonnull(), {}, {}));
|
||||
}
|
||||
}
|
||||
|
||||
if (cursors.is_empty())
|
||||
return nullptr;
|
||||
|
||||
transaction.commit();
|
||||
if (cursors.size() == 1)
|
||||
return *cursors.first();
|
||||
|
||||
return StyleValueList::create(move(cursors), StyleValueList::Separator::Comma);
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/css-sizing-4/#aspect-ratio
|
||||
RefPtr<CSSStyleValue> Parser::parse_aspect_ratio_value(TokenStream<ComponentValue>& tokens)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -127,8 +127,8 @@ public:
|
|||
bool is_in_tooltip_area() const { return m_is_in_tooltip_area; }
|
||||
void set_is_in_tooltip_area(bool b) { m_is_in_tooltip_area = b; }
|
||||
|
||||
Gfx::StandardCursor current_cursor() const { return m_current_cursor; }
|
||||
void set_current_cursor(Gfx::StandardCursor cursor) { m_current_cursor = cursor; }
|
||||
Gfx::Cursor current_cursor() const { return m_current_cursor; }
|
||||
void set_current_cursor(Gfx::Cursor cursor) { m_current_cursor = move(cursor); }
|
||||
|
||||
DevicePixelPoint window_position() const { return m_window_position; }
|
||||
void set_window_position(DevicePixelPoint position) { m_window_position = position; }
|
||||
|
@ -258,7 +258,7 @@ private:
|
|||
bool m_is_hovering_link { false };
|
||||
bool m_is_in_tooltip_area { false };
|
||||
|
||||
Gfx::StandardCursor m_current_cursor { Gfx::StandardCursor::Arrow };
|
||||
Gfx::Cursor m_current_cursor { Gfx::StandardCursor::Arrow };
|
||||
|
||||
DevicePixelPoint m_window_position {};
|
||||
DevicePixelSize m_window_size {};
|
||||
|
@ -338,7 +338,7 @@ public:
|
|||
virtual void page_did_create_new_document(Web::DOM::Document&) { }
|
||||
virtual void page_did_change_active_document_in_top_level_browsing_context(Web::DOM::Document&) { }
|
||||
virtual void page_did_finish_loading(URL::URL const&) { }
|
||||
virtual void page_did_request_cursor_change(Gfx::StandardCursor) { }
|
||||
virtual void page_did_request_cursor_change(Gfx::Cursor const&) { }
|
||||
virtual void page_did_request_context_menu(CSSPixelPoint) { }
|
||||
virtual void page_did_request_link_context_menu(CSSPixelPoint, URL::URL const&, [[maybe_unused]] ByteString const& target, [[maybe_unused]] unsigned modifiers) { }
|
||||
virtual void page_did_request_image_context_menu(CSSPixelPoint, URL::URL const&, [[maybe_unused]] ByteString const& target, [[maybe_unused]] unsigned modifiers, Optional<Gfx::Bitmap const*>) { }
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <LibWeb/DOM/Document.h>
|
||||
#include <LibWeb/Layout/BlockContainer.h>
|
||||
#include <LibWeb/Painting/Paintable.h>
|
||||
#include <LibWeb/Painting/PaintableBox.h>
|
||||
#include <LibWeb/Painting/StackingContext.h>
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <LibGC/Root.h>
|
||||
#include <LibGfx/Cursor.h>
|
||||
#include <LibWeb/CSS/ComputedValues.h>
|
||||
#include <LibWeb/Forward.h>
|
||||
#include <LibWeb/InvalidateDisplayList.h>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2022-2023, Sam Atkins <atkinssj@serenityos.org>
|
||||
* Copyright (c) 2022-2025, Sam Atkins <sam@ladybird.org>
|
||||
* Copyright (c) 2024, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
||||
* Copyright (c) 2025, Jelle Raaijmakers <jelle@ladybird.org>
|
||||
*
|
||||
|
|
|
@ -194,7 +194,7 @@ public:
|
|||
Function<void(URL::URL const&)> on_load_finish;
|
||||
Function<void(ByteString const& path, i32)> on_request_file;
|
||||
Function<void(Gfx::Bitmap const&)> on_favicon_change;
|
||||
Function<void(Gfx::StandardCursor)> on_cursor_change;
|
||||
Function<void(Gfx::Cursor const&)> on_cursor_change;
|
||||
Function<void(Gfx::IntPoint, ByteString const&)> on_request_tooltip_override;
|
||||
Function<void()> on_stop_tooltip_override;
|
||||
Function<void(ByteString const&)> on_enter_tooltip_area;
|
||||
|
|
|
@ -123,16 +123,11 @@ void WebContentClient::did_request_refresh(u64 page_id)
|
|||
view->reload();
|
||||
}
|
||||
|
||||
void WebContentClient::did_request_cursor_change(u64 page_id, i32 cursor_type)
|
||||
void WebContentClient::did_request_cursor_change(u64 page_id, Gfx::Cursor const& cursor)
|
||||
{
|
||||
if (cursor_type < 0 || cursor_type >= (i32)Gfx::StandardCursor::__Count) {
|
||||
dbgln("DidRequestCursorChange: Bad cursor type");
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto view = view_for_page_id(page_id); view.has_value()) {
|
||||
if (view->on_cursor_change)
|
||||
view->on_cursor_change(static_cast<Gfx::StandardCursor>(cursor_type));
|
||||
view->on_cursor_change(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ private:
|
|||
virtual void did_paint(u64 page_id, Gfx::IntRect const&, i32) override;
|
||||
virtual void did_finish_loading(u64 page_id, URL::URL const&) override;
|
||||
virtual void did_request_refresh(u64 page_id) override;
|
||||
virtual void did_request_cursor_change(u64 page_id, i32) override;
|
||||
virtual void did_request_cursor_change(u64 page_id, Gfx::Cursor const&) override;
|
||||
virtual void did_change_title(u64 page_id, ByteString const&) override;
|
||||
virtual void did_change_url(u64 page_id, URL::URL const&) override;
|
||||
virtual void did_request_tooltip_override(u64 page_id, Gfx::IntPoint, ByteString const&) override;
|
||||
|
|
|
@ -251,9 +251,9 @@ void PageClient::set_viewport_size(Web::DevicePixelSize const& size)
|
|||
m_pending_set_browser_zoom_request = false;
|
||||
}
|
||||
|
||||
void PageClient::page_did_request_cursor_change(Gfx::StandardCursor cursor)
|
||||
void PageClient::page_did_request_cursor_change(Gfx::Cursor const& cursor)
|
||||
{
|
||||
client().async_did_request_cursor_change(m_id, (u32)cursor);
|
||||
client().async_did_request_cursor_change(m_id, cursor);
|
||||
}
|
||||
|
||||
void PageClient::page_did_layout()
|
||||
|
|
|
@ -114,7 +114,7 @@ private:
|
|||
virtual Web::CSS::PreferredColorScheme preferred_color_scheme() const override { return m_preferred_color_scheme; }
|
||||
virtual Web::CSS::PreferredContrast preferred_contrast() const override { return m_preferred_contrast; }
|
||||
virtual Web::CSS::PreferredMotion preferred_motion() const override { return m_preferred_motion; }
|
||||
virtual void page_did_request_cursor_change(Gfx::StandardCursor) override;
|
||||
virtual void page_did_request_cursor_change(Gfx::Cursor const&) override;
|
||||
virtual void page_did_layout() override;
|
||||
virtual void page_did_change_title(ByteString const&) override;
|
||||
virtual void page_did_change_url(URL::URL const&) override;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include <LibCore/AnonymousBuffer.h>
|
||||
#include <LibGfx/Color.h>
|
||||
#include <LibGfx/Cursor.h>
|
||||
#include <LibGfx/ShareableBitmap.h>
|
||||
#include <LibURL/URL.h>
|
||||
#include <LibWeb/Cookie/Cookie.h>
|
||||
|
@ -24,7 +25,7 @@ endpoint WebContentClient
|
|||
did_finish_loading(u64 page_id, URL::URL url) =|
|
||||
did_request_refresh(u64 page_id) =|
|
||||
did_paint(u64 page_id, Gfx::IntRect content_rect, i32 bitmap_id) =|
|
||||
did_request_cursor_change(u64 page_id, i32 cursor_type) =|
|
||||
did_request_cursor_change(u64 page_id, Gfx::Cursor cursor) =|
|
||||
did_change_title(u64 page_id, ByteString title) =|
|
||||
did_change_url(u64 page_id, URL::URL url) =|
|
||||
did_request_tooltip_override(u64 page_id, Gfx::IntPoint position, ByteString title) =|
|
||||
|
|
|
@ -2,8 +2,8 @@ Harness status: OK
|
|||
|
||||
Found 42 tests
|
||||
|
||||
36 Pass
|
||||
6 Fail
|
||||
40 Pass
|
||||
2 Fail
|
||||
Pass e.style['cursor'] = "auto" should set the property value
|
||||
Pass e.style['cursor'] = "default" should set the property value
|
||||
Pass e.style['cursor'] = "none" should set the property value
|
||||
|
@ -40,9 +40,9 @@ Pass e.style['cursor'] = "row-resize" should set the property value
|
|||
Pass e.style['cursor'] = "all-scroll" should set the property value
|
||||
Pass e.style['cursor'] = "zoom-in" should set the property value
|
||||
Pass e.style['cursor'] = "zoom-out" should set the property value
|
||||
Fail e.style['cursor'] = "url(\"https://example.com/\"), alias" should set the property value
|
||||
Fail e.style['cursor'] = "url(\"https://example.com/\") 1 calc(2 + 0), copy" should set the property value
|
||||
Fail e.style['cursor'] = "url(\"https://example.com/\"), url(\"https://example.com/\") 3 -4, move" should set the property value
|
||||
Fail e.style['cursor'] = "url(\"https://example.com/\") 5 6, grab" should set the property value
|
||||
Pass e.style['cursor'] = "url(\"https://example.com/\"), alias" should set the property value
|
||||
Pass e.style['cursor'] = "url(\"https://example.com/\") 1 calc(2 + 0), copy" should set the property value
|
||||
Pass e.style['cursor'] = "url(\"https://example.com/\"), url(\"https://example.com/\") 3 -4, move" should set the property value
|
||||
Pass e.style['cursor'] = "url(\"https://example.com/\") 5 6, grab" should set the property value
|
||||
Fail e.style['cursor'] = "image-set(\"https://example.com/\" 1x) 5 6, grab" should set the property value
|
||||
Fail e.style['cursor'] = "image-set(\"https://example.com/\" 1x, \"https://example.com/highres\" 2x) 5 6, grab" should set the property value
|
|
@ -450,7 +450,15 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
|
|||
if (self == nil) {
|
||||
return;
|
||||
}
|
||||
if (cursor == Gfx::StandardCursor::Hidden) {
|
||||
if (!cursor.template has<Gfx::StandardCursor>()) {
|
||||
// FIXME: Implement image cursors in AppKit.
|
||||
[[NSCursor arrowCursor] set];
|
||||
return;
|
||||
}
|
||||
|
||||
auto standard_cursor = cursor.template get<Gfx::StandardCursor>();
|
||||
|
||||
if (standard_cursor == Gfx::StandardCursor::Hidden) {
|
||||
if (!m_hidden_cursor.has_value()) {
|
||||
m_hidden_cursor.emplace();
|
||||
}
|
||||
|
@ -460,7 +468,7 @@ static void copy_data_to_clipboard(StringView data, NSPasteboardType pasteboard_
|
|||
|
||||
m_hidden_cursor.clear();
|
||||
|
||||
switch (cursor) {
|
||||
switch (standard_cursor) {
|
||||
case Gfx::StandardCursor::Arrow:
|
||||
[[NSCursor arrowCursor] set];
|
||||
break;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* Copyright (c) 2022-2023, Andreas Kling <andreas@ladybird.org>
|
||||
* Copyright (c) 2023, Linus Groh <linusg@serenityos.org>
|
||||
* Copyright (c) 2024-2025, Sam Atkins <sam@ladybird.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -630,67 +631,86 @@ void WebContentView::initialize_client(WebView::ViewImplementation::CreateNewCli
|
|||
update_screen_rects();
|
||||
}
|
||||
|
||||
void WebContentView::update_cursor(Gfx::StandardCursor cursor)
|
||||
void WebContentView::update_cursor(Gfx::Cursor cursor)
|
||||
{
|
||||
switch (cursor) {
|
||||
case Gfx::StandardCursor::Hidden:
|
||||
setCursor(Qt::BlankCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Arrow:
|
||||
setCursor(Qt::ArrowCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Crosshair:
|
||||
setCursor(Qt::CrossCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::IBeam:
|
||||
setCursor(Qt::IBeamCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::ResizeHorizontal:
|
||||
setCursor(Qt::SizeHorCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::ResizeVertical:
|
||||
setCursor(Qt::SizeVerCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::ResizeDiagonalTLBR:
|
||||
setCursor(Qt::SizeFDiagCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::ResizeDiagonalBLTR:
|
||||
setCursor(Qt::SizeBDiagCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::ResizeColumn:
|
||||
setCursor(Qt::SplitHCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::ResizeRow:
|
||||
setCursor(Qt::SplitVCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Hand:
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Help:
|
||||
setCursor(Qt::WhatsThisCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Drag:
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::DragCopy:
|
||||
setCursor(Qt::DragCopyCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Move:
|
||||
setCursor(Qt::DragMoveCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Wait:
|
||||
setCursor(Qt::BusyCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Disallowed:
|
||||
setCursor(Qt::ForbiddenCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Eyedropper:
|
||||
case Gfx::StandardCursor::Zoom:
|
||||
// FIXME: No corresponding Qt cursors, default to Arrow
|
||||
default:
|
||||
setCursor(Qt::ArrowCursor);
|
||||
break;
|
||||
}
|
||||
cursor.visit([this](Gfx::StandardCursor standard_cursor) {
|
||||
switch (standard_cursor) {
|
||||
case Gfx::StandardCursor::Hidden:
|
||||
setCursor(Qt::BlankCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Arrow:
|
||||
setCursor(Qt::ArrowCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Crosshair:
|
||||
setCursor(Qt::CrossCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::IBeam:
|
||||
setCursor(Qt::IBeamCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::ResizeHorizontal:
|
||||
setCursor(Qt::SizeHorCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::ResizeVertical:
|
||||
setCursor(Qt::SizeVerCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::ResizeDiagonalTLBR:
|
||||
setCursor(Qt::SizeFDiagCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::ResizeDiagonalBLTR:
|
||||
setCursor(Qt::SizeBDiagCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::ResizeColumn:
|
||||
setCursor(Qt::SplitHCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::ResizeRow:
|
||||
setCursor(Qt::SplitVCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Hand:
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Help:
|
||||
setCursor(Qt::WhatsThisCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Drag:
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::DragCopy:
|
||||
setCursor(Qt::DragCopyCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Move:
|
||||
setCursor(Qt::DragMoveCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Wait:
|
||||
setCursor(Qt::BusyCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Disallowed:
|
||||
setCursor(Qt::ForbiddenCursor);
|
||||
break;
|
||||
case Gfx::StandardCursor::Eyedropper:
|
||||
case Gfx::StandardCursor::Zoom:
|
||||
// FIXME: No corresponding Qt cursors, default to Arrow
|
||||
default:
|
||||
setCursor(Qt::ArrowCursor);
|
||||
break;
|
||||
} },
|
||||
[this](Gfx::ImageCursor const& image_cursor) {
|
||||
if (!image_cursor.bitmap.is_valid()) {
|
||||
dbgln("Failed to set cursor: Bitmap is invalid.");
|
||||
return;
|
||||
}
|
||||
auto const& bitmap = *image_cursor.bitmap.bitmap();
|
||||
auto qimage = QImage { bitmap.scanline_u8(0), bitmap.width(), bitmap.height(), QImage::Format_ARGB32 };
|
||||
if (qimage.isNull()) {
|
||||
dbgln("Failed to set cursor: Null QImage.");
|
||||
return;
|
||||
}
|
||||
auto qpixmap = QPixmap::fromImage(qimage);
|
||||
if (qimage.isNull()) {
|
||||
dbgln("Failed to set cursor: Couldn't create QPixmap from QImage.");
|
||||
return;
|
||||
}
|
||||
setCursor(QCursor { qpixmap, image_cursor.hotspot.x(), image_cursor.hotspot.y() });
|
||||
});
|
||||
}
|
||||
|
||||
Web::DevicePixelSize WebContentView::viewport_size() const
|
||||
|
|
|
@ -100,7 +100,7 @@ private:
|
|||
virtual Gfx::IntPoint to_widget_position(Gfx::IntPoint content_position) const override;
|
||||
|
||||
void update_viewport_size();
|
||||
void update_cursor(Gfx::StandardCursor cursor);
|
||||
void update_cursor(Gfx::Cursor cursor);
|
||||
|
||||
void enqueue_native_event(Web::MouseEvent::Type, QSinglePointEvent const& event);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue