idlize
1/*
2* Copyright (c) 2022-2024 Huawei Device Co., Ltd.
3* Licensed under the Apache License, Version 2.0 (the "License");
4* you may not use this file except in compliance with the License.
5* You may obtain a copy of the License at
6*
7* http://www.apache.org/licenses/LICENSE-2.0
8*
9* Unless required by applicable law or agreed to in writing, software
10* distributed under the License is distributed on an "AS IS" BASIS,
11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12* See the License for the specific language governing permissions and
13* limitations under the License.
14*/
15
16import { float64, uint32 } from "@koalaui/common"17import { EasingSupport } from "./EasingSupport"18
19/**
20* Easing functions specify the rate of change of a parameter over time.
21* Our animations call them with input value in the ranging [0..1].
22* Usually easing functions return a value in the same range,
23* but some of them may exceed that range.
24*/
25export type EasingCurve = (value: float64) => float6426
27/**
28* This namespace provides predefined easing functions
29* to use with {@link animation} and {@link transition}.
30*/
31export class Easing {32static readonly Linear: EasingCurve = (value: float64) => value33static readonly LinearReversed: EasingCurve = (value: float64) => 1 - value34static readonly EaseInSine: EasingCurve = (value: float64) => 1 - Math.cos(value * Math.PI / 2)35static readonly EaseOutSine: EasingCurve = (value: float64) => Math.sin(value * Math.PI / 2)36static readonly EaseInOutSine: EasingCurve = (value: float64) => (1 - Math.cos(value * Math.PI)) / 237static readonly Ease = Easing.cubicBezier(.25, .1, .25, 1) // https://cubic-bezier.com/#.25,.1,.25,138static readonly EaseIn = Easing.cubicBezier(.42, 0, 1, 1) // https://cubic-bezier.com/#.42,0,1,139static readonly EaseOut = Easing.cubicBezier(0, 0, .58, 1) // https://cubic-bezier.com/#0,0,.58,140static readonly EaseInOut = Easing.cubicBezier(.42, 0, .58, 1) // https://cubic-bezier.com/#.42,0,.58,141static readonly ViscousFluid = Easing.viscousFluid()42static readonly Bubble = Easing.cubicBezier(.32, 2.2, .72, 2.2) // https://cubic-bezier.com/#.32,2.2,.72,2.243
44/**45* @param easing - an easing function to invert
46* @returns easing function that reflects all points of the original shape
47* through the central point (0.5;0.5)
48*/
49static inverted(easing: EasingCurve): EasingCurve {50return (value: float64) => 1 - easing(1 - value)51}52
53/**54* @param easing - an easing function to reverse
55* @returns easing function that reflects all points of the original shape
56* through the central vertical axis (0.5)
57*/
58static reversed(easing: EasingCurve): EasingCurve {59return (value: float64) => easing(1 - value)60}61
62/**63* @param easing - an easing function to modify
64* @returns easing function that returns minimal value instead of maximal one
65* @internal
66*/
67static restarted(easing: EasingCurve): EasingCurve {68return (value: float64) => easing(value < 1 ? value : 0)69}70
71/**72* @param easing - an easing function to join
73* @param count - an amount of iterations of the specified function
74* @returns easing function that repeats the specified one several times
75*/
76static repeated(easing: EasingCurve, count: uint32): EasingCurve {77if (count == 1) return easing78if (!Number.isInteger(count) || (count < 1)) throw new Error("unexpected iteration count: " + count)79return (value: float64) => {80if (value <= 0) return easing(0)81if (value < 1) {82value *= count83const index = Math.floor(value)84if (index < count) return easing(value - index)85}86return easing(1)87}88}89
90/**91* @param easing - easing functions to join
92* @returns easing function that applies specified functions one by one
93*/
94static joined(...easing: EasingCurve[]): EasingCurve {95if (easing.length == 0) throw new Error("no easing functions to join")96if (easing.length == 1) return easing[0]97return (value: float64) => {98if (value <= 0) return easing[0](0)99if (value < 1) {100value *= easing.length101const index = Math.floor(value)102if (index < easing.length) return easing[index as uint32](value - index)103}104return easing[easing.length - 1](1)105}106}107
108/**109* @param easing - an easing function to join
110* @returns easing function that joins the specified function with its reversed variant
111*/
112static thereAndBackAgain(easing: EasingCurve): EasingCurve {113return (value: float64) => value < 0.5 ? easing(2 * value) : easing(2 - 2 * value)114}115
116/**117* @param p1x the horizontal position of the first point
118* @param p1y the vertical position of the first point
119* @param p2x the horizontal position of the second point
120* @param p2y the vertical position of the second point
121* @returns easing function based on the cubic bezier curve
122*/
123static cubicBezier(p1x: float64, p1y: float64, p2x: float64, p2y: float64): EasingCurve {124const matrix = EasingSupport.newCubicBezier(p1x, p1y, p2x, p2y)125return (value: float64) => matrix.convert(value)126}127
128/**129* @param stops - a number of frames with constant values
130* @param jump - a preferred method to generate these values
131* @returns easing function that displays an animation iteration along the specified stops,
132* displaying each stop for equal lengths of time
133* @see EasingStepJump
134*/
135static steps(stops: uint32, jump:EasingStepJump = EasingStepJump.None): EasingCurve {136if (stops == 1) {137switch (jump) {138case EasingStepJump.Start: return (value: float64) => 1.0139case EasingStepJump.End: return (value: float64) => 0.0140case EasingStepJump.Both: return (value: float64) => 0.5141case EasingStepJump.None: throw new Error("easing with one stop must use jump other than EasingStepJump.None")142}143}144if (!Number.isInteger(stops) || stops <= 1) throw new Error("easing stops must be a positive integer, but is " + stops)145const delta = jump == EasingStepJump.None || jump == EasingStepJump.End ? 0 : 1146const count: uint32 = jump == EasingStepJump.None ? stops - 1 : jump == EasingStepJump.Both ? stops + 1 : stops147return (value: float64) => (value > 0 ? Math.min(stops - 1, Math.floor(stops * value)) + delta : delta) / count148}149
150static viscousFluid(v0: float64 = 1, f: float64 = 3): EasingCurve {151return (value: float64) => (v0 / (-4.2) * f) * (Math.exp((-4.2) * value * f) - 1)152}153}
154
155// https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timing-function
156export enum EasingStepJump {157/** Denotes a left-continuous function, so that the first jump happens when the animation begins. */158Start,159/** Denotes a right-continuous function, so that the last jump happens when the animation ends. */160End,161/** There is no jump on either end. Instead, holding at both the 0% mark and the 100% mark, each for 1/n of the duration. */162None,163/** Includes pauses at both the 0% and 100% marks, effectively adding a step during the animation iteration. */164Both,165}
166