As seen, given a timeline
of your model, you can get the current value via:
Timeline.value timeline
You can also ask something like:
Timeline.transition 500 timeline
this says: suppose transitions between different states take 500ms. Where am I at?
The answer is of type Status
which is either:
At t int
: there is no current transition, we have been at this value for int
millisecondsTransitioning t t float
: we are transitioning from an old t
to a new one, and float goes from 1.0 -> 0.0
as we approachNow that you know if you are statically at a state, or transitioning to that state from a previous one, you can use the transition factor to do animations.
{ current : Event t
, history : List (Event t)
, limit : Basics.Int
, now : Time.Posix
, start : Time.Posix
}
A timeline of your model's history.
History is only held long enough to ensure smooth transitions,
based on the maximum transition duration value supplied to init
.
You never need to create these directly.
A timeline Msg.
A transition status. Either At
a value for a specific number of milliseconds,
or Transitioning
from one value to another, with a "remaining" float value.
Note that during a transition, the float value goes from 1.0 -> 0.0
.
The "remaining" float value returned is not linear, but a dampened-spring
eased value. Use it directly (with a multiplier) to fade, scale, translate
with a natural and pleasant effect.
init : Basics.Int -> Time.Posix -> ( model, Platform.Cmd.Cmd msg ) -> ( Timeline model, Platform.Cmd.Cmd (Msg msg) )
Converts the output of your existing init
to a Timeline compatible init
.
limit =
500
now =
Time.millisToPosix 0
timelineInit =
\flags -> myInit flags |> Timeline.init limit now
The limit
parameter is the duration your longest animation (transition) will take.
This helps Timeline know when to throw away history it no longer needs.
When you do timeline |> Timeline.transition 500
you are creating a 500ms transition.
If that's the longest transition in your app, then limit
in init
should be 500
.
The now
parameter will become the timestamp for the initial model state.
It's perfectly safe to set this to any time in history (e.g. Time.millisToPosix 0
).
The only caveat is if you later inquire how long a state has been at rest, you won't
get a correct answer for your initial state. This probably doesn't matter in
most use cases. But seeing as you can animate a static state based on it's age,
this is important to know.
update : (msg -> model -> ( model, Platform.Cmd.Cmd msg )) -> Msg msg -> Timeline model -> ( Timeline model, Platform.Cmd.Cmd (Msg msg) )
Converts your existing update
to a Timeline compatible update
.
See Basic Setup.
subscriptions : (model -> Platform.Sub.Sub msg) -> Timeline model -> Platform.Sub.Sub (Msg msg)
Converts your existing subscriptions
to a Timeline compatible subscriptions
.
See Basic Setup.
view : (Timeline model -> Html msg) -> Timeline model -> Html (Msg msg)
Convenience function.
Converts your view : Timeline model -> Html msg
to
a Timeline model -> Html (Timeline.Msg msg)
for use in your main
.
viewDocument : (Timeline model -> Browser.Document msg) -> Timeline model -> Browser.Document (Msg msg)
Convenience function.
Converts your view : Timeline model -> Browser.Document msg
to
a Timeline model -> Browser.Document (Timeline.Msg msg)
for use in your
main
.
msg : m -> Msg m
Maps a msg
to a Timeline.Msg msg
.
Useful in your main
when defining things such as:
onUrlChange =
Timeline.msg << OnUrlChange
value : Timeline t -> t
Extract the current (most recent) value from the timeline.
transition : Basics.Int -> Timeline t -> Status t
Given a duration for how long a transition on a timeline takes, what's the status of our timeline? Are we transitioning between values, or statically at a value?
let
hamburgerMenuStatus =
modelTimeline
|> Timeline.map .hamburgerMenuState
|> transition 300
in
case hamburgerMenuState of
At state t ->
-- our menu is fully open/closed, and has been for `t` ms
Transitioning from to remaining ->
-- our menu is transitioning.
-- `remaining` goes `1.0 -> 0.0` as `from -> to`
The remaining
float value returned is not linear, but a dampened-spring
eased value. Use it directly (with a multiplier) to fade, scale, translate
with a natural and pleasant effect.
map : (t -> s) -> Timeline t -> Timeline s
Maps a timeline. Very importantly the new timeline can have a different change history.
If
type alias Model =
{ a : Int, b : Int }
then
modelTimeline |> Timeline.map .a
returns a timeline of the history of a
irrespective of b
.
modelTimeline |> Timeline.map .b
returns a timeline of the history of b
irrespective of a
.
One can thus animate transitions of a
and b
(or any other model properties)
independently.
withDefault : t -> Timeline (Maybe t) -> Timeline t
This is for treating non-continuous Timelines as continuous. Usually occurs when mapping your model (and hence Timeline) can result in unwanted maybes.
e.g.
type alias PageAModel =
{ a : Bool }
type alias PageBModel =
{ b : Bool }
type Model
= PageA PageAModel
| PageB PageBModel
Here, the state of the page models are not continuous. They don't always exist. Elm's type system will remind us that we cannot animate a thing that might not exist.
To animate PageA
and PageB
views, the following is required:
Maybe Page?Model
values from Model
e.g.
pageAModel : Model -> Maybe PageAModel
pageAModel model =
case model of
PageA pageAModel_ ->
Just pageAModel_
_ ->
Nothing
pageBModel : Model -> Maybe PageBModel
pageBModel model =
...
view : Timeline Model -> Html Msg
view timeline =
let
model =
Timeline.value timeline
in
case model of
PageA currentPageAModel ->
let
continuousPageATimeline : Timeline PageAModel
continuousPageATimeline =
timeline
|> Timeline.map pageAModel
|> Timeline.withDefault currentPageAModel
in
renderPageA continuousPageATimeline
PageB currentPageBModel ->
...
sequence : (a -> id) -> Timeline (List a) -> List (Timeline (Maybe a))
Turns a Timeline (List a)
to a List (Timeline (Maybe a))
.
When we look at a changing list, typically that list is changing because values are being inserted or removed or modified. So, if we are rendering a list into a list-like view, we want to know what the Timeline is for each slot. That's what this is for.
Because a slot (in this visualisation) might start as empty and have
something inserted, or vice versa, we return a Timeline
of Maybe a
.
The initial parameter is an id
function which allows us to track an
entry's index in the list as it's value changes over time.
currentTime : Timeline t -> Time.Posix
Get the current time from a timeline.
Updated internally via Browser.Events.onAnimationFrame
, thus inheriting its
resolution.
Having this prevents the need from tracking a high resolution time value in your model, because doing such a thing would prohibit use of Timeline due to GC issues.
push : Event t -> Timeline t -> Timeline t
Exposed for testing. You don't need this.