-module(etui@anim).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/etui/anim.gleam").
-export([anim_new/0, tick/1, reset/1, is_done/2, lerp/4, ease_out/4, ease_in/4, oscillate/4, blink/2, cycle/2, ease_in_out/4, interpolate/5, sequence/3]).
-export_type([anim_state/0, easing/0, keyframe/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type anim_state() :: {anim_state, integer()}.
-type easing() :: linear | ease_in | ease_out | ease_in_out.
-type keyframe() :: {keyframe, integer(), integer()}.
-file("src/etui/anim.gleam", 11).
-spec anim_new() -> anim_state().
anim_new() ->
{anim_state, 0}.
-file("src/etui/anim.gleam", 15).
-spec tick(anim_state()) -> anim_state().
tick(State) ->
{anim_state, erlang:element(2, State) + 1}.
-file("src/etui/anim.gleam", 19).
-spec reset(anim_state()) -> anim_state().
reset(_) ->
{anim_state, 0}.
-file("src/etui/anim.gleam", 23).
-spec is_done(anim_state(), integer()) -> boolean().
is_done(State, Duration) ->
erlang:element(2, State) >= Duration.
-file("src/etui/anim.gleam", 31).
?DOC(" Linear interpolation from `start` to `end_` over `duration` frames.\n").
-spec lerp(integer(), integer(), integer(), integer()) -> integer().
lerp(Start, End_, Frame, Duration) ->
case Duration =< 0 of
true ->
End_;
false ->
T = gleam@int:clamp(Frame, 0, Duration),
Start + (case Duration of
0 -> 0;
Gleam@denominator -> (End_ - Start) * T div Gleam@denominator
end)
end.
-file("src/etui/anim.gleam", 42).
?DOC(" EaseOut (fast start, slow end). Quadratic approximation with integers.\n").
-spec ease_out(integer(), integer(), integer(), integer()) -> integer().
ease_out(Start, End_, Frame, Duration) ->
case Duration =< 0 of
true ->
End_;
false ->
T = case Duration of
0 -> 0;
Gleam@denominator -> gleam@int:clamp(Frame, 0, Duration) * 100
div Gleam@denominator
end,
Curve = (T * (200 - T)) div 100,
Start + (((End_ - Start) * Curve) div 100)
end.
-file("src/etui/anim.gleam", 54).
?DOC(" EaseIn (slow start, fast end). Quadratic approximation.\n").
-spec ease_in(integer(), integer(), integer(), integer()) -> integer().
ease_in(Start, End_, Frame, Duration) ->
case Duration =< 0 of
true ->
End_;
false ->
T = case Duration of
0 -> 0;
Gleam@denominator -> gleam@int:clamp(Frame, 0, Duration) * 100
div Gleam@denominator
end,
Curve = (T * T) div 100,
Start + (((End_ - Start) * Curve) div 100)
end.
-file("src/etui/anim.gleam", 67).
?DOC(
" Oscillate between `min` and `max` with a given `period` (in frames).\n"
" Returns current value in the triangle wave.\n"
).
-spec oscillate(integer(), integer(), integer(), integer()) -> integer().
oscillate(Min, Max, Frame, Period) ->
case Period =< 0 of
true ->
Min;
false ->
Range = Max - Min,
Half = Period div 2,
Pos = case Period of
0 -> 0;
Gleam@denominator -> Frame rem Gleam@denominator
end,
case Pos < Half of
true ->
Min + (case gleam@int:max(1, Half) of
0 -> 0;
Gleam@denominator@1 -> Range * Pos div Gleam@denominator@1
end);
false ->
Max - (case gleam@int:max(1, Period - Half) of
0 -> 0;
Gleam@denominator@2 -> Range * (Pos - Half) div Gleam@denominator@2
end)
end
end.
-file("src/etui/anim.gleam", 84).
?DOC(
" Returns True during the \"on\" half of each blink period.\n"
" `period` ≤ 0 means always on.\n"
).
-spec blink(integer(), integer()) -> boolean().
blink(Frame, Period) ->
case Period =< 0 of
true ->
true;
false ->
(case Period of
0 -> 0;
Gleam@denominator -> Frame rem Gleam@denominator
end) < (Period div 2)
end.
-file("src/etui/anim.gleam", 92).
?DOC(" Cycle through [0, count) returning the current index for the given frame.\n").
-spec cycle(integer(), integer()) -> integer().
cycle(Frame, Count) ->
case Count =< 0 of
true ->
0;
false ->
case Count of
0 -> 0;
Gleam@denominator -> Frame rem Gleam@denominator
end
end.
-file("src/etui/anim.gleam", 126).
?DOC(" EaseInOut (slow start, fast middle, slow end). Quadratic approximation.\n").
-spec ease_in_out(integer(), integer(), integer(), integer()) -> integer().
ease_in_out(Start, End_, Frame, Duration) ->
case Duration =< 0 of
true ->
End_;
false ->
T = case Duration of
0 -> 0;
Gleam@denominator -> gleam@int:clamp(Frame, 0, Duration) * 100
div Gleam@denominator
end,
Curve = case T < 50 of
true ->
(T * T) div 50;
false ->
Inv = 100 - T,
100 - ((Inv * Inv) div 50)
end,
Start + (((End_ - Start) * Curve) div 100)
end.
-file("src/etui/anim.gleam", 110).
?DOC(" Unified interpolation with selectable easing curve.\n").
-spec interpolate(integer(), integer(), integer(), integer(), easing()) -> integer().
interpolate(Start, End_, Frame, Duration, Easing) ->
case Easing of
linear ->
lerp(Start, End_, Frame, Duration);
ease_in ->
ease_in(Start, End_, Frame, Duration);
ease_out ->
ease_out(Start, End_, Frame, Duration);
ease_in_out ->
ease_in_out(Start, End_, Frame, Duration)
end.
-file("src/etui/anim.gleam", 158).
-spec find_segment(list(keyframe()), integer(), easing()) -> integer().
find_segment(Kfs, Frame, Easing) ->
case Kfs of
[] ->
0;
[{keyframe, _, V}] ->
V;
[{keyframe, At1, V1}, {keyframe, At2, V2} | Rest] ->
case Frame < At2 of
true ->
interpolate(V1, V2, Frame - At1, At2 - At1, Easing);
false ->
find_segment([{keyframe, At2, V2} | Rest], Frame, Easing)
end
end.
-file("src/etui/anim.gleam", 153).
?DOC(
" Interpolate along a keyframe sequence at the given frame.\n"
" Keyframes are sorted automatically, so order at the call site doesn't matter.\n"
" Returns the last keyframe value if frame exceeds the sequence.\n"
).
-spec sequence(list(keyframe()), integer(), easing()) -> integer().
sequence(Keyframes, Frame, Easing) ->
Sorted = gleam@list:sort(
Keyframes,
fun(A, B) ->
gleam@int:compare(erlang:element(2, A), erlang:element(2, B))
end
),
find_segment(Sorted, Frame, Easing).