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:
Sam Atkins 2025-02-20 12:17:29 +00:00 committed by Andreas Kling
parent fd2414ba35
commit bfd7ac1204
Notes: github-actions[bot] 2025-02-28 12:51:27 +00:00
20 changed files with 297 additions and 170 deletions

View file

@ -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)
{