diff --git a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp index cf21f3187d..64c9f678e5 100644 --- a/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp +++ b/Meta/Lagom/Tools/CodeGenerators/LibWeb/BindingsGenerator/IDLGenerators.cpp @@ -27,6 +27,8 @@ static bool is_platform_object(Type const& type) // might simply need to add another type here. static constexpr Array types = { "AbortSignal"sv, + "AnimationEffect"sv, + "AnimationTimeline"sv, "Attr"sv, "AudioTrack"sv, "Blob"sv, @@ -3499,7 +3501,9 @@ void generate_constructor_implementation(IDL::Interface const& interface, String #include #include #include -#if __has_include() +#if __has_include() +# include +#elif __has_include() # include #elif __has_include() # include diff --git a/Userland/Libraries/LibWeb/Animations/AnimationEffect.cpp b/Userland/Libraries/LibWeb/Animations/AnimationEffect.cpp new file mode 100644 index 0000000000..5d0ac80e4a --- /dev/null +++ b/Userland/Libraries/LibWeb/Animations/AnimationEffect.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2023, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include + +namespace Web::Animations { + +JS::NonnullGCPtr AnimationEffect::create(JS::Realm& realm) +{ + return realm.heap().allocate(realm, realm); +} + +OptionalEffectTiming EffectTiming::to_optional_effect_timing() const +{ + return { + .delay = delay, + .end_delay = end_delay, + .fill = fill, + .iteration_start = iteration_start, + .iterations = iterations, + .duration = duration, + .direction = direction, + .easing = easing, + }; +} + +// https://www.w3.org/TR/web-animations-1/#dom-animationeffect-gettiming +EffectTiming AnimationEffect::get_timing() const +{ + // 1. Returns the specified timing properties for this animation effect. + return { + .delay = m_start_delay, + .end_delay = m_end_delay, + .fill = m_fill_mode, + .iteration_start = m_iteration_start, + .iterations = m_iteration_count, + .duration = m_iteration_duration, + .direction = m_playback_direction, + .easing = m_easing_function, + }; +} + +// https://www.w3.org/TR/web-animations-1/#dom-animationeffect-getcomputedtiming +ComputedEffectTiming AnimationEffect::get_computed_timing() const +{ + // 1. Returns the calculated timing properties for this animation effect. + + // Note: Although some of the attributes of the object returned by getTiming() and getComputedTiming() are common, + // their values may differ in the following ways: + + // - duration: while getTiming() may return the string auto, getComputedTiming() must return a number + // corresponding to the calculated value of the iteration duration as defined in the description of the + // duration member of the EffectTiming interface. + // + // In this level of the specification, that simply means that an auto value is replaced by zero. + auto duration = m_iteration_duration.has() ? 0.0 : m_iteration_duration.get(); + + // - fill: likewise, while getTiming() may return the string auto, getComputedTiming() must return the specific + // FillMode used for timing calculations as defined in the description of the fill member of the EffectTiming + // interface. + // + // In this level of the specification, that simply means that an auto value is replaced by the none FillMode. + auto fill = m_fill_mode == Bindings::FillMode::Auto ? Bindings::FillMode::None : m_fill_mode; + + return { + { + .delay = m_start_delay, + .end_delay = m_end_delay, + .fill = fill, + .iteration_start = m_iteration_start, + .iterations = m_iteration_count, + .duration = duration, + .direction = m_playback_direction, + .easing = m_easing_function, + }, + + // FIXME: + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + }; +} + +// https://www.w3.org/TR/web-animations-1/#dom-animationeffect-updatetiming +// https://www.w3.org/TR/web-animations-1/#update-the-timing-properties-of-an-animation-effect +WebIDL::ExceptionOr AnimationEffect::update_timing(OptionalEffectTiming timing) +{ + // 1. If the iterationStart member of input exists and is less than zero, throw a TypeError and abort this + // procedure. + if (timing.iteration_start.has_value() && timing.iteration_start.value() < 0.0) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid iteration start value"sv }; + + // 2. If the iterations member of input exists, and is less than zero or is the value NaN, throw a TypeError and + // abort this procedure. + if (timing.iterations.has_value() && (timing.iterations.value() < 0.0 || isnan(timing.iterations.value()))) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid iteration count value"sv }; + + // 3. If the duration member of input exists, and is less than zero or is the value NaN, throw a TypeError and + // abort this procedure. + // Note: "auto", the only valid string value, is treated as 0. + auto& duration = timing.duration; + if (duration.has_value() && duration->has() && (duration->get() < 0.0 || isnan(duration->get()))) + return WebIDL::SimpleException { WebIDL::SimpleExceptionType::TypeError, "Invalid duration value"sv }; + + // FIXME: + // 4. If the easing member of input exists but cannot be parsed using the production + // [CSS-EASING-1], throw a TypeError and abort this procedure. + + // 5. Assign each member that exists in input to the corresponding timing property of effect as follows: + + // - delay → start delay + if (timing.delay.has_value()) + m_start_delay = timing.delay.value(); + + // - endDelay → end delay + if (timing.end_delay.has_value()) + m_end_delay = timing.end_delay.value(); + + // - fill → fill mode + if (timing.fill.has_value()) + m_fill_mode = timing.fill.value(); + + // - iterationStart → iteration start + if (timing.iteration_start.has_value()) + m_iteration_start = timing.iteration_start.value(); + + // - iterations → iteration count + if (timing.iterations.has_value()) + m_iteration_count = timing.iterations.value(); + + // - duration → iteration duration + if (timing.duration.has_value()) + m_iteration_duration = timing.duration.value(); + + // - direction → playback direction + if (timing.direction.has_value()) + m_playback_direction = timing.direction.value(); + + // - easing → timing function + if (timing.easing.has_value()) + m_easing_function = timing.easing.value(); + + return {}; +} + +AnimationEffect::AnimationEffect(JS::Realm& realm) + : Bindings::PlatformObject(realm) +{ +} + +void AnimationEffect::initialize(JS::Realm& realm) +{ + Base::initialize(realm); + set_prototype(&Bindings::ensure_web_prototype(realm, "AnimationEffect")); +} + +} diff --git a/Userland/Libraries/LibWeb/Animations/AnimationEffect.h b/Userland/Libraries/LibWeb/Animations/AnimationEffect.h new file mode 100644 index 0000000000..3634613835 --- /dev/null +++ b/Userland/Libraries/LibWeb/Animations/AnimationEffect.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2023, Matthew Olsson + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::Animations { + +// // https://www.w3.org/TR/web-animations-1/#the-effecttiming-dictionaries +struct OptionalEffectTiming { + Optional delay {}; + Optional end_delay {}; + Optional fill {}; + Optional iteration_start {}; + Optional iterations {}; + Optional> duration; + Optional direction {}; + Optional easing {}; +}; + +// // https://www.w3.org/TR/web-animations-1/#the-effecttiming-dictionaries +struct EffectTiming { + double delay { 0 }; + double end_delay { 0 }; + Bindings::FillMode fill { Bindings::FillMode::Auto }; + double iteration_start { 0.0 }; + double iterations { 1.0 }; + Variant duration { "auto"_string }; + Bindings::PlaybackDirection direction { Bindings::PlaybackDirection::Normal }; + String easing { "linear"_string }; + + OptionalEffectTiming to_optional_effect_timing() const; +}; + +// https://www.w3.org/TR/web-animations-1/#the-computedeffecttiming-dictionary +struct ComputedEffectTiming : public EffectTiming { + double end_time; + double active_duration; + Optional local_time; + Optional progress; + Optional current_iteration; +}; + +// https://www.w3.org/TR/web-animations-1/#the-animationeffect-interface +class AnimationEffect : public Bindings::PlatformObject { + WEB_PLATFORM_OBJECT(AnimationEffect, Bindings::PlatformObject); + +public: + static JS::NonnullGCPtr create(JS::Realm&); + + EffectTiming get_timing() const; + ComputedEffectTiming get_computed_timing() const; + WebIDL::ExceptionOr update_timing(OptionalEffectTiming timing = {}); + + double start_delay() const { return m_start_delay; } + void set_start_delay(double start_delay) { m_start_delay = start_delay; } + + double end_delay() const { return m_end_delay; } + void set_end_delay(double end_delay) { m_end_delay = end_delay; } + + Bindings::FillMode fill_mode() const { return m_fill_mode; } + void set_fill_mode(Bindings::FillMode fill_mode) { m_fill_mode = fill_mode; } + + double iteration_start() const { return m_iteration_start; } + void set_iteration_start(double iteration_start) { m_iteration_start = iteration_start; } + + double iteration_count() const { return m_iteration_count; } + void set_iteration_count(double iteration_count) { m_iteration_count = iteration_count; } + + Variant const& iteration_duration() const { return m_iteration_duration; } + void set_iteration_duration(Variant iteration_duration) { m_iteration_duration = move(iteration_duration); } + + Bindings::PlaybackDirection playback_direction() const { return m_playback_direction; } + void set_playback_direction(Bindings::PlaybackDirection playback_direction) { m_playback_direction = playback_direction; } + + String const& easing_function() const { return m_easing_function; } + void set_easing_function(String easing_function) { m_easing_function = move(easing_function); } + +protected: + AnimationEffect(JS::Realm&); + + virtual void initialize(JS::Realm&) override; + + // https://www.w3.org/TR/web-animations-1/#start-delay + double m_start_delay { 0.0 }; + + // https://www.w3.org/TR/web-animations-1/#end-delay + double m_end_delay { 0.0 }; + + // https://www.w3.org/TR/web-animations-1/#fill-mode + Bindings::FillMode m_fill_mode { Bindings::FillMode::Auto }; + + // https://www.w3.org/TR/web-animations-1/#iteration-start + double m_iteration_start { 0.0 }; + + // https://www.w3.org/TR/web-animations-1/#iteration-count + double m_iteration_count { 1.0 }; + + // https://www.w3.org/TR/web-animations-1/#iteration-duration + Variant m_iteration_duration { 0.0 }; + + // https://www.w3.org/TR/web-animations-1/#playback-direction + Bindings::PlaybackDirection m_playback_direction { Bindings::PlaybackDirection::Normal }; + + // https://www.w3.org/TR/css-easing-1/#easing-function + String m_easing_function { "linear"_string }; +}; + +} diff --git a/Userland/Libraries/LibWeb/Animations/AnimationEffect.idl b/Userland/Libraries/LibWeb/Animations/AnimationEffect.idl new file mode 100644 index 0000000000..29f9fbc13f --- /dev/null +++ b/Userland/Libraries/LibWeb/Animations/AnimationEffect.idl @@ -0,0 +1,46 @@ +// https://www.w3.org/TR/web-animations-1/#the-effecttiming-dictionaries +dictionary EffectTiming { + double delay = 0; + double endDelay = 0; + FillMode fill = "auto"; + double iterationStart = 0.0; + unrestricted double iterations = 1.0; + (unrestricted double or DOMString) duration = "auto"; + PlaybackDirection direction = "normal"; + DOMString easing = "linear"; +}; + +// https://www.w3.org/TR/web-animations-1/#dictdef-optionaleffecttiming +dictionary OptionalEffectTiming { + double delay; + double endDelay; + FillMode fill; + double iterationStart; + unrestricted double iterations; + (unrestricted double or DOMString) duration; + PlaybackDirection direction; + DOMString easing; +}; + +// https://www.w3.org/TR/web-animations-1/#the-fillmode-enumeration +enum FillMode { "none", "forwards", "backwards", "both", "auto" }; + +// https://www.w3.org/TR/web-animations-1/#the-playbackdirection-enumeration +enum PlaybackDirection { "normal", "reverse", "alternate", "alternate-reverse" }; + +// https://www.w3.org/TR/web-animations-1/#the-computedeffecttiming-dictionary +dictionary ComputedEffectTiming : EffectTiming { + unrestricted double endTime; + unrestricted double activeDuration; + double? localTime; + double? progress; + unrestricted double? currentIteration; +}; + +// https://www.w3.org/TR/web-animations-1/#the-animationeffect-interface +[Exposed=Window] +interface AnimationEffect { + EffectTiming getTiming(); + ComputedEffectTiming getComputedTiming(); + undefined updateTiming(optional OptionalEffectTiming timing = {}); +}; diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 8638d42ab9..c361985ea8 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -2,6 +2,7 @@ include(libweb_generators) include(accelerated_graphics) set(SOURCES + Animations/AnimationEffect.cpp Animations/AnimationTimeline.cpp Animations/DocumentTimeline.cpp ARIA/AriaData.cpp diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 150d859c55..e679129858 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -27,6 +27,7 @@ class RecordingPainter; } namespace Web::Animations { +class AnimationEffect; class AnimationTimeline; class DocumentTimeline; } diff --git a/Userland/Libraries/LibWeb/idl_files.cmake b/Userland/Libraries/LibWeb/idl_files.cmake index bf200d5d99..49f4e79f7b 100644 --- a/Userland/Libraries/LibWeb/idl_files.cmake +++ b/Userland/Libraries/LibWeb/idl_files.cmake @@ -1,6 +1,7 @@ # This file is included from "Meta/CMake/libweb_data.cmake" # It is defined here so that there is no need to go to the Meta directory when adding new idl files +libweb_js_bindings(Animations/AnimationEffect) libweb_js_bindings(Animations/AnimationTimeline) libweb_js_bindings(Animations/DocumentTimeline) libweb_js_bindings(Crypto/Crypto)