mirror of
https://github.com/mdgriffith/elm-animator.git
synced 2024-09-11 12:47:52 +03:00
Compare commits
9 Commits
22fb914de9
...
caa5520580
Author | SHA1 | Date | |
---|---|---|---|
|
caa5520580 | ||
|
07a390de66 | ||
|
963d415161 | ||
|
65b635e9ef | ||
|
8d9af0afaa | ||
|
1309108299 | ||
|
441177c59b | ||
|
f5d75fee5f | ||
|
aa0aad7b01 |
6
elm.json
6
elm.json
@ -8,17 +8,17 @@
|
||||
"Animator",
|
||||
"Animator.Timeline",
|
||||
"Animator.Value",
|
||||
"Animator.Watcher",
|
||||
"Animator.Transition"
|
||||
],
|
||||
"elm-version": "0.19.0 <= v < 0.20.0",
|
||||
"dependencies": {
|
||||
"avh4/elm-color": "1.0.0 <= v < 2.0.0",
|
||||
"elm/browser": "1.0.0 <= v < 2.0.0",
|
||||
"elm/core": "1.0.0 <= v < 2.0.0",
|
||||
"elm/html": "1.0.0 <= v < 2.0.0",
|
||||
"elm/time": "1.0.0 <= v < 2.0.0",
|
||||
"mdgriffith/elm-bezier": "1.0.0 <= v < 2.0.0"
|
||||
},
|
||||
"test-dependencies": { "elm-explorations/test": "2.2.0 <= v < 3.0.0" }
|
||||
"test-dependencies": {
|
||||
"elm-explorations/test": "2.2.0 <= v < 3.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,43 +0,0 @@
|
||||
{
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
"src",
|
||||
"../src"
|
||||
],
|
||||
"elm-version": "0.19.1",
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"avh4/elm-color": "1.0.0",
|
||||
"elm/browser": "1.0.2",
|
||||
"elm/core": "1.0.5",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/json": "1.1.3",
|
||||
"elm/svg": "1.0.1",
|
||||
"elm/time": "1.0.0",
|
||||
"elm/url": "1.0.0",
|
||||
"ianmackenzie/elm-geometry": "4.0.0",
|
||||
"ianmackenzie/elm-units": "2.10.0",
|
||||
"mdgriffith/elm-bezier": "1.0.0",
|
||||
"terezka/line-charts": "2.0.2"
|
||||
},
|
||||
"indirect": {
|
||||
"debois/elm-dom": "1.3.0",
|
||||
"elm/parser": "1.1.0",
|
||||
"elm/random": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.3",
|
||||
"ianmackenzie/elm-1d-parameter": "1.0.1",
|
||||
"ianmackenzie/elm-float-extra": "1.1.0",
|
||||
"ianmackenzie/elm-interval": "3.1.0",
|
||||
"ianmackenzie/elm-triangular-mesh": "1.1.0",
|
||||
"ianmackenzie/elm-units-interval": "3.2.0",
|
||||
"justinmimbs/date": "4.1.0",
|
||||
"justinmimbs/time-extra": "1.2.0",
|
||||
"myrho/elm-round": "1.0.5",
|
||||
"ryan-haskell/date-format": "1.0.0"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
module AnimationX exposing (main)
|
||||
|
||||
import Animator
|
||||
import Animator.Timeline
|
||||
import Animator.Transition
|
||||
import Animator.Value
|
||||
import Browser
|
||||
import Browser.Events
|
||||
import Html
|
||||
import Html.Events
|
||||
import Time
|
||||
|
||||
|
||||
main : Program () Model Msg
|
||||
main =
|
||||
Browser.element
|
||||
{ init = always init
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
, view = view
|
||||
}
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ timeline : Animator.Timeline.Timeline Float }
|
||||
|
||||
|
||||
type Msg
|
||||
= NewPosix Time.Posix
|
||||
| Start
|
||||
|
||||
|
||||
init : ( Model, Cmd Msg )
|
||||
init =
|
||||
let
|
||||
initialTimeline : Animator.Timeline.Timeline Float
|
||||
initialTimeline =
|
||||
Animator.Timeline.init 10
|
||||
|
||||
queuedSteps : List (Animator.Timeline.Step Float)
|
||||
queuedSteps =
|
||||
[ Animator.Timeline.transitionTo (Animator.ms 1000) 100
|
||||
, Animator.Timeline.transitionTo (Animator.ms 1000) 50
|
||||
, Animator.Timeline.transitionTo (Animator.ms 1000) 5
|
||||
]
|
||||
|
||||
timelineWithSteps : Animator.Timeline.Timeline Float
|
||||
timelineWithSteps =
|
||||
Animator.Timeline.scale 3 <| Animator.Timeline.queue queuedSteps initialTimeline
|
||||
in
|
||||
( { timeline = timelineWithSteps }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
NewPosix posix ->
|
||||
( { model | timeline = Animator.Timeline.update posix model.timeline }, Cmd.none )
|
||||
|
||||
Start ->
|
||||
init
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
if Animator.Timeline.isRunning model.timeline then
|
||||
Browser.Events.onAnimationFrame NewPosix
|
||||
|
||||
else
|
||||
Sub.none
|
||||
|
||||
|
||||
view : Model -> Html.Html Msg
|
||||
view model =
|
||||
let
|
||||
positionStandard : Float
|
||||
positionStandard =
|
||||
Animator.Value.float model.timeline Animator.Value.to
|
||||
|
||||
positionLinear : Float
|
||||
positionLinear =
|
||||
Animator.Value.float model.timeline (Animator.Value.withTransition Animator.Transition.linear << Animator.Value.to)
|
||||
in
|
||||
Html.div []
|
||||
[ Html.div []
|
||||
[ Html.div [] [ Html.text ("Position Standard: " ++ String.fromFloat positionStandard) ]
|
||||
, Html.div [] [ Html.text ("Position Linear: " ++ String.fromFloat positionLinear) ]
|
||||
]
|
||||
, Html.button [ Html.Events.onClick Start ] [ Html.text "Re-Start" ]
|
||||
]
|
@ -1,83 +0,0 @@
|
||||
module Basic exposing (main)
|
||||
|
||||
{-| -}
|
||||
|
||||
import Animator
|
||||
import Browser
|
||||
import Color
|
||||
import Html exposing (..)
|
||||
import Html.Attributes as Attr
|
||||
import Html.Events as Events
|
||||
import Time
|
||||
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
|
||||
|
||||
main =
|
||||
Browser.sandbox
|
||||
{ init =
|
||||
{}
|
||||
, view = view
|
||||
, update = update
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= Check Bool
|
||||
|
||||
|
||||
update : Msg -> Model -> Model
|
||||
update msg model =
|
||||
case msg of
|
||||
Check newChecked ->
|
||||
model
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view model =
|
||||
div
|
||||
[ Attr.class "root"
|
||||
]
|
||||
[ stylesheet
|
||||
, box (Animator.spinning (Animator.ms 2000))
|
||||
, box (Animator.spinning (Animator.ms 1500))
|
||||
, box (Animator.spinning (Animator.ms 1000))
|
||||
]
|
||||
|
||||
|
||||
box animation =
|
||||
Animator.div animation
|
||||
[ Attr.class "box"
|
||||
]
|
||||
[]
|
||||
|
||||
|
||||
stylesheet : Html msg
|
||||
stylesheet =
|
||||
Html.node "style"
|
||||
[]
|
||||
[ text """@import url('https://fonts.googleapis.com/css?family=Roboto&display=swap');
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
|
||||
}
|
||||
.root > * {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.box {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 20%;
|
||||
background-color:red;
|
||||
border-color: black;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
"""
|
||||
]
|
@ -1,251 +0,0 @@
|
||||
module Checkbox exposing (main)
|
||||
|
||||
{-| As you've probably figured out, this is a checkbox example!
|
||||
|
||||
The main purpose of this example is to cover the basics of Elm Animator.
|
||||
|
||||
You should checkout these places in the code, which are marked so you can search for them.
|
||||
|
||||
It looks like a lot, but much of it is only needed once per project!
|
||||
|
||||
- (1) - Instead of a `Bool`, we store an `Animator Bool` in our model.
|
||||
- (2) - The `Animator Model`, which is the piece of code that knows how to update your model when a timeline changes.
|
||||
- (3) - Start a timeline by using `Animator.init`
|
||||
- (4) - Turning out `Timeline` into a subscription using `Animator.toSubscription`.
|
||||
- (5) - Updating our model using our animator and the current time.
|
||||
- (6) - Starting an animation
|
||||
- (7) - Turning our timeline into inline styles.
|
||||
|
||||
-}
|
||||
|
||||
import Animator
|
||||
import Animator.Inline
|
||||
import Browser
|
||||
import Color
|
||||
import Html exposing (..)
|
||||
import Html.Attributes as Attr
|
||||
import Html.Events as Events
|
||||
import Time
|
||||
|
||||
|
||||
{-| (1) - In our model we'd normally store just a `Bool`.
|
||||
|
||||
However now we have an `Animator.Timeline Bool`
|
||||
|
||||
-}
|
||||
type alias Model =
|
||||
{ checked : Animator.Timeline Bool
|
||||
}
|
||||
|
||||
|
||||
{-| (2) - The first thing we do is create an `Animator Model`.
|
||||
|
||||
It's job is to reach into our model and update our timelines when they need to be updated
|
||||
|
||||
Notice you could add any number of timelines to this animator.
|
||||
|
||||
**Note:** You likely only need one animator for a given project.
|
||||
|
||||
-}
|
||||
animator : Animator.Animator Model
|
||||
animator =
|
||||
Animator.animator
|
||||
|> Animator.watching
|
||||
-- we tell the animator how
|
||||
-- to get the checked timeline using .checked
|
||||
.checked
|
||||
-- and we tell the animator how
|
||||
-- to update that timeline as well
|
||||
(\newChecked model ->
|
||||
{ model | checked = newChecked }
|
||||
)
|
||||
|
||||
|
||||
main =
|
||||
Browser.document
|
||||
{ init =
|
||||
\() ->
|
||||
-- (3) - How we create our timeline
|
||||
( { checked = Animator.init False
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
, view = view
|
||||
, update = update
|
||||
, subscriptions =
|
||||
\model ->
|
||||
-- (4) - turning out Animator into a subscription
|
||||
-- this is where the animator will decide to have a subscription to AnimationFrame or not.
|
||||
animator
|
||||
|> Animator.toSubscription Tick model
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= Tick Time.Posix
|
||||
| Check Bool
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
Tick newTime ->
|
||||
( model
|
||||
|> Animator.update newTime animator
|
||||
-- (5) - Updating our model using our animator and the current time.
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Check newChecked ->
|
||||
( { model
|
||||
| checked =
|
||||
-- (6) - Here we're adding a new state to our timeline.
|
||||
model.checked
|
||||
|> Animator.go Animator.slowly newChecked
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
view : Model -> Browser.Document Msg
|
||||
view model =
|
||||
{ title = "Animator - Checkbox"
|
||||
, body =
|
||||
[ stylesheet
|
||||
, div
|
||||
[ Attr.class "root"
|
||||
]
|
||||
[ div
|
||||
[ Attr.class "viewport"
|
||||
]
|
||||
[ viewHugeCheckbox model.checked
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
viewHugeCheckbox : Animator.Timeline Bool -> Html Msg
|
||||
viewHugeCheckbox checked =
|
||||
div
|
||||
[ Attr.style "display" "flex"
|
||||
, Attr.style "align-items" "center"
|
||||
, Attr.style "flex-direction" "column"
|
||||
]
|
||||
[ div
|
||||
[ Attr.style "display" "flex"
|
||||
, Attr.style "align-items" "center"
|
||||
, Attr.style "cursor" "pointer"
|
||||
, Events.onClick (Check (not (Animator.current checked)))
|
||||
]
|
||||
[ div
|
||||
-- (7) - Rendering our timeline as inline styles.
|
||||
-- What we're doing here is mapping our timeline states
|
||||
-- to what values they should be in the view.
|
||||
-- Elm animator then uses these to interpolate where we should be.
|
||||
[ Animator.Inline.backgroundColor checked <|
|
||||
\state ->
|
||||
if state then
|
||||
Color.rgb255 255 96 96
|
||||
|
||||
else
|
||||
Color.white
|
||||
, Animator.Inline.borderColor checked <|
|
||||
\state ->
|
||||
if state then
|
||||
Color.rgb255 255 96 96
|
||||
|
||||
else
|
||||
Color.black
|
||||
, Attr.class "checkbox"
|
||||
]
|
||||
[ div
|
||||
[ Animator.Inline.opacity checked <|
|
||||
\state ->
|
||||
if state then
|
||||
Animator.at 1
|
||||
|
||||
else
|
||||
Animator.at 0
|
||||
, Animator.Inline.transform
|
||||
{ position = { x = 0, y = 0 }
|
||||
, rotate =
|
||||
Animator.move checked <|
|
||||
\state ->
|
||||
if state then
|
||||
Animator.at (turns 0)
|
||||
|
||||
else
|
||||
Animator.at (turns 0.05)
|
||||
, scale =
|
||||
Animator.move checked <|
|
||||
\state ->
|
||||
if state then
|
||||
Animator.at 1
|
||||
|
||||
else
|
||||
Animator.at 0.8
|
||||
}
|
||||
]
|
||||
[ text "!" ]
|
||||
]
|
||||
, span
|
||||
[ Attr.style "margin-left" "32px"
|
||||
, Attr.style "font-size" "190px"
|
||||
]
|
||||
[ text "Click me" ]
|
||||
]
|
||||
, div
|
||||
[ Animator.Inline.opacity checked <|
|
||||
\state ->
|
||||
if state then
|
||||
Animator.at 1
|
||||
|
||||
else
|
||||
Animator.at 0
|
||||
]
|
||||
[ text "Great job "
|
||||
, span
|
||||
[ Attr.style "display" "inline-block"
|
||||
]
|
||||
[ text "👍" ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
stylesheet : Html msg
|
||||
stylesheet =
|
||||
Html.node "style"
|
||||
[]
|
||||
[ text """@import url('https://fonts.googleapis.com/css?family=Roboto&display=swap');
|
||||
|
||||
.root {
|
||||
width: 100%;
|
||||
height: 1000px;
|
||||
font-size: 48px;
|
||||
user-select: none;
|
||||
padding: 50px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
.viewport {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200px;
|
||||
}
|
||||
.checkbox {
|
||||
border-width: 10px;
|
||||
border-style: solid;
|
||||
color: #000;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
border-radius: 20px;
|
||||
font-size: 160px;
|
||||
line-height: 1.0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
"""
|
||||
]
|
@ -1,530 +0,0 @@
|
||||
module Loading exposing (main)
|
||||
|
||||
{-| Animating loading states!
|
||||
|
||||
It's pretty common to have a type, usually called RemoteData, to represent a piece of data that's been requested from the server.
|
||||
|
||||
It generally looks something like this:
|
||||
|
||||
type RemoteData error data
|
||||
= NotAsked
|
||||
| Loading
|
||||
| Failure error
|
||||
| Success data
|
||||
|
||||
This example will show you how to:
|
||||
|
||||
1. Animate a "resting" state, so that when we're at a state of `Loading`, a a loading animation is occurring.
|
||||
2. Animate content that's already been deleted! In this case we can show the previously retrieved comment, even whil we're in a loading state.
|
||||
3. How to debug animations with `Animator.Css.explain`. This will show you a bounding box for an element, as well as it's center of rotation.
|
||||
|
||||
Search for (1), (2), and (3) respectively to see the code!
|
||||
|
||||
-}
|
||||
|
||||
import Animator
|
||||
import Animator.Css
|
||||
import Browser
|
||||
import Color
|
||||
import Html exposing (..)
|
||||
import Html.Attributes as Attr
|
||||
import Html.Events as Events
|
||||
import Process
|
||||
import Task
|
||||
import Time
|
||||
|
||||
|
||||
type RemoteData error data
|
||||
= NotAsked
|
||||
| Loading
|
||||
| Failure error
|
||||
| Success data
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ comment :
|
||||
Animator.Timeline (RemoteData String String)
|
||||
}
|
||||
|
||||
|
||||
main =
|
||||
Browser.document
|
||||
{ init =
|
||||
\() ->
|
||||
( { comment =
|
||||
Animator.init NotAsked
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
, view = view
|
||||
, update = update
|
||||
, subscriptions =
|
||||
\model ->
|
||||
animator
|
||||
|> Animator.toSubscription Tick model
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
animator : Animator.Animator Model
|
||||
animator =
|
||||
Animator.animator
|
||||
-- *NOTE* We're using `the Animator.Css.watching` instead of `Animator.watching`.
|
||||
-- Instead of asking for a constant stream of animation frames, it'll only ask for one
|
||||
-- and we'll render the entire css animation in that frame.
|
||||
|> Animator.Css.watching
|
||||
.comment
|
||||
(\newComment model ->
|
||||
{ model | comment = newComment }
|
||||
)
|
||||
|
||||
|
||||
type Msg
|
||||
= Tick Time.Posix
|
||||
| AskServerForNewComment
|
||||
| NewCommentFromServer String
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
Tick newTime ->
|
||||
( model
|
||||
|> Animator.update newTime animator
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
AskServerForNewComment ->
|
||||
( { model
|
||||
| comment =
|
||||
model.comment
|
||||
|> Animator.go Animator.quickly Loading
|
||||
}
|
||||
, Task.perform (always (NewCommentFromServer "Howdy partner!"))
|
||||
(Process.sleep (2 * 1000))
|
||||
)
|
||||
|
||||
NewCommentFromServer comment ->
|
||||
( { model
|
||||
| comment =
|
||||
model.comment
|
||||
|> Animator.go Animator.quickly (Success comment)
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
view : Model -> Browser.Document Msg
|
||||
view model =
|
||||
{ title = "Animator - Loading"
|
||||
, body =
|
||||
[ stylesheet
|
||||
, div [ Attr.class "root" ]
|
||||
[ div
|
||||
[ Attr.class "viewport"
|
||||
]
|
||||
[ viewComment model.comment
|
||||
, Html.div
|
||||
[ Attr.style "display" "flex"
|
||||
, Attr.style "flex-direction" "row"
|
||||
, Attr.style "justify-content" "space-between"
|
||||
, Attr.style "width" "100%"
|
||||
]
|
||||
[ Html.button
|
||||
[ Attr.class "button"
|
||||
, Events.onClick AskServerForNewComment
|
||||
]
|
||||
[ Html.text "Load comment" ]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
viewComment : Animator.Timeline (RemoteData String String) -> Html Msg
|
||||
viewComment comment =
|
||||
Animator.Css.div comment
|
||||
[ Animator.Css.backgroundColor <|
|
||||
\state ->
|
||||
case state of
|
||||
Loading ->
|
||||
Color.rgb 0.9 0.9 0.9
|
||||
|
||||
_ ->
|
||||
Color.rgb 1 1 1
|
||||
, Animator.Css.fontColor <|
|
||||
\state ->
|
||||
case state of
|
||||
Loading ->
|
||||
Color.rgb 0.5 0.5 0.5
|
||||
|
||||
_ ->
|
||||
Color.rgb 0 0 0
|
||||
, Animator.Css.borderColor <|
|
||||
\state ->
|
||||
case state of
|
||||
Loading ->
|
||||
Color.rgb 0.5 0.5 0.5
|
||||
|
||||
_ ->
|
||||
Color.rgb 0 0 0
|
||||
]
|
||||
[ Attr.style "position" "relative"
|
||||
, Attr.style "display" "flex"
|
||||
, Attr.style "flex-direction" "column"
|
||||
, Attr.style "justify-content" "space-between"
|
||||
, Attr.style "font-size" "16px"
|
||||
, Attr.style "width" "100%"
|
||||
, Attr.style "height" "100px"
|
||||
, Attr.style "padding" "24px"
|
||||
, Attr.style "box-sizing" "border-box"
|
||||
, Attr.style "border" "1px solid black"
|
||||
, Attr.style "border-radius" "3px"
|
||||
]
|
||||
[ loadingIndicator comment
|
||||
, case Animator.current comment of
|
||||
NotAsked ->
|
||||
Html.div [ Attr.style "color" "#CCC" ] [ Html.text "No comment loaded" ]
|
||||
|
||||
Loading ->
|
||||
Html.div []
|
||||
-- (2) - We can still show the previous comment!
|
||||
-- If we're loading a new one, we'll grey it out.
|
||||
[ case Animator.previous comment of
|
||||
Success text ->
|
||||
Html.div
|
||||
[ Attr.style "display" "flex"
|
||||
, Attr.style "flex-direction" "row"
|
||||
, Attr.style "filter" "grayscale(1)"
|
||||
]
|
||||
[ viewThinking
|
||||
, Html.text text
|
||||
]
|
||||
|
||||
_ ->
|
||||
Html.div [] [ Html.text "Loading..." ]
|
||||
]
|
||||
|
||||
Failure error ->
|
||||
Html.div [] [ Html.text error ]
|
||||
|
||||
Success text ->
|
||||
Html.div
|
||||
[ Attr.style "display" "flex"
|
||||
, Attr.style "flex-direction" "row"
|
||||
]
|
||||
[ viewCowboy
|
||||
-- (3) - This `debug` field will turn on `Animator.Css.explain` for the elements animated in the `viewCowboy` function.
|
||||
-- See the other note labeled (3) to get a better idea of what that means.
|
||||
{ debug = False
|
||||
, timeline = comment
|
||||
}
|
||||
, Html.text text
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
loadingIndicator : Animator.Timeline (RemoteData error success) -> Html msg
|
||||
loadingIndicator loading =
|
||||
Html.div
|
||||
[ Attr.class "loading-indicator"
|
||||
]
|
||||
[ viewBlinkingCircle 0.2 loading
|
||||
, viewBlinkingCircle 0.3 loading
|
||||
, viewBlinkingCircle 0.4 loading
|
||||
]
|
||||
|
||||
|
||||
viewBlinkingCircle offset timeline =
|
||||
Animator.Css.div timeline
|
||||
[ Animator.Css.opacity <|
|
||||
\state ->
|
||||
case state of
|
||||
Loading ->
|
||||
-- (1) - When we're loading, use a sine wave to oscillate between 0.05 and 1
|
||||
-- Becase we want multiple blinking circles, we can als "shift" this wave over by some amount.
|
||||
-- A shift of 0 means the value starts at 0.05.
|
||||
-- A shift of 1 would mean the value starts at the "end", at 1.
|
||||
-- Then we tell it to take 700ms for a full loop
|
||||
Animator.wave 0.05 1
|
||||
|> Animator.shift offset
|
||||
|> Animator.loop (Animator.millis 700)
|
||||
|
||||
_ ->
|
||||
Animator.at 0
|
||||
]
|
||||
[ Attr.class "circle"
|
||||
]
|
||||
[]
|
||||
|
||||
|
||||
viewThinking : Html msg
|
||||
viewThinking =
|
||||
Html.div
|
||||
[ Attr.class "thinking"
|
||||
]
|
||||
[ Html.text "\u{1F914}"
|
||||
]
|
||||
|
||||
|
||||
viewCowboy : { debug : Bool, timeline : Animator.Timeline (RemoteData error value) } -> Html msg
|
||||
viewCowboy { debug, timeline } =
|
||||
Html.div [ Attr.class "cowboy" ]
|
||||
[ Animator.Css.div timeline
|
||||
-- (3) - Animator.Css.explain True will show you
|
||||
-- -> the bounding box for this element
|
||||
-- -> the center of rotation, which you can change by setting the origin with transformWith
|
||||
-- -> The coordinate system for this element.
|
||||
[ Animator.Css.explain debug
|
||||
, Animator.Css.opacity <|
|
||||
\state ->
|
||||
case state of
|
||||
Success _ ->
|
||||
Animator.at 1
|
||||
|
||||
_ ->
|
||||
Animator.at 0
|
||||
, Animator.Css.transformWith
|
||||
{ rotationAxis =
|
||||
{ x = 0
|
||||
, y = 0
|
||||
, z = 1
|
||||
}
|
||||
, origin =
|
||||
Animator.Css.offset
|
||||
-5
|
||||
3
|
||||
}
|
||||
<|
|
||||
\state ->
|
||||
case state of
|
||||
NotAsked ->
|
||||
Animator.Css.xy
|
||||
{ x = 0
|
||||
, y = 0
|
||||
}
|
||||
|
||||
Loading ->
|
||||
Animator.Css.rotating (Animator.seconds 5)
|
||||
|
||||
Failure error ->
|
||||
Animator.Css.xy
|
||||
{ x = 0
|
||||
, y = 0
|
||||
}
|
||||
|
||||
Success text ->
|
||||
fingerGuns 0.5
|
||||
]
|
||||
[]
|
||||
[ Html.text "👉"
|
||||
]
|
||||
, Animator.Css.div timeline
|
||||
[ Animator.Css.explain debug
|
||||
, Animator.Css.transformWith
|
||||
{ rotationAxis =
|
||||
{ x = 0
|
||||
, y = 0
|
||||
, z = 1
|
||||
}
|
||||
, origin =
|
||||
Animator.Css.offset
|
||||
0
|
||||
0
|
||||
}
|
||||
<|
|
||||
\state ->
|
||||
case state of
|
||||
NotAsked ->
|
||||
Animator.Css.xy
|
||||
{ x = 0
|
||||
, y = 0
|
||||
}
|
||||
|
||||
Loading ->
|
||||
Animator.Css.rotating (Animator.seconds 5)
|
||||
|
||||
Failure error ->
|
||||
Animator.Css.xy
|
||||
{ x = 0
|
||||
, y = 0
|
||||
}
|
||||
|
||||
Success text ->
|
||||
Animator.Css.xy
|
||||
{ x = 0
|
||||
, y = 0
|
||||
}
|
||||
|> Animator.Css.lookAt
|
||||
{ x = 0.7
|
||||
, y = 0.2
|
||||
, z = 0.8
|
||||
}
|
||||
]
|
||||
[]
|
||||
[ case Animator.current timeline of
|
||||
NotAsked ->
|
||||
Html.text ""
|
||||
|
||||
Loading ->
|
||||
-- thinking face
|
||||
Html.text "\u{1F914}"
|
||||
|
||||
Failure error ->
|
||||
-- sad face
|
||||
Html.text "😞"
|
||||
|
||||
Success text ->
|
||||
-- cowboy
|
||||
Html.text "\u{1F920}"
|
||||
]
|
||||
, Animator.Css.div timeline
|
||||
[ Animator.Css.explain debug
|
||||
, Animator.Css.opacity <|
|
||||
\state ->
|
||||
case state of
|
||||
Success _ ->
|
||||
Animator.at 1
|
||||
|
||||
_ ->
|
||||
Animator.at 0
|
||||
, Animator.Css.transformWith
|
||||
{ rotationAxis =
|
||||
{ x = 0
|
||||
, y = 0
|
||||
, z = 1
|
||||
}
|
||||
, origin =
|
||||
Animator.Css.offset
|
||||
-5
|
||||
5
|
||||
}
|
||||
<|
|
||||
\state ->
|
||||
case state of
|
||||
NotAsked ->
|
||||
Animator.Css.xy
|
||||
{ x = 0
|
||||
, y = 0
|
||||
}
|
||||
|
||||
Loading ->
|
||||
Animator.Css.rotating (Animator.seconds 5)
|
||||
|
||||
Failure error ->
|
||||
Animator.Css.xy
|
||||
{ x = 0
|
||||
, y = 0
|
||||
}
|
||||
|
||||
Success text ->
|
||||
fingerGuns 0
|
||||
]
|
||||
[]
|
||||
[ Html.div
|
||||
-- this is an adjustment to get this hand to point in the desired direction.
|
||||
[ Attr.style "transform" "translateY(3px) scaleX(-1) rotate(270deg)"
|
||||
]
|
||||
[ Html.text "☝️" ]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
fingerGuns : Float -> Animator.Css.Transform
|
||||
fingerGuns offset =
|
||||
Animator.Css.in2d
|
||||
(Animator.Css.repeat 3 (Animator.millis 300))
|
||||
{ x = Animator.Css.resting 0
|
||||
, y = Animator.Css.resting 0
|
||||
, rotate =
|
||||
Animator.zigzag (turns 0) (turns -0.1)
|
||||
|> Animator.shift offset
|
||||
, scaleX = Animator.Css.resting 1
|
||||
, scaleY = Animator.Css.resting 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
{- STYLESHEET -}
|
||||
|
||||
|
||||
pink : String
|
||||
pink =
|
||||
"rgb(240, 0, 245)"
|
||||
|
||||
|
||||
stylesheet : Html msg
|
||||
stylesheet =
|
||||
Html.node "style"
|
||||
[]
|
||||
[ text """@import url('https://fonts.googleapis.com/css?family=Roboto&display=swap');
|
||||
|
||||
|
||||
|
||||
.circle {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
background-color: rgb(240, 0, 245);
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.thinking {
|
||||
display: flex;
|
||||
flex-direction: center;
|
||||
transform: translateY(-6px);
|
||||
width: 72px;
|
||||
font-size: 24px;
|
||||
margin-right: 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
.loading-indicator {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.root {
|
||||
width: 100%;
|
||||
font-size: 48px;
|
||||
user-select: none;
|
||||
padding: 50px;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
padding: 200px;
|
||||
}
|
||||
.viewport {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
width: 400px;
|
||||
|
||||
}
|
||||
.button {
|
||||
background-color: rgb(240, 0, 245);
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
|
||||
}
|
||||
.cowboy {
|
||||
display: flex;
|
||||
transform: translateY(-6px);
|
||||
width: 72px;
|
||||
font-size: 24px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
"""
|
||||
]
|
@ -1,804 +0,0 @@
|
||||
module Mario exposing (main)
|
||||
|
||||
{-|
|
||||
|
||||
|
||||
# Run around as Mario!
|
||||
|
||||
This example is primarily here to show how the sprite system works.
|
||||
|
||||
Essentially we're going to be animating like you would with a flip book!
|
||||
|
||||
But we're also going to be doing some physics calculations directly ourselves, so this example shows how you'd use `elm-animator` side-by-side with other manual animations if you need to.
|
||||
|
||||
(1) **Sprite Sheet** - A sprite sheet is single image file with a bunch of images at different positions in the file. [Here's the one we're using](https://github.com/mdgriffith/elm-animator/blob/master/examples/images/mario-sprites.png)
|
||||
|
||||
To animate this, we need to pick out our subimages from our image file by knowing their bounding boxes.
|
||||
|
||||
(2) **Animating the flipbook** - Here's the new place where we're animating Mario'd state.
|
||||
|
||||
-}
|
||||
|
||||
import Animator
|
||||
import Browser
|
||||
import Browser.Dom
|
||||
import Browser.Events
|
||||
import Html exposing (Html)
|
||||
import Html.Attributes as Attr
|
||||
import Json.Decode as Decode
|
||||
import Task
|
||||
import Time
|
||||
|
||||
|
||||
|
||||
{--}
|
||||
type alias Model =
|
||||
{ x : Float
|
||||
, y : Float
|
||||
, vx : Float
|
||||
, vy : Float
|
||||
, window : Window
|
||||
, gamepad : GamePad
|
||||
, mario : Animator.Timeline Mario
|
||||
}
|
||||
|
||||
|
||||
type alias GamePad =
|
||||
{ left : Pressed
|
||||
, right : Pressed
|
||||
, jump : Pressed
|
||||
, run : Pressed
|
||||
, duck : Pressed
|
||||
}
|
||||
|
||||
|
||||
type Pressed
|
||||
= NotPressed
|
||||
| StartPressed
|
||||
| HeldFor Milliseconds
|
||||
|
||||
|
||||
type alias Milliseconds =
|
||||
Float
|
||||
|
||||
|
||||
type alias Keys =
|
||||
{ x : Int
|
||||
, y : Int
|
||||
}
|
||||
|
||||
|
||||
type Arrow
|
||||
= ArrowDown
|
||||
| ArrowUp
|
||||
| ArrowLeft
|
||||
| ArrowRight
|
||||
|
||||
|
||||
type alias Window =
|
||||
{ width : Int
|
||||
, height : Int
|
||||
}
|
||||
|
||||
|
||||
type Mario
|
||||
= Mario Action Direction
|
||||
|
||||
|
||||
type Action
|
||||
= Running
|
||||
| Walking
|
||||
| Standing
|
||||
| Ducking
|
||||
| Jumping
|
||||
|
||||
|
||||
type Direction
|
||||
= Left
|
||||
| Right
|
||||
|
||||
|
||||
type Msg
|
||||
= Tick Time.Posix
|
||||
| Frame Float
|
||||
| Pressed Button
|
||||
| Released Button
|
||||
| WindowSize Int Int
|
||||
|
||||
|
||||
type Button
|
||||
= GoLeft
|
||||
| GoRight
|
||||
| Duck
|
||||
| Jump
|
||||
| Run
|
||||
|
||||
|
||||
main =
|
||||
Browser.document
|
||||
{ init =
|
||||
\() ->
|
||||
( init
|
||||
, Browser.Dom.getViewport
|
||||
|> Task.attempt
|
||||
(\viewportResult ->
|
||||
case viewportResult of
|
||||
Ok viewport ->
|
||||
WindowSize
|
||||
(round viewport.scene.width)
|
||||
(round viewport.scene.height)
|
||||
|
||||
Err err ->
|
||||
WindowSize
|
||||
(round 800)
|
||||
(round 600)
|
||||
)
|
||||
)
|
||||
, view = view
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
}
|
||||
|
||||
|
||||
init : Model
|
||||
init =
|
||||
{ x = 0
|
||||
, y = 0
|
||||
, vx = 0
|
||||
, vy = 0
|
||||
, window = { width = 800, height = 500 }
|
||||
, gamepad =
|
||||
{ left = NotPressed
|
||||
, right = NotPressed
|
||||
, jump = NotPressed
|
||||
, run = NotPressed
|
||||
, duck = NotPressed
|
||||
}
|
||||
, mario = Animator.init (Mario Standing Right)
|
||||
}
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg mario =
|
||||
case msg of
|
||||
Tick newTime ->
|
||||
( mario
|
||||
|> Animator.update newTime animator
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Frame dt ->
|
||||
( mario
|
||||
|> holdButtons dt
|
||||
|> gravity (dt / 10)
|
||||
|> jump mario.gamepad
|
||||
|> walk mario.gamepad
|
||||
|> physics (dt / 10)
|
||||
|> updateSprites
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Pressed button ->
|
||||
( { mario
|
||||
| gamepad =
|
||||
applyButtonToGamepad button True mario.gamepad
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Released button ->
|
||||
( { mario
|
||||
| gamepad =
|
||||
applyButtonToGamepad button False mario.gamepad
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
WindowSize width height ->
|
||||
( { mario
|
||||
| window =
|
||||
{ width = width
|
||||
, height = height
|
||||
}
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
{-| -}
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
Sub.batch
|
||||
[ animator
|
||||
|> Animator.toSubscription Tick model
|
||||
, Browser.Events.onResize WindowSize
|
||||
, Browser.Events.onKeyDown (Decode.map Pressed decodeButton)
|
||||
, Browser.Events.onKeyUp (Decode.map Released decodeButton)
|
||||
, Browser.Events.onAnimationFrameDelta Frame
|
||||
]
|
||||
|
||||
|
||||
animator : Animator.Animator Model
|
||||
animator =
|
||||
Animator.animator
|
||||
-- we tell the animator how to get the checked timeline using .checked
|
||||
-- and we tell the animator how to update that timeline with updateChecked
|
||||
|> Animator.watching .mario (\mario m -> { m | mario = mario })
|
||||
|
||||
|
||||
decodeButton : Decode.Decoder Button
|
||||
decodeButton =
|
||||
Decode.andThen toButton
|
||||
(Decode.field "key" Decode.string)
|
||||
|
||||
|
||||
toButton : String -> Decode.Decoder Button
|
||||
toButton string =
|
||||
case string of
|
||||
"ArrowLeft" ->
|
||||
Decode.succeed GoLeft
|
||||
|
||||
"ArrowRight" ->
|
||||
Decode.succeed GoRight
|
||||
|
||||
" " ->
|
||||
Decode.succeed Jump
|
||||
|
||||
"ArrowDown" ->
|
||||
Decode.succeed Duck
|
||||
|
||||
"Shift" ->
|
||||
Decode.succeed Run
|
||||
|
||||
_ ->
|
||||
Decode.fail "Skip"
|
||||
|
||||
|
||||
holdButtons : Float -> Model -> Model
|
||||
holdButtons dt model =
|
||||
let
|
||||
gamepad =
|
||||
model.gamepad
|
||||
in
|
||||
{ model
|
||||
| gamepad = holdButtonsOnGamepad dt gamepad
|
||||
}
|
||||
|
||||
|
||||
holdButtonsOnGamepad dt gamepad =
|
||||
{ left = hold dt gamepad.left
|
||||
, right = hold dt gamepad.right
|
||||
, jump = hold dt gamepad.jump
|
||||
, run = hold dt gamepad.run
|
||||
, duck = hold dt gamepad.duck
|
||||
}
|
||||
|
||||
|
||||
hold dt pressed =
|
||||
case pressed of
|
||||
NotPressed ->
|
||||
NotPressed
|
||||
|
||||
StartPressed ->
|
||||
HeldFor dt
|
||||
|
||||
HeldFor existingDt ->
|
||||
HeldFor (existingDt + dt)
|
||||
|
||||
|
||||
applyButtonToGamepad : Button -> Bool -> GamePad -> GamePad
|
||||
applyButtonToGamepad button pressed gamepad =
|
||||
case button of
|
||||
GoLeft ->
|
||||
{ gamepad
|
||||
| left =
|
||||
if pressed then
|
||||
StartPressed
|
||||
|
||||
else
|
||||
NotPressed
|
||||
}
|
||||
|
||||
GoRight ->
|
||||
{ gamepad
|
||||
| right =
|
||||
if pressed then
|
||||
StartPressed
|
||||
|
||||
else
|
||||
NotPressed
|
||||
}
|
||||
|
||||
Duck ->
|
||||
{ gamepad
|
||||
| duck =
|
||||
if pressed then
|
||||
StartPressed
|
||||
|
||||
else
|
||||
NotPressed
|
||||
}
|
||||
|
||||
Jump ->
|
||||
{ gamepad
|
||||
| jump =
|
||||
if pressed then
|
||||
StartPressed
|
||||
|
||||
else
|
||||
NotPressed
|
||||
}
|
||||
|
||||
Run ->
|
||||
{ gamepad
|
||||
| run =
|
||||
if pressed then
|
||||
StartPressed
|
||||
|
||||
else
|
||||
NotPressed
|
||||
}
|
||||
|
||||
|
||||
view : Model -> Browser.Document Msg
|
||||
view model =
|
||||
{ title = "Mario - Elm Animator"
|
||||
, body =
|
||||
[ stylesheet
|
||||
, Html.div
|
||||
[ Attr.style "position" "fixed"
|
||||
, Attr.style "left" "0"
|
||||
, Attr.style "top" "0"
|
||||
, Attr.style "width" (String.fromInt model.window.width ++ "px")
|
||||
, Attr.style "height" (String.fromInt model.window.height ++ "px")
|
||||
]
|
||||
[ Html.div
|
||||
[ Attr.style "position" "absolute"
|
||||
, Attr.style "top" "80px"
|
||||
, Attr.style "left" "80px"
|
||||
, Attr.style "user-select" "none"
|
||||
, Attr.style "font-family" "'Roboto', sans-serif"
|
||||
]
|
||||
[ Html.h1 [] [ Html.text "Mario" ]
|
||||
, Html.div [] [ Html.text "Arrows to move, shift to run, space to jump!" ]
|
||||
]
|
||||
, Html.div
|
||||
[ Attr.class "positioner"
|
||||
, Attr.style "position" "absolute"
|
||||
, Attr.style "top" (String.fromFloat ((toFloat model.window.height / 2) - model.y) ++ "px")
|
||||
, Attr.style "left" (String.fromFloat model.x ++ "px")
|
||||
]
|
||||
-- (2) - Animating Mario's state with sprites
|
||||
-- We're watching te model.mario timeline, which has both a direction and an action that mario is currently doing.
|
||||
--
|
||||
[ viewSprite
|
||||
(Animator.step model.mario <|
|
||||
\(Mario action direction) ->
|
||||
let
|
||||
-- this is where we decide to show the left or the right sprite.
|
||||
--
|
||||
frame mySprite =
|
||||
case direction of
|
||||
Left ->
|
||||
Animator.frame mySprite
|
||||
|
||||
Right ->
|
||||
Animator.frame { mySprite | flipX = True }
|
||||
in
|
||||
case action of
|
||||
-- for these first three states, we only have a single frame we care about.
|
||||
Standing ->
|
||||
frame sprite.tail.stand
|
||||
|
||||
Ducking ->
|
||||
frame sprite.tail.duck
|
||||
|
||||
Jumping ->
|
||||
frame sprite.tail.jump
|
||||
|
||||
Walking ->
|
||||
-- when we're in a `Walking` state, we want to cycle through 3 frames.
|
||||
-- And we can also specify our frames per secton
|
||||
Animator.framesWith
|
||||
-- `transition` are the frames we'd want to take when transitioning to this state.
|
||||
{ transition = frame sprite.tail.stand
|
||||
|
||||
-- `resting` is what we want to do while we're in this state.
|
||||
, resting =
|
||||
Animator.cycle
|
||||
(Animator.fps 15)
|
||||
[ frame sprite.tail.step1
|
||||
, frame sprite.tail.step2
|
||||
, frame sprite.tail.stand
|
||||
]
|
||||
}
|
||||
|
||||
Running ->
|
||||
-- In order to make mario go faster, we're upping the fps
|
||||
-- and we're also changing the frames so that he puts his arms out.
|
||||
Animator.framesWith
|
||||
{ transition = frame sprite.tail.standArms
|
||||
, resting =
|
||||
Animator.cycle
|
||||
(Animator.fps 30)
|
||||
[ frame sprite.tail.runStep1
|
||||
, frame sprite.tail.runStep2
|
||||
, frame sprite.tail.standArms
|
||||
]
|
||||
}
|
||||
)
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
viewSprite : Box -> Html msg
|
||||
viewSprite box =
|
||||
Html.div []
|
||||
[ Html.div
|
||||
[ Attr.style "position" "absolute"
|
||||
, Attr.style "top" (String.fromInt box.adjustY ++ "px")
|
||||
, Attr.style "left" (String.fromInt box.adjustX ++ "px")
|
||||
, Attr.style "width" (String.fromInt box.width ++ "px")
|
||||
, Attr.style "height" (String.fromInt box.height ++ "px")
|
||||
, Attr.style "background-image" "url('http://mdgriffith.github.io/elm-animator/images/mario-sprites.png')"
|
||||
, Attr.style "background-repeat" "no-repeat"
|
||||
, Attr.style "transform-origin" "30% 50%"
|
||||
, Attr.style "transform"
|
||||
(if box.flipX then
|
||||
"scaleX(-1) scale(2)"
|
||||
|
||||
else
|
||||
"scaleX(1) scale(2)"
|
||||
)
|
||||
, Attr.style "background-position"
|
||||
("-"
|
||||
++ (String.fromInt box.x ++ "px -")
|
||||
++ (String.fromInt box.y ++ "px")
|
||||
)
|
||||
|
||||
-- we need to tell the browser to render our image and leave the pixels pixelated.
|
||||
, Attr.class "pixel-art"
|
||||
]
|
||||
[]
|
||||
]
|
||||
|
||||
|
||||
isHeld : Pressed -> Bool
|
||||
isHeld pressed =
|
||||
case pressed of
|
||||
NotPressed ->
|
||||
False
|
||||
|
||||
StartPressed ->
|
||||
True
|
||||
|
||||
HeldFor _ ->
|
||||
True
|
||||
|
||||
|
||||
walk : GamePad -> Model -> Model
|
||||
walk pad mario =
|
||||
let
|
||||
run yes x =
|
||||
if yes then
|
||||
x * 2.0
|
||||
|
||||
else
|
||||
x
|
||||
|
||||
newVx =
|
||||
if isHeld pad.left && isHeld pad.right then
|
||||
0
|
||||
|
||||
else if isHeld pad.left then
|
||||
run (isHeld pad.run) -1.8
|
||||
|
||||
else if isHeld pad.right then
|
||||
run (isHeld pad.run) 1.8
|
||||
|
||||
else
|
||||
0
|
||||
in
|
||||
{ mario
|
||||
| vx = newVx
|
||||
}
|
||||
|
||||
|
||||
jump : GamePad -> Model -> Model
|
||||
jump pad mario =
|
||||
if pad.jump == StartPressed && mario.vy == 0 then
|
||||
{ mario | vy = 6.0 }
|
||||
|
||||
else
|
||||
mario
|
||||
|
||||
|
||||
gravity : Float -> Model -> Model
|
||||
gravity dt mario =
|
||||
{ mario
|
||||
| vy =
|
||||
if mario.y > 0 then
|
||||
mario.vy - dt / 4
|
||||
|
||||
else
|
||||
0
|
||||
}
|
||||
|
||||
|
||||
physics : Float -> Model -> Model
|
||||
physics dt mario =
|
||||
{ mario
|
||||
| x =
|
||||
(mario.x + dt * mario.vx)
|
||||
|> min (toFloat mario.window.width - 40)
|
||||
|> max 0
|
||||
, y = max 0 (mario.y + dt * mario.vy)
|
||||
}
|
||||
|
||||
|
||||
updateSprites : Model -> Model
|
||||
updateSprites model =
|
||||
let
|
||||
current =
|
||||
Animator.current model.mario
|
||||
|
||||
direction =
|
||||
if model.vx > 0 then
|
||||
Right
|
||||
|
||||
else if model.vx < 0 then
|
||||
Left
|
||||
|
||||
else
|
||||
case current of
|
||||
Mario _ currentDirection ->
|
||||
currentDirection
|
||||
|
||||
action =
|
||||
if model.y /= 0 then
|
||||
Jumping
|
||||
|
||||
else if model.vx /= 0 then
|
||||
if abs model.vx > 2 then
|
||||
Running
|
||||
|
||||
else
|
||||
Walking
|
||||
|
||||
else if isHeld model.gamepad.duck then
|
||||
Ducking
|
||||
|
||||
else
|
||||
Standing
|
||||
|
||||
newMario =
|
||||
Mario action direction
|
||||
in
|
||||
if current /= newMario then
|
||||
{ model
|
||||
| mario =
|
||||
model.mario
|
||||
|> Animator.go Animator.immediately newMario
|
||||
}
|
||||
|
||||
else
|
||||
model
|
||||
|
||||
|
||||
|
||||
{- (1) - Sprite Sheet
|
||||
|
||||
x, y -> the coordinates of the image on the sprite sheet
|
||||
|
||||
width, height -> the size of the image I want
|
||||
|
||||
adjustX, adjustY -> adjustX and adjustY move the position of the rendered image so that we can line it up with the previous frames.
|
||||
|
||||
flipX, flipY -> The sprite sheet only shows mario looking in one direction. Though we can flip that image if we need to!
|
||||
|
||||
-}
|
||||
|
||||
|
||||
type alias Box =
|
||||
{ x : Int
|
||||
, y : Int
|
||||
, width : Int
|
||||
, height : Int
|
||||
, adjustX : Int
|
||||
, adjustY : Int
|
||||
, flipX : Bool
|
||||
, flipY : Bool
|
||||
}
|
||||
|
||||
|
||||
sprite =
|
||||
{ tail =
|
||||
{ stand =
|
||||
{ x = 0
|
||||
, y = 240
|
||||
, width = 27
|
||||
, height = 30
|
||||
, adjustX = 4
|
||||
, adjustY = 0
|
||||
, flipX = False
|
||||
, flipY = False
|
||||
}
|
||||
, step1 =
|
||||
{ x = 30
|
||||
, y = 240
|
||||
, width = 27
|
||||
, height = 30
|
||||
, adjustX = 3
|
||||
, adjustY = 0
|
||||
, flipX = False
|
||||
, flipY = False
|
||||
}
|
||||
, step2 =
|
||||
{ x = 60
|
||||
, y = 240
|
||||
, width = 27
|
||||
, height = 30
|
||||
, adjustX = 4
|
||||
, adjustY = 0
|
||||
, flipX = False
|
||||
, flipY = False
|
||||
}
|
||||
, jump =
|
||||
{ x = 90
|
||||
, y = 240
|
||||
, width = 27
|
||||
, height = 30
|
||||
, adjustX = 4
|
||||
, adjustY = 0
|
||||
, flipX = False
|
||||
, flipY = False
|
||||
}
|
||||
, duck =
|
||||
{ x = 120
|
||||
, y = 235
|
||||
, width = 27
|
||||
, height = 30
|
||||
, adjustX = 5
|
||||
, adjustY = 0
|
||||
, flipX = False
|
||||
, flipY = False
|
||||
}
|
||||
, pivot =
|
||||
{ x = 150
|
||||
, y = 240
|
||||
, width = 27
|
||||
, height = 30
|
||||
, adjustX = 0
|
||||
, adjustY = 0
|
||||
, flipX = False
|
||||
, flipY = False
|
||||
}
|
||||
, kick =
|
||||
{ x = 180
|
||||
, y = 240
|
||||
, width = 27
|
||||
, height = 30
|
||||
, adjustX = -1
|
||||
, adjustY = 0
|
||||
, flipX = False
|
||||
, flipY = False
|
||||
}
|
||||
, bum =
|
||||
{ x = 208
|
||||
, y = 239
|
||||
, width = 20
|
||||
, height = 30
|
||||
, adjustX = 3
|
||||
, adjustY = 0
|
||||
, flipX = False
|
||||
, flipY = False
|
||||
}
|
||||
, standArms =
|
||||
{ x = 0
|
||||
, y = 280
|
||||
, width = 25
|
||||
, height = 30
|
||||
, adjustX = 4
|
||||
, adjustY = 0
|
||||
, flipX = False
|
||||
, flipY = False
|
||||
}
|
||||
, runStep1 =
|
||||
{ x = 25
|
||||
, y = 280
|
||||
, width = 27
|
||||
, height = 30
|
||||
, adjustX = 3
|
||||
, adjustY = 0
|
||||
, flipX = False
|
||||
, flipY = False
|
||||
}
|
||||
, runStep2 =
|
||||
{ x = 52
|
||||
, y = 280
|
||||
, width = 25
|
||||
, height = 30
|
||||
, adjustX = 4
|
||||
, adjustY = 0
|
||||
, flipX = False
|
||||
, flipY = False
|
||||
}
|
||||
, runJump1 =
|
||||
{ x = 329
|
||||
, y = 280
|
||||
, width = 27
|
||||
, height = 30
|
||||
, adjustX = 3
|
||||
, adjustY = 0
|
||||
, flipX = False
|
||||
, flipY = False
|
||||
}
|
||||
, runJump2 =
|
||||
{ x = 359
|
||||
, y = 280
|
||||
, width = 27
|
||||
, height = 30
|
||||
, adjustX = 3
|
||||
, adjustY = 0
|
||||
, flipX = False
|
||||
, flipY = False
|
||||
}
|
||||
, runJump3 =
|
||||
{ x = 389
|
||||
, y = 280
|
||||
, width = 27
|
||||
, height = 30
|
||||
, adjustX = 3
|
||||
, adjustY = 0
|
||||
, flipX = False
|
||||
, flipY = False
|
||||
}
|
||||
, fall1 =
|
||||
{ x = 268
|
||||
, y = 280
|
||||
, width = 27
|
||||
, height = 30
|
||||
, adjustX = 3
|
||||
, adjustY = 0
|
||||
, flipX = False
|
||||
, flipY = False
|
||||
}
|
||||
, fall2 =
|
||||
{ x = 300
|
||||
, y = 280
|
||||
, width = 27
|
||||
, height = 30
|
||||
, adjustX = 3
|
||||
, adjustY = 0
|
||||
, flipX = False
|
||||
, flipY = False
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
stylesheet : Html msg
|
||||
stylesheet =
|
||||
Html.node "style"
|
||||
[]
|
||||
[ Html.text """@import url('https://fonts.googleapis.com/css?family=Roboto&display=swap');
|
||||
|
||||
body, html {
|
||||
margin: 0;
|
||||
padding:0;
|
||||
border:0;
|
||||
display:block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.pixel-art {
|
||||
image-rendering: pixelated;
|
||||
image-rendering: -moz-crisp-edges;
|
||||
image-rendering: crisp-edges;
|
||||
}
|
||||
"""
|
||||
]
|
@ -1,28 +0,0 @@
|
||||
# Examples
|
||||
|
||||
Hello and welcome to `elm-animator`!
|
||||
|
||||
Your first step is to read the high-level overview of how this library works in the `README.md`.
|
||||
|
||||
Then, we can dig in to some examples.
|
||||
|
||||
|
||||
**Note** if you clone this library to play with the code locally, make sure to `cd examples` and run `elm make` from there!
|
||||
|
||||
1. [**Checkbox**](https://github.com/mdgriffith/elm-animator/blob/master/examples/Checkbox.elm) - Animate a checkbox as it's checked. It covers:
|
||||
- *How to **get started** with `elm-animator`*
|
||||
- *An idea of how to **incrementally upgrade** existing code to use animations*
|
||||
|
||||
2. [**Page transitions**](https://github.com/mdgriffith/elm-animator/blob/master/examples/Pages.elm) - Transition between pages 3d page transition with routing.
|
||||
- *Set up routing so there are no page **reloads*** (actually this is standard for SPAs in general).
|
||||
- *Animate the transition between pages*
|
||||
- How to do _**CSS Keyframe** generation_
|
||||
|
||||
3. [**Loading**](https://github.com/mdgriffith/elm-animator/blob/master/examples/Loading.elm) - Animate the loading state of a piece of content.
|
||||
- *How to animate **resting** states such as a loading spinner*
|
||||
- _Animate with content that's already been **deleted**_ (whaaaa?!)
|
||||
|
||||
4. [**Mario**](https://github.com/mdgriffith/elm-animator/blob/master/examples/Mario.elm) - The classic Mario example! Run around and jump.
|
||||
- *How to get started with **sprite animation***
|
||||
- *How to interact with a separate animation system such as physics code!*
|
||||
|
@ -1,359 +0,0 @@
|
||||
module Pages exposing (main)
|
||||
|
||||
{-| Animated page transitions!
|
||||
|
||||
This example is meant to show a few things.
|
||||
|
||||
1. That page transitions are just like animating any other state, we'll just create an `Animator.Timeline Page` and animate with that.
|
||||
2. How to use CSS keyframes by using the `Animator.Css` module
|
||||
3. How to handle routing so that the url changes as your transition.
|
||||
|
||||
-}
|
||||
|
||||
import Animator
|
||||
import Animator.Css
|
||||
import Animator.Inline
|
||||
import Browser
|
||||
import Browser.Events
|
||||
import Browser.Navigation
|
||||
import Color
|
||||
import Html exposing (..)
|
||||
import Html.Attributes as Attr
|
||||
import Html.Events as Events
|
||||
import Time
|
||||
import Url
|
||||
import Url.Builder
|
||||
import Url.Parser exposing ((</>))
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias Model =
|
||||
{ page : Animator.Timeline Page
|
||||
, navKey : Browser.Navigation.Key
|
||||
, needsUpdate : Bool
|
||||
}
|
||||
|
||||
|
||||
main =
|
||||
Browser.application
|
||||
{ init =
|
||||
\() url navKey ->
|
||||
let
|
||||
initialPage =
|
||||
Url.Parser.parse urlParser url
|
||||
|> Maybe.withDefault NotFound
|
||||
in
|
||||
( { page = Animator.init initialPage
|
||||
, navKey = navKey
|
||||
, needsUpdate = False
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
, view = view
|
||||
, update = update
|
||||
, subscriptions =
|
||||
\model ->
|
||||
Sub.batch
|
||||
[ animator
|
||||
|> Animator.toSubscription Tick model
|
||||
]
|
||||
, onUrlRequest = ClickedLink
|
||||
, onUrlChange = UrlChanged
|
||||
}
|
||||
|
||||
|
||||
|
||||
{- URL Handling -}
|
||||
|
||||
|
||||
type Page
|
||||
= Home
|
||||
| About
|
||||
| Blog
|
||||
| NotFound
|
||||
|
||||
|
||||
urlParser : Url.Parser.Parser (Page -> a) a
|
||||
urlParser =
|
||||
Url.Parser.oneOf
|
||||
[ Url.Parser.map Home Url.Parser.top
|
||||
, Url.Parser.map Blog (Url.Parser.s "blog")
|
||||
, Url.Parser.map About (Url.Parser.s "about")
|
||||
]
|
||||
|
||||
|
||||
pageToUrl : Page -> String
|
||||
pageToUrl page =
|
||||
case page of
|
||||
Home ->
|
||||
Url.Builder.absolute [] []
|
||||
|
||||
About ->
|
||||
Url.Builder.absolute [ "about" ] []
|
||||
|
||||
Blog ->
|
||||
Url.Builder.absolute [ "blog" ] []
|
||||
|
||||
NotFound ->
|
||||
Url.Builder.absolute [ "notfound" ] []
|
||||
|
||||
|
||||
animator : Animator.Animator Model
|
||||
animator =
|
||||
Animator.animator
|
||||
-- *NOTE* We're using `the Animator.Css.watching` instead of `Animator.watching`.
|
||||
-- Instead of asking for a constant stream of animation frames, it'll only ask for one
|
||||
-- and we'll render the entire css animation in that frame.
|
||||
|> Animator.Css.watching .page
|
||||
(\newPage model ->
|
||||
{ model | page = newPage }
|
||||
)
|
||||
|
||||
|
||||
|
||||
{- UPDATING -}
|
||||
|
||||
|
||||
type Msg
|
||||
= Tick Time.Posix
|
||||
| ClickedLink Browser.UrlRequest
|
||||
| UrlChanged Url.Url
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
Tick newTime ->
|
||||
( Animator.update newTime animator model
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ClickedLink request ->
|
||||
case request of
|
||||
Browser.InternalAnim url ->
|
||||
-- Note - Ideally, starting a new animation with `toNewPage` would only happen in `UrlChanged`
|
||||
-- which occurs immediately after this message if we use `Browser.Navigation.pushUrl`
|
||||
--
|
||||
-- However there seems to be a bug in elm where a subscription to animationFrame fails to fire
|
||||
-- if we start a new animation just in `UrlChanged`.
|
||||
-- Note, this seems to be a sepcial case with routing
|
||||
( toNewPage url model
|
||||
, Browser.Navigation.pushUrl model.navKey (Url.toString url)
|
||||
)
|
||||
|
||||
Browser.External url ->
|
||||
( model
|
||||
, Browser.Navigation.load url
|
||||
)
|
||||
|
||||
UrlChanged url ->
|
||||
-- This should be te only place we need to use `toNewPage`. See above note.
|
||||
( toNewPage url model
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
toNewPage : Url.Url -> Model -> Model
|
||||
toNewPage url model =
|
||||
let
|
||||
newPage =
|
||||
Url.Parser.parse urlParser url
|
||||
|> Maybe.withDefault NotFound
|
||||
in
|
||||
{ model
|
||||
| page =
|
||||
model.page
|
||||
-- full page animations involve moving some large stuff.
|
||||
-- in that case using a slower duration than normal is a good place to start.
|
||||
|> Animator.go Animator.verySlowly newPage
|
||||
}
|
||||
|
||||
|
||||
|
||||
{- Actually viewing our pages! -}
|
||||
|
||||
|
||||
view : Model -> Browser.Document Msg
|
||||
view model =
|
||||
{ title = "Animator - Page Transitions"
|
||||
, body =
|
||||
[ stylesheet
|
||||
, div
|
||||
[ Attr.class "root"
|
||||
]
|
||||
[ nav []
|
||||
[ link Home "Home"
|
||||
, link Blog "Blog"
|
||||
, link About "About"
|
||||
]
|
||||
, div
|
||||
[ Attr.class "page-row"
|
||||
]
|
||||
[ viewPage model.page
|
||||
Home
|
||||
{ title = "The home page"
|
||||
, content = loremIpsum
|
||||
}
|
||||
, viewPage model.page
|
||||
Blog
|
||||
{ title = "Blog"
|
||||
, content = loremIpsum
|
||||
}
|
||||
, viewPage model.page
|
||||
About
|
||||
{ title = "About"
|
||||
, content = loremIpsum
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
viewPage : Animator.Timeline Page -> Page -> { title : String, content : Html msg } -> Html Msg
|
||||
viewPage timeline page { title, content } =
|
||||
let
|
||||
wrapInLink html =
|
||||
if Animator.current timeline == page then
|
||||
html
|
||||
|
||||
else
|
||||
Html.a
|
||||
[ Attr.href (pageToUrl page)
|
||||
, Attr.style "cursor" "pointer"
|
||||
]
|
||||
[ html ]
|
||||
in
|
||||
Animator.Css.div timeline
|
||||
(pageAnimation page)
|
||||
[ Attr.class "page" ]
|
||||
[ Html.h2 [] [ Html.text title ]
|
||||
, loremIpsum
|
||||
]
|
||||
|> wrapInLink
|
||||
|
||||
|
||||
pageAnimation : Page -> List (Animator.Css.Attribute Page)
|
||||
pageAnimation page =
|
||||
[ Animator.Css.opacity <|
|
||||
\currentPage ->
|
||||
if currentPage == page then
|
||||
Animator.at 1
|
||||
|
||||
else
|
||||
Animator.at 0.4
|
||||
, Animator.Css.transform <|
|
||||
\currentPage ->
|
||||
if currentPage == page then
|
||||
Animator.Css.scale 5
|
||||
|
||||
else
|
||||
Animator.Css.scale 1
|
||||
|
||||
-- Here we're animating a style that's not directly supported by elm-animator.
|
||||
, Animator.Css.style "margin"
|
||||
(\float ->
|
||||
let
|
||||
str =
|
||||
String.fromFloat float
|
||||
in
|
||||
"0px " ++ str ++ "px"
|
||||
)
|
||||
(\currentPage ->
|
||||
if currentPage == page then
|
||||
Animator.at 1800
|
||||
|
||||
else
|
||||
Animator.at 0
|
||||
)
|
||||
|
||||
-- because we're zooming everywhere, we can adjust the border width so it still looks nice
|
||||
-- even when the page thumbnail is small.
|
||||
, Animator.Css.style "border-width"
|
||||
(\float ->
|
||||
String.fromFloat float ++ "px"
|
||||
)
|
||||
(\currentPage ->
|
||||
if currentPage == page then
|
||||
Animator.at 1
|
||||
|
||||
else
|
||||
Animator.at 5
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
|
||||
{- Less Exciting Stuff
|
||||
|
||||
Below here is some content and a stylesheet.
|
||||
|
||||
-}
|
||||
|
||||
|
||||
loremIpsum : Html msg
|
||||
loremIpsum =
|
||||
Html.div []
|
||||
[ Html.div []
|
||||
[ Html.text "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
|
||||
]
|
||||
, Html.div []
|
||||
[ Html.text "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of \"de Finibus Bonorum et Malorum\" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. The first line of Lorem Ipsum, \"Lorem ipsum dolor sit amet..\", comes from a line in section 1.10.32."
|
||||
]
|
||||
, Html.div []
|
||||
[ Html.text "The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for those interested. Sections 1.10.32 and 1.10.33 from \"de Finibus Bonorum et Malorum\" by Cicero are also reproduced in their exact original form, accompanied by English versions from the 1914 translation by H. Rackham."
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
link : Page -> String -> Html msg
|
||||
link page label =
|
||||
Html.a
|
||||
[ Attr.href (pageToUrl page)
|
||||
, Attr.style "margin-right" "12px"
|
||||
]
|
||||
[ Html.text label ]
|
||||
|
||||
|
||||
stylesheet : Html msg
|
||||
stylesheet =
|
||||
Html.node "style"
|
||||
[]
|
||||
[ text """@import url('https://fonts.googleapis.com/css?family=Roboto&display=swap');
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
.root {
|
||||
width: 100%;
|
||||
height: 1000px;
|
||||
font-size: 16px;
|
||||
user-select: none;
|
||||
padding: 50px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
.page-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 100px;
|
||||
transform: scale(0.2);
|
||||
}
|
||||
.page {
|
||||
width: 500px;
|
||||
padding: 48px;
|
||||
border: 1px solid black;
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
"""
|
||||
]
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
".",
|
||||
"src",
|
||||
"../src"
|
||||
],
|
||||
"elm-version": "0.19.1",
|
||||
@ -9,33 +9,35 @@
|
||||
"direct": {
|
||||
"avh4/elm-color": "1.0.0",
|
||||
"elm/browser": "1.0.2",
|
||||
"elm/core": "1.0.2",
|
||||
"elm/core": "1.0.5",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/json": "1.1.3",
|
||||
"elm/svg": "1.0.1",
|
||||
"elm/time": "1.0.0",
|
||||
"elm/url": "1.0.0",
|
||||
"ianmackenzie/elm-geometry": "2.0.0",
|
||||
"ianmackenzie/elm-units": "2.2.0",
|
||||
"terezka/line-charts": "2.0.0"
|
||||
"ianmackenzie/elm-geometry": "4.0.0",
|
||||
"ianmackenzie/elm-units": "2.10.0",
|
||||
"mdgriffith/elm-bezier": "1.0.0",
|
||||
"terezka/line-charts": "2.0.2"
|
||||
},
|
||||
"indirect": {
|
||||
"debois/elm-dom": "1.3.0",
|
||||
"elm/parser": "1.1.0",
|
||||
"elm/virtual-dom": "1.0.2",
|
||||
"elm/random": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.3",
|
||||
"ianmackenzie/elm-1d-parameter": "1.0.1",
|
||||
"ianmackenzie/elm-float-extra": "1.1.0",
|
||||
"ianmackenzie/elm-interval": "2.0.0",
|
||||
"ianmackenzie/elm-triangular-mesh": "1.0.2",
|
||||
"ianmackenzie/elm-units-interval": "1.0.0",
|
||||
"justinmimbs/date": "3.2.0",
|
||||
"justinmimbs/time-extra": "1.1.0",
|
||||
"myrho/elm-round": "1.0.4",
|
||||
"ryannhg/date-format": "2.3.0"
|
||||
"ianmackenzie/elm-interval": "3.1.0",
|
||||
"ianmackenzie/elm-triangular-mesh": "1.1.0",
|
||||
"ianmackenzie/elm-units-interval": "3.2.0",
|
||||
"justinmimbs/date": "4.1.0",
|
||||
"justinmimbs/time-extra": "1.2.0",
|
||||
"myrho/elm-round": "1.0.5",
|
||||
"ryan-haskell/date-format": "1.0.0"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
{
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
"src"
|
||||
],
|
||||
"elm-version": "0.19.1",
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"elm/browser": "1.0.2",
|
||||
"elm/core": "1.0.5",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/json": "1.1.3",
|
||||
"elm/svg": "1.0.1"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/time": "1.0.0",
|
||||
"elm/url": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.2"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
@ -1,438 +0,0 @@
|
||||
module Bezier exposing (main)
|
||||
|
||||
{-| -}
|
||||
|
||||
import Browser
|
||||
import Browser.Events
|
||||
import Html
|
||||
import Html.Attributes as Attr
|
||||
import Html.Events
|
||||
import Json.Decode as Decode
|
||||
import Svg exposing (..)
|
||||
import Svg.Attributes exposing (..)
|
||||
|
||||
|
||||
main =
|
||||
Browser.element
|
||||
{ init =
|
||||
\() ->
|
||||
( { moving = NoMovement
|
||||
, one = Point 0 0.5
|
||||
, controlOne = Point 0.4 0.5
|
||||
, controlTwo = Point 0.2 0
|
||||
, two = Point 1 0
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
, view = view
|
||||
, update =
|
||||
update
|
||||
, subscriptions =
|
||||
subscriptions
|
||||
}
|
||||
|
||||
|
||||
subscriptions model =
|
||||
case model.moving of
|
||||
NoMovement ->
|
||||
Sub.none
|
||||
|
||||
_ ->
|
||||
Sub.batch
|
||||
[ Browser.Events.onMouseUp (Decode.succeed EndMovement)
|
||||
, Browser.Events.onMouseMove
|
||||
(Decode.map MouseCoords
|
||||
(Decode.map2
|
||||
Point
|
||||
(Decode.field "pageX" Decode.float)
|
||||
(Decode.field "pageY" Decode.float)
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
controlOneFull =
|
||||
Point 1 0.5
|
||||
|
||||
|
||||
controlTwoFull =
|
||||
Point 0 0
|
||||
|
||||
|
||||
type Movement
|
||||
= NoMovement
|
||||
| Start Entity
|
||||
| Moving
|
||||
{ entity : Entity
|
||||
, startedAt : Point
|
||||
, currentlyAt : Point
|
||||
}
|
||||
|
||||
|
||||
type Entity
|
||||
= Top
|
||||
| Bottom
|
||||
|
||||
|
||||
type Msg
|
||||
= StartMovementForTop
|
||||
| StartMovementForBottom
|
||||
| MouseCoords Point
|
||||
| EndMovement
|
||||
| ResetTo Coords
|
||||
|
||||
|
||||
type alias Coords =
|
||||
{ one : Point
|
||||
, controlOne : Point
|
||||
, controlTwo : Point
|
||||
, two : Point
|
||||
}
|
||||
|
||||
|
||||
linear =
|
||||
{ one = Point 0 0.5
|
||||
, controlOne = Point 0 0.5
|
||||
, controlTwo = Point 1 0
|
||||
, two = Point 1 0
|
||||
}
|
||||
|
||||
|
||||
default =
|
||||
{ one = Point 0 0.5
|
||||
, controlOne = Point 0.4 0.5
|
||||
, controlTwo = Point 0.2 0
|
||||
, two = Point 1 0
|
||||
}
|
||||
|
||||
|
||||
type alias Point =
|
||||
{ x : Float
|
||||
, y : Float
|
||||
}
|
||||
|
||||
|
||||
update msg model =
|
||||
case msg of
|
||||
StartMovementForTop ->
|
||||
( { model | moving = Start Top }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
StartMovementForBottom ->
|
||||
( { model | moving = Start Bottom }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
MouseCoords coords ->
|
||||
case model.moving of
|
||||
NoMovement ->
|
||||
( model
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Start entity ->
|
||||
( { model
|
||||
| moving =
|
||||
Moving
|
||||
{ entity = entity
|
||||
, startedAt = coords
|
||||
, currentlyAt = coords
|
||||
}
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Moving xy ->
|
||||
( { model
|
||||
| moving =
|
||||
Moving { xy | currentlyAt = coords }
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
EndMovement ->
|
||||
( { model
|
||||
| moving = NoMovement
|
||||
, controlOne = adjust Bottom model.moving model.controlOne
|
||||
, controlTwo = adjust Top model.moving model.controlTwo
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
ResetTo coords ->
|
||||
( { model
|
||||
| one = coords.one
|
||||
, controlOne = coords.controlOne
|
||||
, controlTwo = coords.controlTwo
|
||||
, two = coords.two
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
view model =
|
||||
let
|
||||
controlOne =
|
||||
adjust Bottom model.moving model.controlOne
|
||||
|
||||
controlTwo =
|
||||
adjust Top model.moving model.controlTwo
|
||||
in
|
||||
Html.div [ Attr.style "user-select" "none" ]
|
||||
[ Html.div
|
||||
[ Attr.style "display" "flex"
|
||||
, Attr.style "flex-direction" "row"
|
||||
, Attr.style "width" "600px"
|
||||
, Attr.style "box-sizing" "border-box"
|
||||
, Attr.style "justify-content" "center"
|
||||
, Attr.style "padding-top" "50px"
|
||||
]
|
||||
[ Html.button
|
||||
[ Attr.style "border" "2px solid black"
|
||||
, Attr.style "border-radius" "3px"
|
||||
, Attr.style "background-color" "white"
|
||||
, Attr.style "cursor" "pointer"
|
||||
, Attr.style "padding" "8px 12px"
|
||||
, Html.Events.onClick (ResetTo linear)
|
||||
]
|
||||
[ Html.text "Linear" ]
|
||||
, Html.button
|
||||
[ Attr.style "border" "2px solid black"
|
||||
, Attr.style "border-radius" "3px"
|
||||
, Attr.style "background-color" "white"
|
||||
, Attr.style "cursor" "pointer"
|
||||
, Attr.style "padding" "8px 12px"
|
||||
, Html.Events.onClick (ResetTo default)
|
||||
, Attr.style "margin-left" "20px"
|
||||
]
|
||||
[ Html.text "Elm Animator Default" ]
|
||||
]
|
||||
, svg
|
||||
[ width "800"
|
||||
, height "1000"
|
||||
, viewBox "-0.2 -0.2 1.8 1.8"
|
||||
]
|
||||
[ Svg.path
|
||||
[ fill "none"
|
||||
, stroke "black"
|
||||
, strokeWidth "0.005"
|
||||
, d (renderPath { model | controlOne = controlOne, controlTwo = controlTwo })
|
||||
]
|
||||
[]
|
||||
, viewLine model.one controlOne
|
||||
, viewLine model.two controlTwo
|
||||
, viewScale model.one controlOneFull "Leave Smoothly"
|
||||
|> move 0 0.15
|
||||
, viewScale model.two controlTwoFull "Arrive Smoothly"
|
||||
|> move 0 -0.15
|
||||
, viewCircle model.one "Start"
|
||||
, viewMovableCircle
|
||||
{ onMouseDown = StartMovementForBottom
|
||||
, isMoving = somethingMoving model.moving
|
||||
, point = controlOne
|
||||
, toLabel = fromFloat << .x
|
||||
}
|
||||
, viewCircle model.two "End"
|
||||
, viewMovableCircle
|
||||
{ onMouseDown = StartMovementForTop
|
||||
, isMoving = somethingMoving model.moving
|
||||
, point = controlTwo
|
||||
, toLabel = \p -> fromFloat (1 - p.x)
|
||||
}
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
somethingMoving moving =
|
||||
case moving of
|
||||
Moving _ ->
|
||||
True
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
|
||||
fromFloat f =
|
||||
String.fromFloat f
|
||||
|> String.left 4
|
||||
|
||||
|
||||
adjust label moving point =
|
||||
case moving of
|
||||
NoMovement ->
|
||||
point
|
||||
|
||||
Start _ ->
|
||||
point
|
||||
|
||||
Moving movement ->
|
||||
if movement.entity == label then
|
||||
{ x =
|
||||
point.x
|
||||
- (factor * (movement.startedAt.x - movement.currentlyAt.x))
|
||||
|> Basics.max 0
|
||||
|> Basics.min 1
|
||||
, y = point.y
|
||||
}
|
||||
|
||||
else
|
||||
point
|
||||
|
||||
|
||||
factor =
|
||||
1.8 / 800
|
||||
|
||||
|
||||
viewCircle point label =
|
||||
g []
|
||||
[ circle
|
||||
[ cx (String.fromFloat point.x)
|
||||
, cy (String.fromFloat point.y)
|
||||
, r "0.02"
|
||||
]
|
||||
[]
|
||||
, text_
|
||||
[ fontSize "0.05"
|
||||
, x (String.fromFloat point.x)
|
||||
, y (String.fromFloat (point.y + 0.065))
|
||||
, textAnchor "middle"
|
||||
]
|
||||
[ text label ]
|
||||
]
|
||||
|
||||
|
||||
viewMovableCircle details =
|
||||
g
|
||||
[ Html.Events.onMouseDown details.onMouseDown
|
||||
, Attr.style "cursor"
|
||||
(if details.isMoving then
|
||||
"grabbing"
|
||||
|
||||
else
|
||||
"grab"
|
||||
)
|
||||
]
|
||||
[ circle
|
||||
[ cx (String.fromFloat details.point.x)
|
||||
, cy (String.fromFloat details.point.y)
|
||||
, r "0.02"
|
||||
, fill "red"
|
||||
]
|
||||
[]
|
||||
, text_
|
||||
[ fontSize "0.05"
|
||||
, x (String.fromFloat details.point.x)
|
||||
, y (String.fromFloat (details.point.y - 0.04))
|
||||
, textAnchor "middle"
|
||||
]
|
||||
[ text (details.toLabel details.point) ]
|
||||
]
|
||||
|
||||
|
||||
viewLine one two =
|
||||
line
|
||||
[ x1 (String.fromFloat one.x)
|
||||
, y1 (String.fromFloat one.y)
|
||||
, x2 (String.fromFloat two.x)
|
||||
, y2 (String.fromFloat two.y)
|
||||
, stroke "black"
|
||||
, strokeWidth "0.005"
|
||||
, strokeDasharray "0.01 0.01"
|
||||
]
|
||||
[]
|
||||
|
||||
|
||||
move x y content =
|
||||
g
|
||||
[ transform
|
||||
("translate(" ++ String.fromFloat x ++ "," ++ String.fromFloat y ++ ")")
|
||||
]
|
||||
[ content ]
|
||||
|
||||
|
||||
viewScale one two label =
|
||||
g
|
||||
[]
|
||||
[ line
|
||||
[ x1 (String.fromFloat one.x)
|
||||
, y1 (String.fromFloat one.y)
|
||||
, x2 (String.fromFloat two.x)
|
||||
, y2 (String.fromFloat two.y)
|
||||
, stroke "#EEE"
|
||||
, strokeWidth "0.005"
|
||||
]
|
||||
[]
|
||||
, viewText
|
||||
{ width = 0.35
|
||||
, height = 0.05
|
||||
, label = label
|
||||
, x = abs (two.x - one.x) / 2
|
||||
, y = one.y
|
||||
}
|
||||
, viewText
|
||||
{ width = 0.05
|
||||
, height = 0.05
|
||||
, label = "0"
|
||||
, x = one.x
|
||||
, y = one.y
|
||||
}
|
||||
, viewText
|
||||
{ width = 0.05
|
||||
, height = 0.05
|
||||
, label = "1"
|
||||
, x = two.x
|
||||
, y = two.y
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
viewText details =
|
||||
g []
|
||||
[ rect
|
||||
[ x
|
||||
(String.fromFloat
|
||||
(details.x
|
||||
- (details.width / 2)
|
||||
)
|
||||
)
|
||||
, y
|
||||
(String.fromFloat
|
||||
(details.y - (details.height / 2))
|
||||
)
|
||||
, width (String.fromFloat details.width)
|
||||
, height (String.fromFloat details.height)
|
||||
, fill "white"
|
||||
]
|
||||
[]
|
||||
, text_
|
||||
[ fontSize "0.05"
|
||||
, x
|
||||
(String.fromFloat
|
||||
details.x
|
||||
)
|
||||
, y
|
||||
(String.fromFloat
|
||||
((details.y + (details.height / 2)) - 0.01)
|
||||
)
|
||||
, textAnchor "middle"
|
||||
]
|
||||
[ text details.label ]
|
||||
]
|
||||
|
||||
|
||||
renderPath points =
|
||||
("M " ++ renderPoint points.one)
|
||||
++ (" C "
|
||||
++ ([ points.controlOne
|
||||
, points.controlTwo
|
||||
, points.two
|
||||
]
|
||||
|> List.map renderPoint
|
||||
|> String.join " "
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
renderPoint point =
|
||||
String.fromFloat point.x ++ "," ++ String.fromFloat point.y
|
Binary file not shown.
Before Width: | Height: | Size: 78 KiB |
Binary file not shown.
Before Width: | Height: | Size: 39 KiB |
Binary file not shown.
Before Width: | Height: | Size: 21 KiB |
343
examples/src/Loading.elm
Normal file
343
examples/src/Loading.elm
Normal file
@ -0,0 +1,343 @@
|
||||
module Loading exposing (main)
|
||||
|
||||
{-| Animating loading states!
|
||||
|
||||
It's pretty common to have a type, usually called RemoteData, to represent a piece of data that's been requested from the server.
|
||||
|
||||
It generally looks something like this:
|
||||
|
||||
type RemoteData error data
|
||||
= NotAsked
|
||||
| Loading
|
||||
| Failure error
|
||||
| Success data
|
||||
|
||||
This example will show you how to:
|
||||
|
||||
1. Animate a "resting" state, so that when we're at a state of `Loading`, a a loading animation is occurring.
|
||||
2. Animate content that's already been deleted! In this case we can show the previously retrieved comment, even whil we're in a loading state.
|
||||
3. How to debug animations with `Animator.Css.explain`. This will show you a bounding box for an element, as well as it's center of rotation.
|
||||
|
||||
Search for (1), (2), and (3) respectively to see the code!
|
||||
|
||||
-}
|
||||
|
||||
import Animator
|
||||
import Animator.Timeline as Timeline
|
||||
import Browser
|
||||
import Browser.Events
|
||||
import Color
|
||||
import Html exposing (..)
|
||||
import Html.Attributes as Attr
|
||||
import Html.Events as Events
|
||||
import Process
|
||||
import Task
|
||||
import Time
|
||||
|
||||
|
||||
type RemoteData error data
|
||||
= NotAsked
|
||||
| Loading
|
||||
| Failure error
|
||||
| Success data
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ comment :
|
||||
Timeline.Timeline (RemoteData String String)
|
||||
}
|
||||
|
||||
|
||||
main =
|
||||
Browser.document
|
||||
{ init =
|
||||
\() ->
|
||||
( { comment =
|
||||
Timeline.init NotAsked
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
, view = view
|
||||
, update = update
|
||||
, subscriptions =
|
||||
\model ->
|
||||
if Timeline.isRunning model.comment then
|
||||
Browser.Events.onAnimationFrame Tick
|
||||
|
||||
else
|
||||
Sub.none
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= Tick Time.Posix
|
||||
| AskServerForNewComment
|
||||
| NewCommentFromServer String
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
Tick newTime ->
|
||||
( { model | comment = Timeline.update newTime model.comment }
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
AskServerForNewComment ->
|
||||
( { model
|
||||
| comment =
|
||||
model.comment
|
||||
|> Timeline.to (Animator.ms 300) Loading
|
||||
}
|
||||
, Task.perform (always (NewCommentFromServer "Howdy partner!"))
|
||||
(Process.sleep (2 * 1000))
|
||||
)
|
||||
|
||||
NewCommentFromServer comment ->
|
||||
( { model
|
||||
| comment =
|
||||
model.comment
|
||||
|> Timeline.to (Animator.ms 300) (Success comment)
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
view : Model -> Browser.Document Msg
|
||||
view model =
|
||||
{ title = "Animator - Loading"
|
||||
, body =
|
||||
[ stylesheet
|
||||
, div [ Attr.class "root" ]
|
||||
[ div
|
||||
[ Attr.class "viewport"
|
||||
]
|
||||
[ viewComment model.comment
|
||||
, Html.div
|
||||
[ Attr.style "display" "flex"
|
||||
, Attr.style "flex-direction" "row"
|
||||
, Attr.style "justify-content" "space-between"
|
||||
, Attr.style "width" "100%"
|
||||
]
|
||||
[ Html.button
|
||||
[ Attr.class "button"
|
||||
, Events.onClick AskServerForNewComment
|
||||
]
|
||||
[ Html.text "Load comment" ]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
viewComment : Timeline.Timeline (RemoteData String String) -> Html Msg
|
||||
viewComment commentTimeline =
|
||||
Animator.div
|
||||
(Animator.onTimeline commentTimeline
|
||||
(\state ->
|
||||
case state of
|
||||
Loading ->
|
||||
[ Animator.color "background-color"
|
||||
(Color.rgb 0.9 0.9 0.9)
|
||||
, Animator.color "color"
|
||||
(Color.rgb 0.5 0.5 0.5)
|
||||
, Animator.color "border-color"
|
||||
(Color.rgb 0.5 0.5 0.5)
|
||||
]
|
||||
|
||||
_ ->
|
||||
[ Animator.color "background-color"
|
||||
(Color.rgb 1 1 1)
|
||||
, Animator.color "color"
|
||||
(Color.rgb 0 0 0)
|
||||
, Animator.color "border-color"
|
||||
(Color.rgb 0 0 0)
|
||||
]
|
||||
)
|
||||
)
|
||||
[ Attr.style "position" "relative"
|
||||
, Attr.style "display" "flex"
|
||||
, Attr.style "flex-direction" "column"
|
||||
, Attr.style "justify-content" "space-between"
|
||||
, Attr.style "font-size" "16px"
|
||||
, Attr.style "width" "100%"
|
||||
, Attr.style "height" "100px"
|
||||
, Attr.style "padding" "24px"
|
||||
, Attr.style "box-sizing" "border-box"
|
||||
, Attr.style "border" "1px solid black"
|
||||
, Attr.style "border-radius" "3px"
|
||||
]
|
||||
[ loadingIndicator commentTimeline
|
||||
, case Timeline.current commentTimeline of
|
||||
NotAsked ->
|
||||
Html.div [ Attr.style "color" "#CCC" ] [ Html.text "No comment loaded" ]
|
||||
|
||||
Loading ->
|
||||
Html.div []
|
||||
-- (2) - We can still show the previous comment!
|
||||
-- If we're loading a new one, we'll grey it out.
|
||||
[ case Timeline.previous commentTimeline of
|
||||
Success text ->
|
||||
Html.div
|
||||
[ Attr.style "display" "flex"
|
||||
, Attr.style "flex-direction" "row"
|
||||
, Attr.style "filter" "grayscale(1)"
|
||||
]
|
||||
[ viewThinking
|
||||
, Html.text text
|
||||
]
|
||||
|
||||
_ ->
|
||||
Html.div [] [ Html.text "Loading..." ]
|
||||
]
|
||||
|
||||
Failure error ->
|
||||
Html.div [] [ Html.text error ]
|
||||
|
||||
Success text ->
|
||||
Html.div
|
||||
[ Attr.style "display" "flex"
|
||||
, Attr.style "flex-direction" "row"
|
||||
]
|
||||
[ -- viewCowboy
|
||||
-- -- (3) - This `debug` field will turn on `Animator.Css.explain` for the elements animated in the `viewCowboy` function.
|
||||
-- -- See the other note labeled (3) to get a better idea of what that means.
|
||||
-- { debug = False
|
||||
-- , timeline = comment
|
||||
-- }
|
||||
-- ,
|
||||
Html.text text
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
loadingIndicator : Timeline.Timeline (RemoteData error success) -> Html msg
|
||||
loadingIndicator loading =
|
||||
Html.div
|
||||
[ Attr.class "loading-indicator"
|
||||
]
|
||||
[ viewBlinkingCircle 0.2 loading
|
||||
, viewBlinkingCircle 0.3 loading
|
||||
, viewBlinkingCircle 0.4 loading
|
||||
]
|
||||
|
||||
|
||||
viewBlinkingCircle offset timeline =
|
||||
Animator.div
|
||||
(Animator.onTimeline timeline <|
|
||||
\state ->
|
||||
case state of
|
||||
Loading ->
|
||||
-- (1) - When we're loading, use a sine wave to oscillate between 0.05 and 1
|
||||
-- Becase we want multiple blinking circles, we can als "shift" this wave over by some amount.
|
||||
-- A shift of 0 means the value starts at 0.05.
|
||||
-- A shift of 1 would mean the value starts at the "end", at 1.
|
||||
-- Then we tell it to take 700ms for a full loop
|
||||
[ -- Animator.wave 0.05 1
|
||||
-- |> Animator.shift offset
|
||||
-- |> Animator.loop (Animator.millis 700)
|
||||
Animator.opacity 1
|
||||
]
|
||||
|
||||
_ ->
|
||||
[ Animator.opacity 0
|
||||
]
|
||||
)
|
||||
[ Attr.class "circle"
|
||||
]
|
||||
[]
|
||||
|
||||
|
||||
viewThinking : Html msg
|
||||
viewThinking =
|
||||
Html.div
|
||||
[ Attr.class "thinking"
|
||||
]
|
||||
[ Html.text "🤔"
|
||||
]
|
||||
|
||||
|
||||
|
||||
{- STYLESHEET -}
|
||||
|
||||
|
||||
pink : String
|
||||
pink =
|
||||
"rgb(240, 0, 245)"
|
||||
|
||||
|
||||
stylesheet : Html msg
|
||||
stylesheet =
|
||||
Html.node "style"
|
||||
[]
|
||||
[ text """@import url('https://fonts.googleapis.com/css?family=Roboto&display=swap');
|
||||
|
||||
|
||||
|
||||
.circle {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
background-color: rgb(240, 0, 245);
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.thinking {
|
||||
display: flex;
|
||||
flex-direction: center;
|
||||
transform: translateY(-6px);
|
||||
width: 72px;
|
||||
font-size: 24px;
|
||||
margin-right: 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
.loading-indicator {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
box-sizing: border-box;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.root {
|
||||
width: 100%;
|
||||
font-size: 48px;
|
||||
user-select: none;
|
||||
padding: 50px;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
padding: 200px;
|
||||
}
|
||||
.viewport {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
width: 400px;
|
||||
|
||||
}
|
||||
.button {
|
||||
background-color: rgb(240, 0, 245);
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
margin-top: 20px;
|
||||
|
||||
}
|
||||
.cowboy {
|
||||
display: flex;
|
||||
transform: translateY(-6px);
|
||||
width: 72px;
|
||||
font-size: 24px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
"""
|
||||
]
|
152
examples/src/TimelineValue.elm
Normal file
152
examples/src/TimelineValue.elm
Normal file
@ -0,0 +1,152 @@
|
||||
module TimelineValue exposing (main)
|
||||
|
||||
import Animator
|
||||
import Animator.Timeline
|
||||
import Animator.Transition
|
||||
import Animator.Value
|
||||
import Browser
|
||||
import Browser.Events
|
||||
import Html
|
||||
import Html.Attributes as Attr
|
||||
import Html.Events
|
||||
import Time
|
||||
|
||||
|
||||
main : Program () Model Msg
|
||||
main =
|
||||
Browser.element
|
||||
{ init = always init
|
||||
, update = update
|
||||
, subscriptions = subscriptions
|
||||
, view = view
|
||||
}
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ timeline : Animator.Timeline.Timeline Float
|
||||
, historyOne : List Float
|
||||
, historyTwo : List Float
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= NewPosix Time.Posix
|
||||
| Start
|
||||
|
||||
|
||||
init : ( Model, Cmd Msg )
|
||||
init =
|
||||
let
|
||||
initialTimeline : Animator.Timeline.Timeline Float
|
||||
initialTimeline =
|
||||
Animator.Timeline.init 10
|
||||
|
||||
queuedSteps : List (Animator.Timeline.Step Float)
|
||||
queuedSteps =
|
||||
[ Animator.Timeline.transitionTo (Animator.ms 1000) 100
|
||||
, Animator.Timeline.transitionTo (Animator.ms 1000) 50
|
||||
, Animator.Timeline.transitionTo (Animator.ms 1000) 5
|
||||
]
|
||||
|
||||
timelineWithSteps : Animator.Timeline.Timeline Float
|
||||
timelineWithSteps =
|
||||
Animator.Timeline.scale 3 <| Animator.Timeline.queue queuedSteps initialTimeline
|
||||
in
|
||||
( { timeline = timelineWithSteps
|
||||
, historyOne = []
|
||||
, historyTwo = []
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
NewPosix posix ->
|
||||
( { model
|
||||
| timeline = Animator.Timeline.update posix model.timeline
|
||||
, historyOne = Animator.Value.float model.timeline Animator.Value.to :: model.historyOne
|
||||
, historyTwo = Animator.Value.float model.timeline (Animator.Value.withTransition Animator.Transition.linear << Animator.Value.to) :: model.historyTwo
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
Start ->
|
||||
init
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
if Animator.Timeline.isRunning model.timeline then
|
||||
Browser.Events.onAnimationFrame NewPosix
|
||||
|
||||
else
|
||||
Sub.none
|
||||
|
||||
|
||||
view : Model -> Html.Html Msg
|
||||
view model =
|
||||
let
|
||||
positionStandard : Float
|
||||
positionStandard =
|
||||
Animator.Value.float model.timeline Animator.Value.to
|
||||
|
||||
positionLinear : Float
|
||||
positionLinear =
|
||||
Animator.Value.float model.timeline (Animator.Value.withTransition Animator.Transition.linear << Animator.Value.to)
|
||||
in
|
||||
Html.div []
|
||||
[ row [ Attr.style "width" "600px", Attr.style "justify-content" "center", Attr.style "align-items" "center" ]
|
||||
[ column [ Attr.style "align-self" "flex-start", Attr.style "width" "200px", Attr.style "margin-top" "50px" ]
|
||||
[ Html.div []
|
||||
[ Html.div [] [ Html.text ("Position Standard: " ++ String.fromFloat positionStandard) ]
|
||||
, Html.div [] [ Html.text ("Position Linear: " ++ String.fromFloat positionLinear) ]
|
||||
]
|
||||
, Html.button [ Html.Events.onClick Start ] [ Html.text "Re-Start" ]
|
||||
]
|
||||
, column [ Attr.style "width" "200px" ]
|
||||
(List.map (\x -> circle [ positionX x ] []) model.historyOne)
|
||||
, column [ Attr.style "width" "200px" ]
|
||||
(List.map (\x -> circle [ positionX x ] []) model.historyTwo)
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
row : List (Html.Attribute msg) -> List (Html.Html msg) -> Html.Html msg
|
||||
row attributes columns =
|
||||
Html.div
|
||||
(Attr.style "display" "flex"
|
||||
:: Attr.style "flex-direction" "row"
|
||||
:: Attr.style "gap" "10px"
|
||||
:: attributes
|
||||
)
|
||||
columns
|
||||
|
||||
|
||||
column : List (Html.Attribute msg) -> List (Html.Html msg) -> Html.Html msg
|
||||
column attributes columns =
|
||||
Html.div
|
||||
(Attr.style "display" "flex"
|
||||
:: Attr.style "flex-direction" "column"
|
||||
:: Attr.style "gap" "10px"
|
||||
:: attributes
|
||||
)
|
||||
columns
|
||||
|
||||
|
||||
circle : List (Html.Attribute msg) -> List (Html.Html msg) -> Html.Html msg
|
||||
circle attributes content =
|
||||
Html.div
|
||||
(Attr.style "width" "2px"
|
||||
:: Attr.style "height" "2px"
|
||||
:: Attr.style "border-radius" "50%"
|
||||
:: Attr.style "background-color" "black"
|
||||
:: attributes
|
||||
)
|
||||
content
|
||||
|
||||
|
||||
positionX : Float -> Html.Attribute msg
|
||||
positionX x =
|
||||
Attr.style "transform" ("translateX(" ++ String.fromFloat x ++ "px)")
|
@ -45,7 +45,8 @@ config =
|
||||
-- }
|
||||
-- ,
|
||||
Docs.ReviewLinksAndSections.rule
|
||||
, Docs.ReviewAtDocs.rule
|
||||
|
||||
-- , Docs.ReviewAtDocs.rule
|
||||
, Docs.UpToDateReadmeLinks.rule
|
||||
, NoConfusingPrefixOperator.rule
|
||||
, NoDebug.Log.rule
|
||||
@ -54,8 +55,6 @@ config =
|
||||
, NoExposingEverything.rule
|
||||
, NoImportingEverything.rule []
|
||||
, NoMissingTypeAnnotation.rule
|
||||
|
||||
-- , NoMissingTypeAnnotationInLetIn.rule
|
||||
, NoMissingTypeExpose.rule
|
||||
, NoSimpleLetBody.rule
|
||||
, NoPrematureLetComputation.rule
|
||||
@ -63,7 +62,6 @@ config =
|
||||
, NoUnused.CustomTypeConstructorArgs.rule
|
||||
, NoUnused.Dependencies.rule
|
||||
, NoUnused.Exports.rule
|
||||
, NoUnused.Parameters.rule
|
||||
, NoUnused.Patterns.rule
|
||||
, NoUnused.Variables.rule
|
||||
, Simplify.rule Simplify.defaults
|
||||
|
@ -118,16 +118,6 @@ opacity o =
|
||||
InternalAnim.Css.Props.float
|
||||
|
||||
|
||||
{-| -}
|
||||
xAsSingleProp : Float -> Attribute
|
||||
xAsSingleProp o =
|
||||
Css.Prop
|
||||
InternalAnim.Css.Props.ids.opacity
|
||||
"transform"
|
||||
(Move.to o)
|
||||
InternalAnim.Css.Props.translateX
|
||||
|
||||
|
||||
{-| -}
|
||||
scale : Float -> Attribute
|
||||
scale s =
|
||||
@ -189,6 +179,7 @@ rotationAround axis n =
|
||||
(InternalAnim.Css.Props.turns axis)
|
||||
|
||||
|
||||
zAxis : { x : Float, y : Float, z : Float }
|
||||
zAxis =
|
||||
{ x = 0, y = 0, z = 1 }
|
||||
|
||||
@ -261,21 +252,6 @@ ms =
|
||||
Duration.milliseconds
|
||||
|
||||
|
||||
{-| When transitioning to this state, start with a little extra velocity!
|
||||
|
||||
This takes a number from 0-1.
|
||||
|
||||
-}
|
||||
withImpulse : Float -> Attribute -> Attribute
|
||||
withImpulse impulse prop =
|
||||
case prop of
|
||||
Css.Prop id name move format ->
|
||||
Css.Prop id name (Move.withVelocities impulse 0 move) format
|
||||
|
||||
Css.ColorProp name move ->
|
||||
Css.ColorProp name (Move.withVelocities impulse 0 move)
|
||||
|
||||
|
||||
{-| -}
|
||||
delay : Duration -> Animation -> Animation
|
||||
delay dur (Animation now attrs) =
|
||||
@ -340,7 +316,7 @@ keyframes steps =
|
||||
[] ->
|
||||
[]
|
||||
|
||||
(Step dur props) :: _ ->
|
||||
(Step _ props) :: _ ->
|
||||
props
|
||||
, now = imminent
|
||||
, delay = Time.zeroDuration
|
||||
@ -508,12 +484,12 @@ formatColorSteps steps prop pastSteps =
|
||||
Nothing ->
|
||||
List.reverse pastSteps
|
||||
|
||||
Just (Css.Prop id name _ format) ->
|
||||
Just (Css.Prop _ _ _ _) ->
|
||||
formatColorSteps next
|
||||
prop
|
||||
pastSteps
|
||||
|
||||
Just (Css.ColorProp name (Move.Pos trans value _)) ->
|
||||
Just (Css.ColorProp _ (Move.Pos trans value _)) ->
|
||||
formatColorSteps next
|
||||
prop
|
||||
(Move.stepWith dur trans value :: pastSteps)
|
||||
@ -534,12 +510,12 @@ formatSteps steps prop pastSteps =
|
||||
Nothing ->
|
||||
List.reverse pastSteps
|
||||
|
||||
Just (Css.Prop id name (Move.Pos trans value _) format) ->
|
||||
Just (Css.Prop _ _ (Move.Pos trans value _) _) ->
|
||||
formatSteps next
|
||||
prop
|
||||
(Move.stepWith dur trans value :: pastSteps)
|
||||
|
||||
Just (Css.ColorProp name movement) ->
|
||||
Just (Css.ColorProp _ _) ->
|
||||
formatSteps next
|
||||
prop
|
||||
pastSteps
|
||||
@ -683,7 +659,7 @@ pinging dur =
|
||||
]
|
||||
|
||||
|
||||
{-| Animate an element on a specific timeline. Check out [`Animator.Timeline`](#Animator/Timeline) for more details.
|
||||
{-| Animate an element on a specific timeline. Check out [`Animator.Timeline`](https://package.elm-lang.org/packages/mdgriffith/elm-animator/latest/Animator-Timeline) for more details.
|
||||
|
||||
This will
|
||||
|
||||
|
@ -6,6 +6,7 @@ module Animator.Timeline exposing
|
||||
, Step, wait, transitionTo
|
||||
, scale, delay
|
||||
, current, previous, upcoming, upcomingWith, arrived, arrivedAt, arrivedAtWith
|
||||
, Duration
|
||||
)
|
||||
|
||||
{-|
|
||||
@ -408,9 +409,6 @@ update =
|
||||
|
||||
|
||||
{-| Does this timeline have upcoming events?
|
||||
|
||||
**Note** this is only useful if you're not using a `Animator.Watcher`
|
||||
|
||||
-}
|
||||
isRunning : Timeline state -> Bool
|
||||
isRunning (Timeline.Timeline tl) =
|
||||
|
@ -93,11 +93,8 @@ movement : Timeline state -> (state -> Movement) -> { position : Float, velocity
|
||||
movement timeline lookup =
|
||||
Timeline.foldpAll lookup
|
||||
Move.init
|
||||
(\_ prev target now startTransition interruptedOrEnd future state ->
|
||||
(\_ _ target now startTransition interruptedOrEnd future state ->
|
||||
let
|
||||
arrived =
|
||||
Timeline.startTime target
|
||||
|
||||
isHappening =
|
||||
Time.thisAfterOrEqualThat now startTransition
|
||||
|| (List.isEmpty future
|
||||
@ -106,6 +103,9 @@ movement timeline lookup =
|
||||
in
|
||||
if isHappening then
|
||||
let
|
||||
arrived =
|
||||
Timeline.startTime target
|
||||
|
||||
progress =
|
||||
Time.progress startTransition arrived now
|
||||
|
||||
|
@ -1,224 +0,0 @@
|
||||
module Animator.Watcher exposing
|
||||
( Watching
|
||||
, init, watching, list
|
||||
, update, toSubscription
|
||||
)
|
||||
|
||||
{-| If you're tracking a number of timelines in your model, you may decide to use a `Watching` to simplify adding new timelines.
|
||||
|
||||
@docs Watching
|
||||
|
||||
@docs init, watching, list
|
||||
|
||||
@docs update, toSubscription
|
||||
|
||||
-}
|
||||
|
||||
import Animator.Timeline exposing (Timeline)
|
||||
import Browser.Events
|
||||
import InternalAnim.Time as Time
|
||||
import InternalAnim.Timeline as Timeline
|
||||
import Time
|
||||
|
||||
|
||||
{-| An `Animator` knows how to read and write all the `Timelines` within your `Model`.
|
||||
|
||||
Here's an animator from the [Checkbox.elm example](https://github.com/mdgriffith/elm-animator/blob/master/examples/Checkbox.elm),
|
||||
|
||||
import Animator.Watcher as Watcher
|
||||
|
||||
animator : Watcher.Watching Model
|
||||
animator =
|
||||
Watcher.init
|
||||
|> Watcher.watching
|
||||
-- we tell the animator how
|
||||
-- to get the checked timeline using .checked
|
||||
.checked
|
||||
-- and we tell the animator how
|
||||
-- to update that timeline as well
|
||||
(\newChecked model ->
|
||||
{ model | checked = newChecked }
|
||||
)
|
||||
|
||||
Notice you could add any number of timelines to this animator:
|
||||
|
||||
animator : Watcher.Watching Model
|
||||
animator =
|
||||
Watcher.init
|
||||
|> Watcher.watching
|
||||
.checked
|
||||
(\newChecked model ->
|
||||
{ model | checked = newChecked }
|
||||
)
|
||||
|> Watcher.watching
|
||||
.anotherChecked
|
||||
(\anotherCheckboxState ->
|
||||
{ model | anotherChecked = anotherCheckboxState }
|
||||
)
|
||||
|
||||
**Note** — You likely only need one animator for a given project.
|
||||
|
||||
**Note 2** — Once we have an `Animator Model`, we have two more steps in order to set things up:
|
||||
|
||||
- [create a _subscription_](#toSubscription)
|
||||
- [_update_ our model](#update)
|
||||
|
||||
-}
|
||||
type alias Watching model =
|
||||
Timeline.Animator model
|
||||
|
||||
|
||||
{-| -}
|
||||
init : Watching model
|
||||
init =
|
||||
Timeline.Animator (\_ -> { running = False, ping = Nothing }) (\now model -> model)
|
||||
|
||||
|
||||
{-| -}
|
||||
watching :
|
||||
(model -> Timeline state)
|
||||
-> (Timeline state -> model -> model)
|
||||
-> Watching model
|
||||
-> Watching model
|
||||
watching get setValue (Timeline.Animator isRunning updateModel) =
|
||||
Timeline.Animator
|
||||
(\model ->
|
||||
let
|
||||
prev =
|
||||
isRunning model
|
||||
|
||||
timeline =
|
||||
get model
|
||||
|
||||
ping =
|
||||
case Timeline.sendPing timeline of
|
||||
Nothing ->
|
||||
prev.ping
|
||||
|
||||
Just currentPing ->
|
||||
case prev.ping of
|
||||
Nothing ->
|
||||
Just currentPing
|
||||
|
||||
Just prevPing ->
|
||||
if prevPing.delay < currentPing.delay then
|
||||
Just prevPing
|
||||
|
||||
else
|
||||
Just currentPing
|
||||
|
||||
running =
|
||||
if prev.running then
|
||||
True
|
||||
|
||||
else
|
||||
Timeline.hasChanged timeline
|
||||
|| Timeline.justInitialized timeline
|
||||
in
|
||||
{ running = running
|
||||
, ping = ping
|
||||
}
|
||||
)
|
||||
(\now model ->
|
||||
let
|
||||
newModel =
|
||||
updateModel now model
|
||||
in
|
||||
setValue (Timeline.update now (get newModel)) newModel
|
||||
)
|
||||
|
||||
|
||||
{-| -}
|
||||
list :
|
||||
(model -> List item)
|
||||
-> (List item -> model -> model)
|
||||
-> Watching item
|
||||
-> Watching model
|
||||
-> Watching model
|
||||
list getItems setItems (Timeline.Animator getItemRunning updateItem) (Timeline.Animator getModelRunning updateModel) =
|
||||
Timeline.Animator
|
||||
(\model ->
|
||||
let
|
||||
modelRunning =
|
||||
getModelRunning model
|
||||
in
|
||||
List.foldl
|
||||
(\item running ->
|
||||
Timeline.combineRunning
|
||||
(getItemRunning item)
|
||||
running
|
||||
)
|
||||
modelRunning
|
||||
(getItems model)
|
||||
)
|
||||
(\now model ->
|
||||
let
|
||||
newModel =
|
||||
updateModel now model
|
||||
in
|
||||
setItems
|
||||
(List.map
|
||||
(updateItem now)
|
||||
(getItems newModel)
|
||||
)
|
||||
newModel
|
||||
)
|
||||
|
||||
|
||||
{-| Convert an `Animator` to a subscription.
|
||||
|
||||
This is where the animator will decide if a running animation needs another frame or not.
|
||||
|
||||
subscriptions model =
|
||||
Animator.toSubscription Tick model animator
|
||||
|
||||
-}
|
||||
toSubscription : (Time.Posix -> msg) -> model -> Watching model -> Sub msg
|
||||
toSubscription toMsg model (Timeline.Animator getContext _) =
|
||||
let
|
||||
context =
|
||||
getContext model
|
||||
in
|
||||
if context.running || not (context.ping == Nothing) then
|
||||
Sub.batch
|
||||
[ if context.running then
|
||||
Browser.Events.onAnimationFrame
|
||||
toMsg
|
||||
|
||||
else
|
||||
Sub.none
|
||||
, case context.ping of
|
||||
Just ping ->
|
||||
Time.every ping.delay
|
||||
(\time ->
|
||||
toMsg ping.target
|
||||
)
|
||||
|
||||
Nothing ->
|
||||
Sub.none
|
||||
]
|
||||
|
||||
else
|
||||
Sub.none
|
||||
|
||||
|
||||
{-| When new messages come in, we then need to update our model. This looks something like this:
|
||||
|
||||
type Msg
|
||||
= Tick Time.Posix
|
||||
|
||||
update msg model =
|
||||
case msg of
|
||||
Tick newTime ->
|
||||
( Animator.update newTime animator model
|
||||
, Cmd.none
|
||||
)
|
||||
|
||||
And voilà, we can begin animating!
|
||||
|
||||
**Note** — To animate more things, all you need to do is add a new `with` to your `Animator`.
|
||||
|
||||
-}
|
||||
update : Time.Posix -> Watching model -> model -> model
|
||||
update newTime (Timeline.Animator _ updateModel) model =
|
||||
updateModel newTime model
|
@ -1,4 +1,4 @@
|
||||
module InternalAnim.Bits exposing (Bits, has, init, off, on, store4, store4Float, value, zeroes)
|
||||
module InternalAnim.Bits exposing (Bits, store4Float, value)
|
||||
|
||||
{-| Let's make storing values within a single Int a bit easier to do while not compromising performance.
|
||||
|
||||
@ -24,14 +24,6 @@ type Bits bits
|
||||
= Bits Int
|
||||
|
||||
|
||||
type Size
|
||||
= Flag
|
||||
|
||||
|
||||
type Offset
|
||||
= Offset
|
||||
|
||||
|
||||
|
||||
-- flip : Int -> Bool -> Bits bits -> Bits bits
|
||||
-- flip offset on (Bits b) =
|
||||
@ -40,43 +32,6 @@ type Offset
|
||||
-- |> Bits
|
||||
|
||||
|
||||
init : Bits bits
|
||||
init =
|
||||
Bits zeroes
|
||||
|
||||
|
||||
has : Int -> Bits bits -> Bool
|
||||
has offset (Bits b) =
|
||||
let
|
||||
target =
|
||||
zeroes |> store offset 1 ones
|
||||
in
|
||||
Bitwise.and target b - target == 0
|
||||
|
||||
|
||||
on : Int -> Bits bits -> Bits bits
|
||||
on offset (Bits b) =
|
||||
b
|
||||
|> store offset 1 ones
|
||||
|> Bits
|
||||
|
||||
|
||||
off : Int -> Bits bits -> Bits bits
|
||||
off offset (Bits b) =
|
||||
b
|
||||
|> store offset 1 zeroes
|
||||
|> Bits
|
||||
|
||||
|
||||
bool : Bool -> Int
|
||||
bool yes =
|
||||
if yes then
|
||||
ones
|
||||
|
||||
else
|
||||
zeroes
|
||||
|
||||
|
||||
store4Float : Float -> Float -> Float -> Float -> Bits bits
|
||||
store4Float one two three four =
|
||||
Bitwise.and top8 (round one)
|
||||
@ -89,28 +44,6 @@ store4Float one two three four =
|
||||
|> Bits
|
||||
|
||||
|
||||
store4 : Int -> Int -> Int -> Int -> Bits bits
|
||||
store4 one two three four =
|
||||
Bitwise.and top8 one
|
||||
|> Bitwise.or
|
||||
(Bitwise.shiftLeftBy 8 (Bitwise.and top8 two))
|
||||
|> Bitwise.or
|
||||
(Bitwise.shiftLeftBy 16 (Bitwise.and top8 three))
|
||||
|> Bitwise.or
|
||||
(Bitwise.shiftLeftBy 24 (Bitwise.and top8 four))
|
||||
|> Bits
|
||||
|
||||
|
||||
store : Int -> Int -> Int -> Int -> Int
|
||||
store offset length val target =
|
||||
target
|
||||
|
||||
|
||||
get : Int -> Int -> Int -> Int
|
||||
get offset length val =
|
||||
val
|
||||
|
||||
|
||||
{-| -}
|
||||
ones : Int
|
||||
ones =
|
||||
@ -124,21 +57,6 @@ zeroes =
|
||||
Bitwise.or 0 0
|
||||
|
||||
|
||||
top10 : Int
|
||||
top10 =
|
||||
Bitwise.shiftRightZfBy (32 - 10) ones
|
||||
|
||||
|
||||
top8 : Int
|
||||
top8 =
|
||||
Bitwise.shiftRightZfBy (32 - 8) ones
|
||||
|
||||
|
||||
top6 : Int
|
||||
top6 =
|
||||
Bitwise.shiftRightZfBy (32 - 6) ones
|
||||
|
||||
|
||||
top5 : Int
|
||||
top5 =
|
||||
Bitwise.shiftRightZfBy (32 - 5) ones
|
||||
|
@ -1,7 +1,6 @@
|
||||
module InternalAnim.Css exposing
|
||||
( Prop(..)
|
||||
, RenderedProp(..)
|
||||
, cssFromProps
|
||||
, match
|
||||
, propsToRenderedProps
|
||||
, toCss
|
||||
@ -9,21 +8,15 @@ module InternalAnim.Css exposing
|
||||
|
||||
{-| -}
|
||||
|
||||
import Bezier
|
||||
import Color
|
||||
import Html
|
||||
import Html.Attributes as Attr exposing (id)
|
||||
import InternalAnim.Bits as Bits
|
||||
import InternalAnim.Css.Props as Props
|
||||
import InternalAnim.Duration as Duration
|
||||
import InternalAnim.Hash as Hash
|
||||
import InternalAnim.Move as Move
|
||||
import InternalAnim.Quantity as Quantity
|
||||
import InternalAnim.Time as Time
|
||||
import InternalAnim.Timeline as Timeline
|
||||
import InternalAnim.Transition as Transition
|
||||
import InternalAnim.Units as Units
|
||||
import Set exposing (Set)
|
||||
|
||||
|
||||
{-| An id representing a prop type.
|
||||
@ -68,16 +61,6 @@ type Prop
|
||||
| ColorProp String (Move.Move Color.Color)
|
||||
|
||||
|
||||
isTransformProp : Prop -> Bool
|
||||
isTransformProp prop =
|
||||
case prop of
|
||||
Prop id name _ _ ->
|
||||
Props.isTransformId id
|
||||
|
||||
ColorProp _ _ ->
|
||||
False
|
||||
|
||||
|
||||
isGroupProp : Id -> Prop -> Bool
|
||||
isGroupProp groupId prop =
|
||||
case prop of
|
||||
@ -162,21 +145,6 @@ toCss now renderedProps =
|
||||
}
|
||||
|
||||
|
||||
cssFromProps : Timeline.Timeline state -> (state -> List Prop) -> CssAnim
|
||||
cssFromProps timeline lookup =
|
||||
let
|
||||
present =
|
||||
getInitial timeline lookup
|
||||
|
||||
renderedProps =
|
||||
Timeline.foldpAll lookup
|
||||
(\_ -> present)
|
||||
toPropCurves2
|
||||
timeline
|
||||
in
|
||||
props2Css (Timeline.getCurrentTime timeline) renderedProps emptyAnim
|
||||
|
||||
|
||||
getInitial : Timeline.Timeline event -> (event -> List Prop) -> List RenderedProp
|
||||
getInitial timeline lookup =
|
||||
let
|
||||
@ -185,7 +153,7 @@ getInitial timeline lookup =
|
||||
(\props ->
|
||||
toInitialProps props { props = [], translation = Nothing, scale = Nothing }
|
||||
)
|
||||
(\get prev target now startTime endTime future cursor ->
|
||||
(\get _ target _ _ _ _ cursor ->
|
||||
addInitialProps (get (Timeline.getEvent target)) cursor
|
||||
)
|
||||
timeline
|
||||
@ -205,6 +173,7 @@ addMaybeVector maybeVector renderedProps =
|
||||
VectorProp vector :: renderedProps
|
||||
|
||||
|
||||
initState : Float -> Move.State
|
||||
initState x =
|
||||
{ position =
|
||||
Units.pixels x
|
||||
@ -233,12 +202,7 @@ toInitialProps props rendered =
|
||||
[] ->
|
||||
rendered
|
||||
|
||||
(Prop id name movement format) :: remaining ->
|
||||
let
|
||||
state =
|
||||
Move.init
|
||||
(Props.default id)
|
||||
in
|
||||
(Prop id name _ format) :: remaining ->
|
||||
toInitialProps remaining
|
||||
(if Props.isTranslateId id then
|
||||
case rendered.translation of
|
||||
@ -279,6 +243,11 @@ toInitialProps props rendered =
|
||||
rendered
|
||||
|
||||
else
|
||||
let
|
||||
state =
|
||||
Move.init
|
||||
(Props.default id)
|
||||
in
|
||||
{ props =
|
||||
RenderedProp
|
||||
{ id = id
|
||||
@ -313,29 +282,23 @@ matchProp id renderedProp =
|
||||
RenderedProp details ->
|
||||
details.id - id == 0
|
||||
|
||||
RenderedColorProp details ->
|
||||
RenderedColorProp _ ->
|
||||
False
|
||||
|
||||
TransformProp details ->
|
||||
False
|
||||
|
||||
VectorProp details ->
|
||||
VectorProp _ ->
|
||||
False
|
||||
|
||||
|
||||
matchColor : String -> RenderedProp -> Bool
|
||||
matchColor name renderedProp =
|
||||
case renderedProp of
|
||||
RenderedProp details ->
|
||||
RenderedProp _ ->
|
||||
False
|
||||
|
||||
RenderedColorProp details ->
|
||||
details.name == name
|
||||
|
||||
TransformProp details ->
|
||||
False
|
||||
|
||||
VectorProp details ->
|
||||
VectorProp _ ->
|
||||
False
|
||||
|
||||
|
||||
@ -347,7 +310,7 @@ addInitialProps props rendered =
|
||||
[] ->
|
||||
rendered
|
||||
|
||||
(Prop id name movement format) :: remaining ->
|
||||
(Prop id name _ format) :: remaining ->
|
||||
let
|
||||
new =
|
||||
if Props.isTranslateId id then
|
||||
@ -537,36 +500,6 @@ props2Css now renderedProps anim =
|
||||
|> combine anim
|
||||
)
|
||||
|
||||
(TransformProp details) :: remain ->
|
||||
props2Css now
|
||||
remain
|
||||
(case details.sections of
|
||||
[] ->
|
||||
{ anim
|
||||
| hash = transformToHash (stateToTransform details.state) ++ anim.hash
|
||||
, props =
|
||||
( "transform"
|
||||
, renderTransformState details.state
|
||||
)
|
||||
:: anim.props
|
||||
}
|
||||
|
||||
_ ->
|
||||
Move.cssForSections now
|
||||
(stateToTransform details.state)
|
||||
"transform"
|
||||
(\t one two ->
|
||||
"transform: "
|
||||
++ transformToString
|
||||
(Move.lerpTransform t one two)
|
||||
)
|
||||
transformToString
|
||||
transformToHash
|
||||
(List.reverse details.sections)
|
||||
emptyAnim
|
||||
|> combine anim
|
||||
)
|
||||
|
||||
|
||||
vectorToHash : Props.Id -> Vector -> String
|
||||
vectorToHash group vec =
|
||||
@ -594,38 +527,6 @@ vectorToHash group vec =
|
||||
++ Hash.float vec.z
|
||||
|
||||
|
||||
transformToHash : { a | x : Float, y : Float, rotation : Float, scale : Float } -> String
|
||||
transformToHash trans =
|
||||
let
|
||||
scaleStr =
|
||||
if trans.scale == 1 then
|
||||
""
|
||||
|
||||
else
|
||||
"s" ++ String.fromInt (round (trans.scale * 100))
|
||||
|
||||
rotationStr =
|
||||
if trans.rotation == 0 then
|
||||
""
|
||||
|
||||
else
|
||||
"r" ++ String.fromInt (round (trans.rotation * 100))
|
||||
|
||||
translateStr =
|
||||
if trans.x == 0 && trans.y == 0 then
|
||||
""
|
||||
|
||||
else
|
||||
"t"
|
||||
++ String.fromInt (round trans.x)
|
||||
++ "-"
|
||||
++ String.fromInt (round trans.y)
|
||||
in
|
||||
translateStr
|
||||
++ rotationStr
|
||||
++ scaleStr
|
||||
|
||||
|
||||
vectorStateToVector : VectorState -> Vector
|
||||
vectorStateToVector state =
|
||||
{ x =
|
||||
@ -637,99 +538,6 @@ vectorStateToVector state =
|
||||
}
|
||||
|
||||
|
||||
stateToTransform : TransformState -> Transform
|
||||
stateToTransform state =
|
||||
{ x =
|
||||
Units.inPixels state.x.position
|
||||
, y =
|
||||
Units.inPixels state.y.position
|
||||
, scale =
|
||||
Units.inPixels state.scale.position
|
||||
, rotation =
|
||||
Units.inPixels state.rotation.position
|
||||
}
|
||||
|
||||
|
||||
renderTransformState :
|
||||
{ a
|
||||
| x : { b | position : Units.Pixels }
|
||||
, y : { c | position : Units.Pixels }
|
||||
, rotation : { d | position : Units.Pixels }
|
||||
, scale : { e | position : Units.Pixels }
|
||||
}
|
||||
-> String
|
||||
renderTransformState state =
|
||||
"translate("
|
||||
++ pixelsToString state.x.position
|
||||
++ "px, "
|
||||
++ pixelsToString state.y.position
|
||||
++ "px) rotate("
|
||||
++ pixelsToString state.rotation.position
|
||||
++ "turn)"
|
||||
++ " scale("
|
||||
++ pixelsToString state.scale.position
|
||||
++ ")"
|
||||
|
||||
|
||||
pixelsToString : Units.Pixels -> String
|
||||
pixelsToString pixels =
|
||||
floatToString (Units.inPixels pixels)
|
||||
|
||||
|
||||
floatToString : Float -> String
|
||||
floatToString plusMinus =
|
||||
let
|
||||
float =
|
||||
abs plusMinus
|
||||
|
||||
base =
|
||||
floor float
|
||||
|
||||
decimal =
|
||||
floor (100 * (float - toFloat base))
|
||||
|
||||
toString f =
|
||||
if plusMinus < 0 then
|
||||
"-" ++ String.fromInt f
|
||||
|
||||
else
|
||||
String.fromInt f
|
||||
in
|
||||
if decimal == 0 then
|
||||
toString base
|
||||
|
||||
else
|
||||
toString base ++ "." ++ String.fromInt decimal
|
||||
|
||||
|
||||
transformToString trans =
|
||||
"translate("
|
||||
++ floatToString trans.x
|
||||
++ "px, "
|
||||
++ floatToString trans.y
|
||||
++ "px) rotate("
|
||||
++ floatToString trans.rotation
|
||||
++ "turn)"
|
||||
++ " scale("
|
||||
++ floatToString trans.scale
|
||||
++ ")"
|
||||
|
||||
|
||||
infinite : String
|
||||
infinite =
|
||||
"infinite"
|
||||
|
||||
|
||||
isEmptyAnim : { css | keyframes : String } -> Bool
|
||||
isEmptyAnim anim =
|
||||
case anim.keyframes of
|
||||
"" ->
|
||||
True
|
||||
|
||||
_ ->
|
||||
False
|
||||
|
||||
|
||||
emptyAnim : CssAnim
|
||||
emptyAnim =
|
||||
{ hash = ""
|
||||
@ -824,37 +632,6 @@ toPropCurves2 lookup prev target now startTime endTime future cursor =
|
||||
|> lookup
|
||||
|> stateOrDefault rendered.id
|
||||
rendered.name
|
||||
|
||||
finalProp =
|
||||
-- this is the check for being a transition
|
||||
if not (Time.equal (Timeline.endTime prev) startTime) then
|
||||
-- adjust the transition by taking into account
|
||||
-- the intro and exit velocity
|
||||
-- but only if this is an interruption
|
||||
targetProp
|
||||
|> Move.withVelocities
|
||||
(normalizeVelocity
|
||||
startTime
|
||||
targetTime
|
||||
startPosition
|
||||
targetPosition
|
||||
rendered.state.velocity
|
||||
)
|
||||
-- If we do any transition smoothing
|
||||
-- we'll need to normalize this velocity too
|
||||
--Estimation.velocityAtTarget lookupState target future
|
||||
0
|
||||
|
||||
else
|
||||
targetProp
|
||||
|
||||
startPosition =
|
||||
Units.inPixels rendered.state.position
|
||||
|
||||
targetPosition =
|
||||
case targetProp of
|
||||
Move.Pos _ x _ ->
|
||||
x
|
||||
in
|
||||
RenderedProp
|
||||
{ id = rendered.id
|
||||
@ -865,6 +642,39 @@ toPropCurves2 lookup prev target now startTime endTime future cursor =
|
||||
rendered.sections
|
||||
|
||||
else
|
||||
let
|
||||
finalProp =
|
||||
-- this is the check for being a transition
|
||||
if not (Time.equal (Timeline.endTime prev) startTime) then
|
||||
-- adjust the transition by taking into account
|
||||
-- the intro and exit velocity
|
||||
-- but only if this is an interruption
|
||||
let
|
||||
startPosition =
|
||||
Units.inPixels rendered.state.position
|
||||
|
||||
targetPosition =
|
||||
case targetProp of
|
||||
Move.Pos _ x _ ->
|
||||
x
|
||||
in
|
||||
targetProp
|
||||
|> Move.withVelocities
|
||||
(normalizeVelocity
|
||||
startTime
|
||||
targetTime
|
||||
startPosition
|
||||
targetPosition
|
||||
rendered.state.velocity
|
||||
)
|
||||
-- If we do any transition smoothing
|
||||
-- we'll need to normalize this velocity too
|
||||
--Estimation.velocityAtTarget lookupState target future
|
||||
0
|
||||
|
||||
else
|
||||
targetProp
|
||||
in
|
||||
Move.sequences
|
||||
startTime
|
||||
targetTime
|
||||
@ -933,11 +743,6 @@ toPropCurves2 lookup prev target now startTime endTime future cursor =
|
||||
targetProps
|
||||
Transition.standard
|
||||
|
||||
commonSequence =
|
||||
getCommonVectorSequence details.group
|
||||
targetProps
|
||||
[]
|
||||
|
||||
targets =
|
||||
{ x =
|
||||
getVectorSlot details.group Props.X targetProps
|
||||
@ -946,11 +751,6 @@ toPropCurves2 lookup prev target now startTime endTime future cursor =
|
||||
, z =
|
||||
getVectorSlot details.group Props.Z targetProps
|
||||
}
|
||||
|
||||
commonMovement =
|
||||
Move.move commonTransition
|
||||
targets
|
||||
commonSequence
|
||||
in
|
||||
VectorProp
|
||||
{ group = details.group
|
||||
@ -961,6 +761,17 @@ toPropCurves2 lookup prev target now startTime endTime future cursor =
|
||||
details.sections
|
||||
|
||||
else
|
||||
let
|
||||
commonSequence =
|
||||
getCommonVectorSequence details.group
|
||||
targetProps
|
||||
[]
|
||||
|
||||
commonMovement =
|
||||
Move.move commonTransition
|
||||
targets
|
||||
commonSequence
|
||||
in
|
||||
Move.sequences
|
||||
startTime
|
||||
targetTime
|
||||
@ -995,139 +806,6 @@ toPropCurves2 lookup prev target now startTime endTime future cursor =
|
||||
details.state.z
|
||||
}
|
||||
}
|
||||
|
||||
TransformProp details ->
|
||||
-- for each prop
|
||||
-- calculate a new state
|
||||
-- calculate a new transition
|
||||
-- (for now), take the most "different" curve
|
||||
-- Compose a new `Move Transform` with the transition
|
||||
--
|
||||
let
|
||||
targetProps : List Prop
|
||||
targetProps =
|
||||
Timeline.getEvent target
|
||||
|> lookup
|
||||
|> List.filter isTransformProp
|
||||
|
||||
commonTransition =
|
||||
if not (Time.equal (Timeline.endTime prev) startTime) then
|
||||
let
|
||||
fastestVelocity =
|
||||
firstNonZero
|
||||
[ normalizeVelocity
|
||||
startTime
|
||||
targetTime
|
||||
(Units.inPixels details.state.x.position)
|
||||
targets.x
|
||||
details.state.x.velocity
|
||||
, normalizeVelocity
|
||||
startTime
|
||||
targetTime
|
||||
(Units.inPixels details.state.y.position)
|
||||
targets.y
|
||||
details.state.y.velocity
|
||||
, normalizeVelocity
|
||||
startTime
|
||||
targetTime
|
||||
(Units.inPixels details.state.rotation.position)
|
||||
targets.rotation
|
||||
details.state.rotation.velocity
|
||||
, normalizeVelocity
|
||||
startTime
|
||||
targetTime
|
||||
(Units.inPixels details.state.scale.position)
|
||||
targets.scale
|
||||
details.state.scale.velocity
|
||||
]
|
||||
in
|
||||
getCommonTransformTransition
|
||||
targetProps
|
||||
Transition.standard
|
||||
|> Transition.withVelocities fastestVelocity
|
||||
-- If we do any transition smoothing
|
||||
-- we'll need to normalize this velocity too
|
||||
--Estimation.velocityAtTarget lookupState target future
|
||||
0
|
||||
|
||||
else
|
||||
getCommonTransformTransition
|
||||
targetProps
|
||||
Transition.standard
|
||||
|
||||
commonSequence =
|
||||
getCommonTransformSequence
|
||||
targetProps
|
||||
[]
|
||||
|
||||
targets =
|
||||
{ x =
|
||||
transformOrDefault Props.ids.x
|
||||
targetProps
|
||||
, y =
|
||||
transformOrDefault Props.ids.y
|
||||
targetProps
|
||||
, scale =
|
||||
transformOrDefault Props.ids.scale
|
||||
targetProps
|
||||
, rotation =
|
||||
transformOrDefault Props.ids.rotation
|
||||
targetProps
|
||||
}
|
||||
|
||||
commonMovement =
|
||||
Move.move commonTransition
|
||||
targets
|
||||
commonSequence
|
||||
in
|
||||
TransformProp
|
||||
{ sections =
|
||||
if finished then
|
||||
details.sections
|
||||
|
||||
else
|
||||
Move.sequences
|
||||
startTime
|
||||
targetTime
|
||||
now
|
||||
endTime
|
||||
commonMovement
|
||||
details.sections
|
||||
, state =
|
||||
{ x =
|
||||
Move.at progress
|
||||
startTime
|
||||
targetTime
|
||||
(Move.toWith commonTransition
|
||||
targets.x
|
||||
)
|
||||
details.state.x
|
||||
, y =
|
||||
Move.at progress
|
||||
startTime
|
||||
targetTime
|
||||
(Move.toWith commonTransition
|
||||
targets.y
|
||||
)
|
||||
details.state.y
|
||||
, scale =
|
||||
Move.at progress
|
||||
startTime
|
||||
targetTime
|
||||
(Move.toWith commonTransition
|
||||
targets.scale
|
||||
)
|
||||
details.state.scale
|
||||
, rotation =
|
||||
Move.at progress
|
||||
startTime
|
||||
targetTime
|
||||
(Move.toWith commonTransition
|
||||
targets.rotation
|
||||
)
|
||||
details.state.rotation
|
||||
}
|
||||
}
|
||||
)
|
||||
cursor
|
||||
|
||||
@ -1158,7 +836,7 @@ getCommonVectorSequence :
|
||||
-> List (Move.Sequence Vector)
|
||||
getCommonVectorSequence groupId props sequences =
|
||||
case props of
|
||||
(Prop id name (Move.Pos trans v propSeq) format) :: _ ->
|
||||
(Prop _ _ (Move.Pos _ _ propSeq) _) :: _ ->
|
||||
-- sequences
|
||||
vectorSeq groupId props propSeq 0 []
|
||||
|
||||
@ -1210,7 +888,7 @@ gatherVectorSteps groupId seqLevel stepLevel steps props transforms =
|
||||
[] ->
|
||||
transforms
|
||||
|
||||
(Move.Step dur trans target) :: remainingSteps ->
|
||||
(Move.Step dur trans _) :: remainingSteps ->
|
||||
gatherVectorSteps groupId
|
||||
seqLevel
|
||||
(stepLevel + 1)
|
||||
@ -1221,6 +899,7 @@ gatherVectorSteps groupId seqLevel stepLevel steps props transforms =
|
||||
)
|
||||
|
||||
|
||||
getVectorStepAt : Id -> Duration.Duration -> Transition.Transition -> Int -> Int -> List Prop -> Move.Step Vector
|
||||
getVectorStepAt groupId dur trans seqLevel stepLevel props =
|
||||
let
|
||||
x =
|
||||
@ -1262,109 +941,6 @@ getVectorStepAt groupId dur trans seqLevel stepLevel props =
|
||||
{- END VECTOR -}
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
*warning! this need to be called with pre-filtered props that are only transform props!
|
||||
|
||||
-}
|
||||
getCommonTransformSequence :
|
||||
List Prop
|
||||
-> List (Move.Sequence Transform)
|
||||
-> List (Move.Sequence Transform)
|
||||
getCommonTransformSequence props sequences =
|
||||
case props of
|
||||
(Prop id name (Move.Pos trans v propSeq) format) :: _ ->
|
||||
-- sequences
|
||||
transformSeq props propSeq 0 []
|
||||
|
||||
_ ->
|
||||
sequences
|
||||
|
||||
|
||||
transformSeq :
|
||||
List Prop
|
||||
-> List (Move.Sequence Float)
|
||||
-> Int
|
||||
-> List (Move.Sequence Transform)
|
||||
-> List (Move.Sequence Transform)
|
||||
transformSeq props pilotSequence seqLevel renderedTransforms =
|
||||
case pilotSequence of
|
||||
[] ->
|
||||
renderedTransforms
|
||||
|
||||
(Move.Sequence n delay dur steps) :: remain ->
|
||||
transformSeq props
|
||||
remain
|
||||
(seqLevel + 1)
|
||||
(Move.Sequence n
|
||||
delay
|
||||
dur
|
||||
(gatherSequenceSteps seqLevel
|
||||
0
|
||||
steps
|
||||
props
|
||||
[]
|
||||
)
|
||||
:: renderedTransforms
|
||||
)
|
||||
|
||||
|
||||
gatherSequenceSteps :
|
||||
Int
|
||||
-> Int
|
||||
-> List (Move.Step Float)
|
||||
-> List Prop
|
||||
-> List (Move.Step Transform)
|
||||
-> List (Move.Step Transform)
|
||||
gatherSequenceSteps seqLevel stepLevel steps props transforms =
|
||||
case steps of
|
||||
[] ->
|
||||
transforms
|
||||
|
||||
(Move.Step dur trans target) :: remainingSteps ->
|
||||
gatherSequenceSteps seqLevel
|
||||
(stepLevel + 1)
|
||||
remainingSteps
|
||||
props
|
||||
(getTransformStepAt dur trans seqLevel stepLevel props
|
||||
:: transforms
|
||||
)
|
||||
|
||||
|
||||
getTransformStepAt dur trans seqLevel stepLevel props =
|
||||
Move.Step dur
|
||||
trans
|
||||
{ x =
|
||||
getTransformSequenceValueAt seqLevel
|
||||
stepLevel
|
||||
Nothing
|
||||
Props.ids.x
|
||||
props
|
||||
Nothing
|
||||
, y =
|
||||
getTransformSequenceValueAt seqLevel
|
||||
stepLevel
|
||||
Nothing
|
||||
Props.ids.y
|
||||
props
|
||||
Nothing
|
||||
, scale =
|
||||
getTransformSequenceValueAt seqLevel
|
||||
stepLevel
|
||||
Nothing
|
||||
Props.ids.scale
|
||||
props
|
||||
Nothing
|
||||
, rotation =
|
||||
getTransformSequenceValueAt seqLevel
|
||||
stepLevel
|
||||
Nothing
|
||||
Props.ids.rotation
|
||||
props
|
||||
Nothing
|
||||
}
|
||||
|
||||
|
||||
getTransformSequenceValueAt : Int -> Int -> Maybe Props.Id -> Props.Id -> List Prop -> Maybe Float -> Float
|
||||
getTransformSequenceValueAt seqLevel stepLevel maybeDefaultId targetId props defaultValue =
|
||||
case props of
|
||||
@ -1376,7 +952,7 @@ getTransformSequenceValueAt seqLevel stepLevel maybeDefaultId targetId props def
|
||||
Just default ->
|
||||
default
|
||||
|
||||
(Prop id name move _) :: remain ->
|
||||
(Prop id _ move _) :: remain ->
|
||||
if id - targetId == 0 then
|
||||
case move of
|
||||
Move.Pos _ v seq ->
|
||||
@ -1420,7 +996,7 @@ getTransformSequenceValueAt seqLevel stepLevel maybeDefaultId targetId props def
|
||||
else
|
||||
getTransformSequenceValueAt seqLevel stepLevel maybeDefaultId targetId remain defaultValue
|
||||
|
||||
(ColorProp name movement) :: remain ->
|
||||
(ColorProp _ _) :: remain ->
|
||||
getTransformSequenceValueAt seqLevel stepLevel maybeDefaultId targetId remain defaultValue
|
||||
|
||||
|
||||
@ -1447,7 +1023,7 @@ getCommonTransformTransition props currentTrans =
|
||||
[] ->
|
||||
currentTrans
|
||||
|
||||
(Prop id _ (Move.Pos trans _ _) _) :: remain ->
|
||||
(Prop _ _ (Move.Pos trans _ _) _) :: remain ->
|
||||
if Transition.isStandard trans then
|
||||
getCommonTransformTransition remain currentTrans
|
||||
|
||||
@ -1478,7 +1054,7 @@ valueOrDefault maybeDefaultid targetId props defaultVal =
|
||||
Just val ->
|
||||
val
|
||||
|
||||
(Prop id name move _) :: remain ->
|
||||
(Prop id _ move _) :: remain ->
|
||||
if id - targetId == 0 then
|
||||
case move of
|
||||
Move.Pos _ v _ ->
|
||||
@ -1498,30 +1074,10 @@ valueOrDefault maybeDefaultid targetId props defaultVal =
|
||||
else
|
||||
valueOrDefault maybeDefaultid targetId remain defaultVal
|
||||
|
||||
(ColorProp name movement) :: remain ->
|
||||
(ColorProp _ _) :: remain ->
|
||||
valueOrDefault maybeDefaultid targetId remain defaultVal
|
||||
|
||||
|
||||
{-| -}
|
||||
transformOrDefault : Id -> List Prop -> Float
|
||||
transformOrDefault targetId props =
|
||||
case props of
|
||||
[] ->
|
||||
Props.defaultPosition targetId
|
||||
|
||||
(Prop id name move _) :: remain ->
|
||||
if id - targetId == 0 then
|
||||
case move of
|
||||
Move.Pos _ v _ ->
|
||||
v
|
||||
|
||||
else
|
||||
transformOrDefault targetId remain
|
||||
|
||||
(ColorProp name movement) :: remain ->
|
||||
transformOrDefault targetId remain
|
||||
|
||||
|
||||
{-| -}
|
||||
stateOrDefault : Id -> String -> List Prop -> Move.Move Float
|
||||
stateOrDefault targetId targetName props =
|
||||
@ -1543,7 +1099,7 @@ stateOrDefault targetId targetName props =
|
||||
else
|
||||
stateOrDefault targetId targetName remain
|
||||
|
||||
(ColorProp name movement) :: remain ->
|
||||
(ColorProp _ _) :: remain ->
|
||||
stateOrDefault targetId targetName remain
|
||||
|
||||
|
||||
@ -1554,7 +1110,7 @@ colorOrDefault targetName default props =
|
||||
[] ->
|
||||
default
|
||||
|
||||
(Prop id _ move _) :: remain ->
|
||||
(Prop _ _ _ _) :: remain ->
|
||||
colorOrDefault targetName default remain
|
||||
|
||||
(ColorProp name (Move.Pos _ clr _)) :: remain ->
|
||||
@ -1565,30 +1121,6 @@ colorOrDefault targetName default props =
|
||||
colorOrDefault targetName default remain
|
||||
|
||||
|
||||
matchForMovement : Id -> String -> List Prop -> Maybe (Move.Move Float)
|
||||
matchForMovement onlyId onlyName props =
|
||||
case props of
|
||||
[] ->
|
||||
Nothing
|
||||
|
||||
(ColorProp name move) :: remain ->
|
||||
matchForMovement onlyId onlyName remain
|
||||
|
||||
((Prop id name movement _) as top) :: remain ->
|
||||
if id + 1 == 0 then
|
||||
if name == onlyName then
|
||||
Just movement
|
||||
|
||||
else
|
||||
matchForMovement onlyId onlyName remain
|
||||
|
||||
else if id - onlyId == 0 then
|
||||
Just movement
|
||||
|
||||
else
|
||||
matchForMovement onlyId onlyName remain
|
||||
|
||||
|
||||
{-| A group of curves represents the trail of one scalar property
|
||||
|
||||
(Scalar property meaning something like opacity, or just the `R` channel of rgb.)
|
||||
@ -1597,7 +1129,6 @@ matchForMovement onlyId onlyName props =
|
||||
type RenderedProp
|
||||
= RenderedProp RenderedPropDetails
|
||||
| RenderedColorProp RenderedColorPropDetails
|
||||
| TransformProp TransformPropDetails
|
||||
-- transform can now be deconstructed into its parts
|
||||
-- This is for translation and scaling
|
||||
-- Rotation is a RenderedProp
|
||||
@ -1645,33 +1176,6 @@ type alias RenderedColorPropDetails =
|
||||
}
|
||||
|
||||
|
||||
type alias TransformPropDetails =
|
||||
{ sections :
|
||||
List (Move.Sequence Transform)
|
||||
, state : TransformState
|
||||
}
|
||||
|
||||
|
||||
type alias TransformState =
|
||||
{ x : Move.State
|
||||
, y : Move.State
|
||||
, scale : Move.State
|
||||
, rotation : Move.State
|
||||
}
|
||||
|
||||
|
||||
type alias Transform =
|
||||
{ x : Float
|
||||
, y : Float
|
||||
, scale : Float
|
||||
, rotation : Float
|
||||
}
|
||||
|
||||
|
||||
type alias TransformPresence =
|
||||
Bits.Bits Transform
|
||||
|
||||
|
||||
{-| Slightly different than CssAnim in that we can also have style properties
|
||||
This is for when a property has not changed and so a full animation is not necessary
|
||||
-}
|
||||
|
@ -1,22 +1,19 @@
|
||||
module InternalAnim.Css.Props exposing
|
||||
( Id, ids, hash, default, defaultPosition, groups
|
||||
, isTransformId, isTranslateId, isRotateId, isScaleId
|
||||
, isTranslateId, isScaleId
|
||||
, Format, format, float, int, px, turns
|
||||
, roundFloat, floatToString
|
||||
, isGroup
|
||||
, VectorSlot(..), colorHash, groupToCompoundId, name, noId, toStr, translateX, transparent, vectorSlotToId, vectorToString, zero
|
||||
, VectorSlot(..), colorHash, groupToCompoundId, noId, transparent, vectorSlotToId, vectorToString
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs Id, ids, hash, default, defaultPosition, groups
|
||||
|
||||
@docs isTransformId, isTranslateId, isRotateId, isScaleId
|
||||
@docs isTranslateId, isScaleId
|
||||
|
||||
@docs Format, format, float, int, px, turns
|
||||
|
||||
@docs roundFloat, floatToString
|
||||
|
||||
@docs translateToString, isGroup
|
||||
|
||||
-}
|
||||
@ -139,12 +136,9 @@ hashFormat form num =
|
||||
Px ->
|
||||
String.fromInt (round num) ++ "px"
|
||||
|
||||
Turns vec ->
|
||||
Turns _ ->
|
||||
String.fromInt (round num)
|
||||
|
||||
TranslateX ->
|
||||
"translateX(" ++ String.fromInt (round num) ++ "px)"
|
||||
|
||||
|
||||
format : Format -> Float -> String
|
||||
format form num =
|
||||
@ -162,15 +156,11 @@ format form num =
|
||||
-- Number here is 1/1000 of a turn
|
||||
vectorToCssString vec ++ " " ++ String.fromFloat (num / 1000) ++ "turn"
|
||||
|
||||
TranslateX ->
|
||||
"translateX(" ++ String.fromInt (round num) ++ "px)"
|
||||
|
||||
|
||||
type Format
|
||||
= AsFloat
|
||||
| AsInt
|
||||
| Px
|
||||
| TranslateX
|
||||
| Turns Vector
|
||||
|
||||
|
||||
@ -194,11 +184,6 @@ px =
|
||||
Px
|
||||
|
||||
|
||||
translateX : Format
|
||||
translateX =
|
||||
TranslateX
|
||||
|
||||
|
||||
{-| We make this huge because we want it last.
|
||||
|
||||
The order of the ids matters, as it's the order that they're rendered in.
|
||||
@ -249,12 +234,24 @@ isGroup groupId id =
|
||||
False
|
||||
|
||||
|
||||
groups : { scaling : Id, translation : Id }
|
||||
groups =
|
||||
{ scaling = 20
|
||||
, translation = 10
|
||||
}
|
||||
|
||||
|
||||
ids :
|
||||
{ x : Id
|
||||
, y : Id
|
||||
, z : Id
|
||||
, rotation : Id
|
||||
, scale : Id
|
||||
, scaleX : Id
|
||||
, scaleY : Id
|
||||
, scaleZ : Id
|
||||
, opacity : Id
|
||||
}
|
||||
ids =
|
||||
{ x = 0
|
||||
, y = 1
|
||||
@ -268,30 +265,15 @@ ids =
|
||||
}
|
||||
|
||||
|
||||
firstTransform : Id
|
||||
firstTransform =
|
||||
ids.x
|
||||
|
||||
|
||||
type alias Id =
|
||||
Int
|
||||
|
||||
|
||||
isTransformId : Id -> Bool
|
||||
isTransformId id =
|
||||
id < 12
|
||||
|
||||
|
||||
isTranslateId : Id -> Bool
|
||||
isTranslateId id =
|
||||
id < 3
|
||||
|
||||
|
||||
isRotateId : Id -> Bool
|
||||
isRotateId id =
|
||||
id == 3
|
||||
|
||||
|
||||
isScaleId : Id -> Bool
|
||||
isScaleId id =
|
||||
id == 4 || id == 5 || id == 6 || id == 7
|
||||
@ -382,60 +364,6 @@ hashId id =
|
||||
"unknown"
|
||||
|
||||
|
||||
name : Id -> String
|
||||
name id =
|
||||
case id of
|
||||
13 ->
|
||||
"opacity"
|
||||
|
||||
14 ->
|
||||
"background-color"
|
||||
|
||||
_ ->
|
||||
"unknown"
|
||||
|
||||
|
||||
toStr : Id -> (Float -> String)
|
||||
toStr id =
|
||||
case id of
|
||||
0 ->
|
||||
\f ->
|
||||
"translateX(" ++ String.fromFloat f ++ "px)"
|
||||
|
||||
1 ->
|
||||
\f ->
|
||||
"translateY(" ++ String.fromFloat f ++ "px)"
|
||||
|
||||
2 ->
|
||||
\f ->
|
||||
"translateZ(" ++ String.fromFloat f ++ "px)"
|
||||
|
||||
3 ->
|
||||
\f ->
|
||||
"rotate(" ++ String.fromFloat f ++ "rad)"
|
||||
|
||||
4 ->
|
||||
\f ->
|
||||
"scale(" ++ String.fromFloat f ++ ")"
|
||||
|
||||
5 ->
|
||||
\f ->
|
||||
"scaleX(" ++ String.fromFloat f ++ ")"
|
||||
|
||||
6 ->
|
||||
\f ->
|
||||
"scaleY(" ++ String.fromFloat f ++ ")"
|
||||
|
||||
13 ->
|
||||
-- opacity
|
||||
\f ->
|
||||
String.fromFloat f
|
||||
|
||||
_ ->
|
||||
\f ->
|
||||
String.fromFloat f
|
||||
|
||||
|
||||
defaultPosition : Id -> Float
|
||||
defaultPosition id =
|
||||
case id of
|
||||
|
@ -1,130 +0,0 @@
|
||||
module InternalAnim.Estimation exposing (velocity, velocityAtTarget)
|
||||
|
||||
{-| -}
|
||||
|
||||
import Animator
|
||||
import Animator.Timeline
|
||||
import Animator.Value
|
||||
import InternalAnim.Duration as Duration
|
||||
import InternalAnim.Move as Move
|
||||
import InternalAnim.Quantity as Quantity
|
||||
import InternalAnim.Time as Time
|
||||
import InternalAnim.Timeline as Timeline
|
||||
import InternalAnim.Units as Units exposing (Pixels, PixelsPerSecond)
|
||||
import Time
|
||||
|
||||
|
||||
mapTime fn time =
|
||||
Time.millisToPosix (fn (Time.posixToMillis time))
|
||||
|
||||
|
||||
{-| Estimate velocity in pixels/second
|
||||
-}
|
||||
velocity : Int -> Time.Posix -> Animator.Timeline.Timeline event -> (event -> Animator.Value.Movement) -> Float
|
||||
velocity resolution time timeline toPosition =
|
||||
let
|
||||
before =
|
||||
mapTime (\t -> t - resolution) time
|
||||
|
||||
after =
|
||||
mapTime (\t -> t + resolution) time
|
||||
|
||||
zero =
|
||||
Animator.Value.movement (Timeline.atTime before timeline) toPosition
|
||||
|
||||
one =
|
||||
Animator.Value.movement (Timeline.atTime time timeline) toPosition
|
||||
|
||||
two =
|
||||
Animator.Value.movement (Timeline.atTime after timeline) toPosition
|
||||
|
||||
first =
|
||||
(one.position - zero.position) / toFloat resolution
|
||||
|
||||
second =
|
||||
(two.position - one.position) / toFloat resolution
|
||||
|
||||
expected =
|
||||
-- 1000 * avg first second
|
||||
1000 * (two.position - zero.position) / (2 * toFloat resolution)
|
||||
in
|
||||
expected
|
||||
|
||||
|
||||
{-| -}
|
||||
velocityAtTarget :
|
||||
(state -> Move.Move Float)
|
||||
-> Timeline.Occurring state
|
||||
-> List (Timeline.Occurring state)
|
||||
-> PixelsPerSecond
|
||||
velocityAtTarget lookup target future =
|
||||
let
|
||||
movement =
|
||||
lookup (Timeline.getEvent target)
|
||||
in
|
||||
case future of
|
||||
[] ->
|
||||
case movement of
|
||||
Move.Pos _ _ [] ->
|
||||
zeroVelocity
|
||||
|
||||
Move.Pos _ _ (seq :: _) ->
|
||||
Move.initialSequenceVelocity seq
|
||||
|
||||
next :: _ ->
|
||||
let
|
||||
targetPosition =
|
||||
case movement of
|
||||
Move.Pos _ x _ ->
|
||||
Units.pixels x
|
||||
in
|
||||
case lookup (Timeline.getEvent next) of
|
||||
Move.Pos _ aheadPosition [] ->
|
||||
-- our target velocity is the linear velocity between target and lookahead
|
||||
velocityBetween
|
||||
targetPosition
|
||||
(Timeline.endTime target)
|
||||
(Units.pixels aheadPosition)
|
||||
(Timeline.startTime next)
|
||||
|
||||
Move.Pos _ aheadPosition (seq :: _) ->
|
||||
if Timeline.isResting target then
|
||||
Move.initialSequenceVelocity seq
|
||||
|
||||
else
|
||||
velocityBetween
|
||||
targetPosition
|
||||
(Timeline.endTime target)
|
||||
(Units.pixels aheadPosition)
|
||||
(Timeline.startTime next)
|
||||
|
||||
|
||||
velocityBetween :
|
||||
Pixels
|
||||
-> Time.Absolute
|
||||
-> Pixels
|
||||
-> Time.Absolute
|
||||
-> PixelsPerSecond
|
||||
velocityBetween one oneTime two twoTime =
|
||||
let
|
||||
distance =
|
||||
two
|
||||
|> Quantity.minus one
|
||||
|
||||
duration =
|
||||
Time.duration oneTime twoTime
|
||||
|
||||
vel =
|
||||
Units.inPixels distance
|
||||
/ Duration.inSeconds duration
|
||||
in
|
||||
if isNaN vel || isInfinite vel then
|
||||
Quantity.zero
|
||||
|
||||
else
|
||||
Units.pixelsPerSecond vel
|
||||
|
||||
|
||||
zeroVelocity : PixelsPerSecond
|
||||
zeroVelocity =
|
||||
Units.pixelsPerSecond 0
|
@ -1,15 +1,14 @@
|
||||
module InternalAnim.Move exposing
|
||||
( Move(..), to, toWith
|
||||
, State, init
|
||||
, lerpColor, lerpFloat, lerpTransform, lerpVector
|
||||
, lerpColor, lerpFloat, lerpVector
|
||||
, Sequence(..)
|
||||
, Step(..), step, stepWith, set
|
||||
, Step(..), stepWith
|
||||
, sequences
|
||||
, addSequence, cssForSections
|
||||
, withTransition, withVelocities
|
||||
, at, atX, transitionTo
|
||||
, denormalize, normalizeOver, toReal
|
||||
, floatToString, initialSequenceVelocity, move
|
||||
, at, transitionTo
|
||||
, move
|
||||
)
|
||||
|
||||
{-|
|
||||
@ -18,10 +17,10 @@ module InternalAnim.Move exposing
|
||||
|
||||
@docs State, init
|
||||
|
||||
@docs lerpColor, lerpFloat, lerpTransform, lerpVector
|
||||
@docs lerpColor, lerpFloat, lerpVector
|
||||
|
||||
@docs Sequence
|
||||
@docs Step, step, stepWith, set
|
||||
@docs Step, stepWith
|
||||
|
||||
@docs sequences, goto
|
||||
|
||||
@ -29,9 +28,7 @@ module InternalAnim.Move exposing
|
||||
|
||||
@docs withTransition, withVelocities
|
||||
|
||||
@docs at, atX, transitionTo
|
||||
|
||||
@docs denormalize, normalizeOver, toReal
|
||||
@docs at, transitionTo
|
||||
|
||||
-}
|
||||
|
||||
@ -121,6 +118,7 @@ toWith t v =
|
||||
Pos t v []
|
||||
|
||||
|
||||
move : Transition.Transition -> value -> List (Sequence value) -> Move value
|
||||
move =
|
||||
Pos
|
||||
|
||||
@ -140,27 +138,12 @@ withSequenceDelay delay (Sequence i _ dur steps) =
|
||||
Sequence i delay dur steps
|
||||
|
||||
|
||||
set : value -> Step value
|
||||
set =
|
||||
Step zeroDuration Transition.standard
|
||||
|
||||
|
||||
step : Duration.Duration -> value -> Step value
|
||||
step dur value =
|
||||
Step dur Transition.standard value
|
||||
|
||||
|
||||
stepWith : Duration.Duration -> Transition.Transition -> value -> Step value
|
||||
stepWith =
|
||||
Step
|
||||
|
||||
|
||||
{--}
|
||||
zeroVelocity : Units.PixelsPerSecond
|
||||
zeroVelocity =
|
||||
Units.pixelsPerSecond 0
|
||||
|
||||
|
||||
type alias State =
|
||||
{ position : Units.Pixels
|
||||
, velocity : Units.PixelsPerSecond
|
||||
@ -185,14 +168,6 @@ addDelayToSequence delay seqs captured =
|
||||
)
|
||||
|
||||
|
||||
type alias Transform =
|
||||
{ x : Float
|
||||
, y : Float
|
||||
, scale : Float
|
||||
, rotation : Float
|
||||
}
|
||||
|
||||
|
||||
lerpFloat : Float -> Float -> Float -> Float
|
||||
lerpFloat t one two =
|
||||
one + ((two - one) * t)
|
||||
@ -237,30 +212,6 @@ lerpVector t one two =
|
||||
}
|
||||
|
||||
|
||||
lerpTransform : Float -> Transform -> Transform -> Transform
|
||||
lerpTransform t one two =
|
||||
{ x =
|
||||
lerpFloat t one.x two.x
|
||||
, y =
|
||||
lerpFloat t one.y two.y
|
||||
, scale =
|
||||
lerpFloat t one.scale two.scale
|
||||
, rotation =
|
||||
lerpFloat t one.rotation two.rotation
|
||||
}
|
||||
|
||||
|
||||
atX :
|
||||
Float
|
||||
-> Move Float
|
||||
->
|
||||
{ position : Bezier.Point
|
||||
, velocity : Bezier.Point
|
||||
}
|
||||
atX progress (Pos trans value dwell) =
|
||||
Transition.atX2 progress trans
|
||||
|
||||
|
||||
at :
|
||||
Float
|
||||
-> Time.Absolute
|
||||
@ -273,7 +224,7 @@ at progress startTime targetTime (Pos transition targetPosition dwell) startingS
|
||||
startPosition =
|
||||
Units.inPixels startingState.position
|
||||
in
|
||||
Transition.atX2 progress transition
|
||||
Transition.atX progress transition
|
||||
|> denormalize startTime
|
||||
targetTime
|
||||
startPosition
|
||||
@ -313,7 +264,7 @@ transitionTo progress startTime targetTime (Pos trans targetPosition dwell) star
|
||||
else
|
||||
Transition.withVelocities introVelocity 0 trans
|
||||
in
|
||||
Transition.atX2 progress transition
|
||||
Transition.atX progress transition
|
||||
|> denormalize startTime
|
||||
targetTime
|
||||
startPosition
|
||||
@ -389,26 +340,6 @@ scaleXYBy { x, y } point =
|
||||
{ x = point.x * x, y = point.y * y }
|
||||
|
||||
|
||||
{-|
|
||||
|
||||
Map a value to 0:1 given a range it should be in.
|
||||
|
||||
-}
|
||||
normalizeOver : Float -> Float -> Float -> Float
|
||||
normalizeOver start end current =
|
||||
let
|
||||
total =
|
||||
abs (end - start)
|
||||
in
|
||||
if total == 0 then
|
||||
0
|
||||
|
||||
else
|
||||
((current - start) / total)
|
||||
|> max 0
|
||||
|> min 1
|
||||
|
||||
|
||||
{-| The opposite of `normalizeOver`.
|
||||
|
||||
I guess this is denormalization? Though i was always confused by that term :/
|
||||
@ -481,162 +412,160 @@ sequences :
|
||||
-> List (Sequence value)
|
||||
-> List (Sequence value)
|
||||
sequences startTime targetTime now stopTime movement existingSequence =
|
||||
let
|
||||
durationToNow =
|
||||
Time.duration startTime now
|
||||
in
|
||||
if Time.equal now stopTime && not (Time.equal targetTime stopTime) then
|
||||
-- We've probably been interrupted
|
||||
[]
|
||||
|
||||
else if Time.thisAfterOrEqualThat startTime now then
|
||||
-- We've definitely started, so we want to report the full sequence
|
||||
-- most common case will be startTime == now
|
||||
case movement of
|
||||
Pos trans value [] ->
|
||||
let
|
||||
transitionDuration =
|
||||
Time.duration startTime targetTime
|
||||
|
||||
seq =
|
||||
Sequence 1
|
||||
durationToNow
|
||||
transitionDuration
|
||||
[ Step transitionDuration trans value
|
||||
]
|
||||
in
|
||||
push seq existingSequence
|
||||
|
||||
Pos trans value [ Sequence 1 delay dur steps ] ->
|
||||
let
|
||||
stepDuration =
|
||||
Time.duration startTime targetTime
|
||||
|
||||
transitionDuration =
|
||||
stepDuration
|
||||
|> Time.expand dur
|
||||
|
||||
transitionSequence =
|
||||
Sequence 1
|
||||
(Time.expand durationToNow delay)
|
||||
transitionDuration
|
||||
(Step stepDuration trans value
|
||||
:: steps
|
||||
)
|
||||
in
|
||||
push transitionSequence
|
||||
existingSequence
|
||||
|
||||
Pos trans value dwell ->
|
||||
let
|
||||
transitionDuration =
|
||||
Time.duration startTime targetTime
|
||||
|
||||
transitionSequence =
|
||||
Sequence 1
|
||||
durationToNow
|
||||
transitionDuration
|
||||
[ Step transitionDuration trans value
|
||||
]
|
||||
in
|
||||
if Time.isZeroDuration transitionDuration && not (List.isEmpty dwell) then
|
||||
existingSequence
|
||||
|> append (addDelayToSequence durationToNow dwell [])
|
||||
|
||||
else
|
||||
existingSequence
|
||||
|> push transitionSequence
|
||||
|> append (addDelayToSequence (Time.expand durationToNow transitionDuration) dwell [])
|
||||
|
||||
else if after startTime stopTime now movement then
|
||||
-- we've completely passed this state, no splines are returned
|
||||
[]
|
||||
|
||||
else
|
||||
-- now is during the new sequence
|
||||
-- so let's compose the new sequence and then split it at the new time
|
||||
-- we also know that existingSequence should be [] here
|
||||
case movement of
|
||||
Pos trans value [] ->
|
||||
let
|
||||
splitTime =
|
||||
Time.progress startTime targetTime now
|
||||
|
||||
transitionDuration =
|
||||
Time.duration startTime targetTime
|
||||
|
||||
newSequence =
|
||||
Sequence 1 Quantity.zero transitionDuration [ Step transitionDuration trans value ]
|
||||
|> takeAfter durationToNow
|
||||
in
|
||||
case newSequence.following of
|
||||
Nothing ->
|
||||
[ newSequence.base ]
|
||||
|
||||
Just following ->
|
||||
[ newSequence.base ]
|
||||
|> push
|
||||
(following
|
||||
|> withSequenceDelay (getSequenceDuration newSequence.base)
|
||||
)
|
||||
|
||||
Pos trans value [ Sequence 1 delay dur steps ] ->
|
||||
let
|
||||
stepDuration =
|
||||
Time.duration startTime targetTime
|
||||
|
||||
transitionDuration =
|
||||
stepDuration
|
||||
|> Time.expand dur
|
||||
|
||||
new =
|
||||
Sequence 1 Quantity.zero transitionDuration (Step stepDuration trans value :: steps)
|
||||
|> takeAfter durationToNow
|
||||
in
|
||||
case new.following of
|
||||
Nothing ->
|
||||
[ new.base
|
||||
]
|
||||
|
||||
Just following ->
|
||||
[ new.base ]
|
||||
|> push
|
||||
(following |> withSequenceDelay (getSequenceDuration new.base))
|
||||
|
||||
Pos trans value dwell ->
|
||||
let
|
||||
transitionDuration =
|
||||
Time.duration startTime targetTime
|
||||
in
|
||||
if Time.thisAfterThat now targetTime then
|
||||
takeAfterSequenceList durationToNow dwell
|
||||
|
||||
else
|
||||
let
|
||||
durationToNow =
|
||||
Time.duration startTime now
|
||||
in
|
||||
if Time.thisAfterOrEqualThat startTime now then
|
||||
-- We've definitely started, so we want to report the full sequence
|
||||
-- most common case will be startTime == now
|
||||
case movement of
|
||||
Pos trans value [] ->
|
||||
let
|
||||
new =
|
||||
transitionDuration =
|
||||
Time.duration startTime targetTime
|
||||
|
||||
seq =
|
||||
Sequence 1
|
||||
durationToNow
|
||||
transitionDuration
|
||||
[ Step transitionDuration trans value
|
||||
]
|
||||
in
|
||||
push seq existingSequence
|
||||
|
||||
Pos trans value [ Sequence 1 delay dur steps ] ->
|
||||
let
|
||||
stepDuration =
|
||||
Time.duration startTime targetTime
|
||||
|
||||
transitionDuration =
|
||||
stepDuration
|
||||
|> Time.expand dur
|
||||
|
||||
transitionSequence =
|
||||
Sequence 1
|
||||
(Time.expand durationToNow delay)
|
||||
transitionDuration
|
||||
(Step stepDuration trans value
|
||||
:: steps
|
||||
)
|
||||
in
|
||||
push transitionSequence
|
||||
existingSequence
|
||||
|
||||
Pos trans value dwell ->
|
||||
let
|
||||
transitionDuration =
|
||||
Time.duration startTime targetTime
|
||||
in
|
||||
if Time.isZeroDuration transitionDuration && not (List.isEmpty dwell) then
|
||||
existingSequence
|
||||
|> append (addDelayToSequence durationToNow dwell [])
|
||||
|
||||
else
|
||||
let
|
||||
transitionSequence =
|
||||
Sequence 1
|
||||
durationToNow
|
||||
transitionDuration
|
||||
[ Step transitionDuration trans value
|
||||
]
|
||||
in
|
||||
existingSequence
|
||||
|> push transitionSequence
|
||||
|> append (addDelayToSequence (Time.expand durationToNow transitionDuration) dwell [])
|
||||
|
||||
else if after startTime stopTime now movement then
|
||||
-- we've completely passed this state, no splines are returned
|
||||
[]
|
||||
|
||||
else
|
||||
-- now is during the new sequence
|
||||
-- so let's compose the new sequence and then split it at the new time
|
||||
-- we also know that existingSequence should be [] here
|
||||
case movement of
|
||||
Pos trans value [] ->
|
||||
let
|
||||
transitionDuration =
|
||||
Time.duration startTime targetTime
|
||||
|
||||
newSequence =
|
||||
Sequence 1 Quantity.zero transitionDuration [ Step transitionDuration trans value ]
|
||||
|> takeAfter durationToNow
|
||||
in
|
||||
case newSequence.following of
|
||||
Nothing ->
|
||||
[ newSequence.base ]
|
||||
|
||||
Just following ->
|
||||
[ newSequence.base ]
|
||||
|> push
|
||||
(following
|
||||
|> withSequenceDelay (getSequenceDuration newSequence.base)
|
||||
)
|
||||
|
||||
Pos trans value [ Sequence 1 _ dur steps ] ->
|
||||
let
|
||||
stepDuration =
|
||||
Time.duration startTime targetTime
|
||||
|
||||
transitionDuration =
|
||||
stepDuration
|
||||
|> Time.expand dur
|
||||
|
||||
new =
|
||||
Sequence 1 Quantity.zero transitionDuration (Step stepDuration trans value :: steps)
|
||||
|> takeAfter durationToNow
|
||||
in
|
||||
case new.following of
|
||||
Nothing ->
|
||||
new.base
|
||||
:: addDelayToSequence (getSequenceDuration new.base) dwell []
|
||||
[ new.base
|
||||
]
|
||||
|
||||
Just following ->
|
||||
let
|
||||
delayToFollowing =
|
||||
getSequenceDuration new.base
|
||||
in
|
||||
[ new.base ]
|
||||
|> push (following |> withSequenceDelay delayToFollowing)
|
||||
|> append
|
||||
(addDelayToSequence
|
||||
(Time.expand (getSequenceDuration following)
|
||||
delayToFollowing
|
||||
|> push
|
||||
(following |> withSequenceDelay (getSequenceDuration new.base))
|
||||
|
||||
Pos trans value dwell ->
|
||||
if Time.thisAfterThat now targetTime then
|
||||
takeAfterSequenceList durationToNow dwell
|
||||
|
||||
else
|
||||
let
|
||||
transitionDuration =
|
||||
Time.duration startTime targetTime
|
||||
|
||||
new =
|
||||
Sequence 1 Quantity.zero transitionDuration [ Step transitionDuration trans value ]
|
||||
|> takeAfter durationToNow
|
||||
in
|
||||
case new.following of
|
||||
Nothing ->
|
||||
new.base
|
||||
:: addDelayToSequence (getSequenceDuration new.base) dwell []
|
||||
|
||||
Just following ->
|
||||
let
|
||||
delayToFollowing =
|
||||
getSequenceDuration new.base
|
||||
in
|
||||
[ new.base ]
|
||||
|> push (following |> withSequenceDelay delayToFollowing)
|
||||
|> append
|
||||
(addDelayToSequence
|
||||
(Time.expand (getSequenceDuration following)
|
||||
delayToFollowing
|
||||
)
|
||||
dwell
|
||||
[]
|
||||
)
|
||||
dwell
|
||||
[]
|
||||
)
|
||||
|
||||
|
||||
takeAfterSequenceList :
|
||||
@ -648,7 +577,7 @@ takeAfterSequenceList durationToNow seqs =
|
||||
[] ->
|
||||
[]
|
||||
|
||||
((Sequence n delay duration steps) as top) :: remain ->
|
||||
((Sequence n _ duration _) as top) :: remain ->
|
||||
let
|
||||
floatN =
|
||||
toFloat n
|
||||
@ -824,7 +753,7 @@ afterSequenceList durationTillNow seq remaining =
|
||||
[] ->
|
||||
True
|
||||
|
||||
((Sequence n delay duration steps) as top) :: rest ->
|
||||
((Sequence n _ duration _) as top) :: rest ->
|
||||
let
|
||||
durationOfUnrolledSeq =
|
||||
duration |> Quantity.multiplyBy (toFloat n)
|
||||
@ -838,6 +767,7 @@ afterSequenceList durationTillNow seq remaining =
|
||||
False
|
||||
|
||||
|
||||
afterSequence : Duration.Duration -> Sequence value -> Bool
|
||||
afterSequence durationTillNow (Sequence n delay duration steps) =
|
||||
let
|
||||
floatN =
|
||||
@ -863,21 +793,6 @@ zeroDuration =
|
||||
{- CSS KEYFRAMES -}
|
||||
|
||||
|
||||
initialSequenceVelocity : Sequence value -> Units.PixelsPerSecond
|
||||
initialSequenceVelocity seq =
|
||||
case seq of
|
||||
Sequence 0 _ _ _ ->
|
||||
zeroVelocity
|
||||
|
||||
Sequence _ _ _ [] ->
|
||||
zeroVelocity
|
||||
|
||||
Sequence n _ _ ((Step dur trans _) :: _) ->
|
||||
Transition.initialVelocity trans
|
||||
|> (*) 1000
|
||||
|> Units.pixelsPerSecond
|
||||
|
||||
|
||||
hash : Time.Absolute -> String -> Sequence value -> (value -> String) -> String
|
||||
hash now name (Sequence n delay dur steps) toString =
|
||||
-- we need to encode the current time in the animations name so the browser doesn't cache anything
|
||||
@ -914,16 +829,6 @@ stepHash steps toString hashed =
|
||||
)
|
||||
|
||||
|
||||
roundFloat : Float -> Float
|
||||
roundFloat f =
|
||||
toFloat (round (f * 100)) / 100
|
||||
|
||||
|
||||
floatToString : Float -> String
|
||||
floatToString f =
|
||||
String.fromFloat (roundFloat f)
|
||||
|
||||
|
||||
hashDuration : String -> Duration.Duration -> String
|
||||
hashDuration prefix dur =
|
||||
let
|
||||
@ -939,38 +844,6 @@ hashDuration prefix dur =
|
||||
(round (Duration.inSeconds dur))
|
||||
|
||||
|
||||
lastPosOr : value -> Sequence value -> value
|
||||
lastPosOr x (Sequence _ _ _ steps) =
|
||||
case steps of
|
||||
[] ->
|
||||
x
|
||||
|
||||
_ ->
|
||||
lastPosOrHelper x steps
|
||||
|
||||
|
||||
lastPosOrHelper : value -> List (Step value) -> value
|
||||
lastPosOrHelper x steps =
|
||||
case steps of
|
||||
[] ->
|
||||
x
|
||||
|
||||
(Step _ _ v) :: [] ->
|
||||
v
|
||||
|
||||
(Step _ _ _) :: (Step _ _ v) :: [] ->
|
||||
v
|
||||
|
||||
(Step _ _ _) :: (Step _ _ _) :: (Step _ _ v) :: [] ->
|
||||
v
|
||||
|
||||
(Step _ _ _) :: (Step _ _ _) :: (Step _ _ _) :: (Step _ _ v) :: [] ->
|
||||
v
|
||||
|
||||
(Step _ _ _) :: (Step _ _ _) :: (Step _ _ _) :: (Step _ _ v) :: remain ->
|
||||
lastPosOrHelper v remain
|
||||
|
||||
|
||||
type alias CssAnim =
|
||||
{ hash : String
|
||||
, animation : String
|
||||
@ -1005,7 +878,7 @@ cssForSections now startPos name lerp toString toHashString sections anim =
|
||||
[] ->
|
||||
anim
|
||||
|
||||
[ (Sequence 1 delay dur [ Step stepDur (Transition.Transition spline) v ]) as seq ] ->
|
||||
[ (Sequence 1 delay _ [ Step stepDur (Transition.Transition spline) v ]) as seq ] ->
|
||||
-- NOTE, we're not using the above `dur` because it's the total duration of the sequence
|
||||
-- and therefore equal to stepDur in this case
|
||||
{ anim
|
||||
@ -1156,7 +1029,7 @@ initialProps name toString startPos (Sequence _ _ _ steps) =
|
||||
[] ->
|
||||
[]
|
||||
|
||||
(Step dur transition val) :: _ ->
|
||||
(Step dur _ val) :: _ ->
|
||||
if Duration.isZero dur then
|
||||
[ ( name, toString val ) ]
|
||||
|
||||
@ -1219,16 +1092,6 @@ keyframeHelper name lerp startPos sequenceDuration currentDur steps rendered =
|
||||
(rendered ++ frames)
|
||||
|
||||
|
||||
emptyAnim : CssAnim
|
||||
emptyAnim =
|
||||
{ hash = ""
|
||||
, animation = ""
|
||||
, transition = ""
|
||||
, keyframes = ""
|
||||
, props = []
|
||||
}
|
||||
|
||||
|
||||
combine : CssAnim -> CssAnim -> CssAnim
|
||||
combine one two =
|
||||
if String.isEmpty one.hash && List.isEmpty one.props then
|
||||
|
@ -4,12 +4,7 @@ module InternalAnim.Quantity exposing
|
||||
, equalWithin
|
||||
, greaterThan
|
||||
, greaterThanOrEqualTo
|
||||
, greaterThanOrEqualToZero
|
||||
, greaterThanZero
|
||||
, lessThan
|
||||
, lessThanOrEqualTo
|
||||
, lessThanOrEqualToZero
|
||||
, lessThanZero
|
||||
, max
|
||||
, minus
|
||||
, multiplyBy
|
||||
@ -38,12 +33,6 @@ minus (Quantity y) (Quantity x) =
|
||||
Quantity (x - y)
|
||||
|
||||
|
||||
{-| -}
|
||||
lessThan : Quantity number units -> Quantity number units -> Bool
|
||||
lessThan (Quantity y) (Quantity x) =
|
||||
x < y
|
||||
|
||||
|
||||
{-| -}
|
||||
greaterThan : Quantity number units -> Quantity number units -> Bool
|
||||
greaterThan (Quantity y) (Quantity x) =
|
||||
@ -62,30 +51,6 @@ greaterThanOrEqualTo (Quantity y) (Quantity x) =
|
||||
x >= y
|
||||
|
||||
|
||||
{-| -}
|
||||
lessThanZero : Quantity number units -> Bool
|
||||
lessThanZero (Quantity x) =
|
||||
x < 0
|
||||
|
||||
|
||||
{-| -}
|
||||
greaterThanZero : Quantity number units -> Bool
|
||||
greaterThanZero (Quantity x) =
|
||||
x > 0
|
||||
|
||||
|
||||
{-| -}
|
||||
lessThanOrEqualToZero : Quantity number units -> Bool
|
||||
lessThanOrEqualToZero (Quantity x) =
|
||||
x <= 0
|
||||
|
||||
|
||||
{-| -}
|
||||
greaterThanOrEqualToZero : Quantity number units -> Bool
|
||||
greaterThanOrEqualToZero (Quantity x) =
|
||||
x >= 0
|
||||
|
||||
|
||||
max : Quantity number units -> Quantity number units -> Quantity number units
|
||||
max (Quantity x) (Quantity y) =
|
||||
Quantity (Basics.max x y)
|
||||
|
@ -3,6 +3,7 @@ module InternalAnim.Random exposing (random)
|
||||
{-| -}
|
||||
|
||||
|
||||
fract : Float -> Float
|
||||
fract x =
|
||||
x - toFloat (floor x)
|
||||
|
||||
@ -20,4 +21,4 @@ random seed low high =
|
||||
0
|
||||
|
||||
else
|
||||
(fract (sin seed * 100000.0) + 1.5707) / pi
|
||||
low + (((fract (sin seed * 100000.0) + 1.5707) / pi) * (high - low))
|
||||
|
@ -2,8 +2,8 @@ module InternalAnim.Time exposing
|
||||
( thisBeforeOrEqualThat, thisAfterOrEqualThat, equal
|
||||
, Absolute, AbsoluteTime(..), Duration, absolute, duration, progress
|
||||
, inMilliseconds
|
||||
, latest, earliest, toPosix, durationToString, reduceDurationBy
|
||||
, advanceBy, equalDuration, expand, isZeroDuration, maxDuration, millis, numberOfFrames, positiveDuration, progressWithin, rollbackBy, scaleDuration, thisAfterThat, thisBeforeThat, zeroDuration
|
||||
, latest, toPosix, durationToString
|
||||
, advanceBy, expand, isZeroDuration, maxDuration, millis, positiveDuration, progressWithin, rollbackBy, scaleDuration, thisAfterThat, thisBeforeThat, zeroDuration
|
||||
)
|
||||
|
||||
{-|
|
||||
@ -14,7 +14,7 @@ module InternalAnim.Time exposing
|
||||
|
||||
@docs inMilliseconds
|
||||
|
||||
@docs latest, earliest, toPosix, durationToString, reduceDurationBy
|
||||
@docs latest, toPosix, durationToString
|
||||
|
||||
-}
|
||||
|
||||
@ -74,11 +74,6 @@ expand one two =
|
||||
Quantity.plus one two
|
||||
|
||||
|
||||
reduceDurationBy : Duration -> Duration -> Duration
|
||||
reduceDurationBy one two =
|
||||
Quantity.minus one two
|
||||
|
||||
|
||||
advanceBy : Duration -> Absolute -> Absolute
|
||||
advanceBy dur time =
|
||||
Quantity.plus time (Quantity.Quantity (Duration.inMilliseconds dur))
|
||||
@ -147,15 +142,6 @@ latest ((Quantity.Quantity one) as oneQty) ((Quantity.Quantity two) as twoQty) =
|
||||
oneQty
|
||||
|
||||
|
||||
earliest : Absolute -> Absolute -> Absolute
|
||||
earliest ((Quantity.Quantity one) as oneQty) ((Quantity.Quantity two) as twoQty) =
|
||||
if (one - two) >= 0 then
|
||||
twoQty
|
||||
|
||||
else
|
||||
oneQty
|
||||
|
||||
|
||||
thisBeforeThat : Absolute -> Absolute -> Bool
|
||||
thisBeforeThat (Quantity.Quantity this) (Quantity.Quantity that) =
|
||||
(this - that) < 0
|
||||
@ -189,34 +175,3 @@ isZeroDuration (Quantity.Quantity dur) =
|
||||
equal : Absolute -> Absolute -> Bool
|
||||
equal (Quantity.Quantity this) (Quantity.Quantity that) =
|
||||
(this - that) == 0
|
||||
|
||||
|
||||
equalDuration : Duration -> Duration -> Bool
|
||||
equalDuration (Quantity.Quantity this) (Quantity.Quantity that) =
|
||||
(this - that) == 0
|
||||
|
||||
|
||||
{-| The number of frames, and the offset that's needed to preserve the framerate.
|
||||
|
||||
Offset
|
||||
|
||||
-}
|
||||
numberOfFrames : Float -> Absolute -> Absolute -> Absolute -> ( Float, Int )
|
||||
numberOfFrames fps lastFrameTime startAt endAt =
|
||||
let
|
||||
millisecondsPerFrame =
|
||||
1000 / fps
|
||||
|
||||
totalDurationInMs =
|
||||
Duration.inMilliseconds (duration startAt endAt)
|
||||
|
||||
framesSinceLastFrame =
|
||||
max 0 (Duration.inMilliseconds (duration lastFrameTime startAt))
|
||||
/ millisecondsPerFrame
|
||||
|
||||
offset =
|
||||
1 - (framesSinceLastFrame - toFloat (floor framesSinceLastFrame))
|
||||
in
|
||||
( offset * millisecondsPerFrame
|
||||
, max 1 (round (totalDurationInMs / millisecondsPerFrame))
|
||||
)
|
||||
|
@ -1,35 +1,40 @@
|
||||
module InternalAnim.Timeline exposing
|
||||
( Timeline(..), TimelineDetails, Occurring(..), getEvents
|
||||
( Timeline(..), TimelineDetails, Occurring(..)
|
||||
, Schedule(..), Event(..)
|
||||
, needsUpdate, update, updateWith
|
||||
, startTime, endTime, getEvent, extendEventDwell, hasDwell, isResting
|
||||
, update, updateWith
|
||||
, startTime, endTime, getEvent, extendEventDwell
|
||||
, addToDwell
|
||||
, sendPing
|
||||
, current, arrivedAt, arrived, previous, upcoming
|
||||
, progress
|
||||
, Line(..), Timetable(..)
|
||||
, foldpAll, captureTimeline
|
||||
, ActualDuration(..), Animator(..), Description(..), Frame(..), Frames(..), FramesSummary, Interp, LookAhead, Period(..), Previous(..), Resting(..), Summary, SummaryEvent(..), Transition, atTime, combineRunning, dwellingTime, gc, getCurrentTime, hasChanged, justInitialized, linesAreActive, periodDuration, progress
|
||||
, foldpAll
|
||||
, gc, atTime, dwellingTime, getCurrentTime, linesAreActive
|
||||
, Transition
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
@docs Timeline, TimelineDetails, Occurring, getEvents
|
||||
@docs Timeline, TimelineDetails, Occurring
|
||||
|
||||
@docs Schedule, Event
|
||||
|
||||
@docs needsUpdate, update, updateWith
|
||||
@docs update, updateWith
|
||||
|
||||
@docs startTime, endTime, getEvent, extendEventDwell, hasDwell, isResting
|
||||
@docs startTime, endTime, getEvent, extendEventDwell
|
||||
|
||||
@docs addToDwell
|
||||
|
||||
@docs sendPing
|
||||
|
||||
@docs current, arrivedAt, arrived, previous, upcoming
|
||||
|
||||
@docs progress
|
||||
|
||||
@docs Line, Timetable
|
||||
|
||||
@docs foldpAll, captureTimeline
|
||||
@docs foldpAll
|
||||
|
||||
@docs gc, atTime, dwellingTime, getCurrentTime, linesAreActive
|
||||
|
||||
@docs Transition
|
||||
|
||||
-}
|
||||
|
||||
@ -50,21 +55,6 @@ type Event event
|
||||
= Event Time.Duration event (Maybe Time.Duration)
|
||||
|
||||
|
||||
type Period
|
||||
= Loop Time.Duration
|
||||
| Repeat Int Time.Duration
|
||||
|
||||
|
||||
periodDuration : Period -> Time.Duration
|
||||
periodDuration per =
|
||||
case per of
|
||||
Loop dur ->
|
||||
dur
|
||||
|
||||
Repeat i dur ->
|
||||
dur
|
||||
|
||||
|
||||
getScheduledEvent : Event event -> event
|
||||
getScheduledEvent (Event _ ev _) =
|
||||
ev
|
||||
@ -121,29 +111,6 @@ type Occurring event
|
||||
{- TYPES FOR INTERPOLATION -}
|
||||
|
||||
|
||||
{-| First, let's cover what the type parameters are
|
||||
|
||||
Examples:
|
||||
|
||||
The symbolic state of the timeline entry.
|
||||
state -> MenuOpen
|
||||
|
||||
The description of how to animate that symbolic state
|
||||
anchor ->
|
||||
= Osc Personality Period (Float -> Float)
|
||||
| Pos Personality Float
|
||||
|
||||
The actual value that's moving
|
||||
motion -> { x:34, y: 34 }
|
||||
|
||||
-}
|
||||
type alias Interp state anchor motion =
|
||||
{ start : anchor -> motion
|
||||
, visit : Visit state anchor motion
|
||||
, transition : Lerp anchor motion
|
||||
}
|
||||
|
||||
|
||||
type alias Transition state anchor motion =
|
||||
(state -> anchor)
|
||||
-- previous event
|
||||
@ -167,74 +134,6 @@ type alias Transition state anchor motion =
|
||||
-> motion
|
||||
|
||||
|
||||
type alias Visit state anchor motion =
|
||||
(state -> anchor)
|
||||
-> Occurring state
|
||||
-> Time.Absolute
|
||||
-> Maybe (LookAhead anchor)
|
||||
-> motion
|
||||
-> motion
|
||||
|
||||
|
||||
type alias LookAhead state =
|
||||
{ anchor : state
|
||||
, time : Time.Absolute
|
||||
, resting : Bool
|
||||
}
|
||||
|
||||
|
||||
mapLookAhead : (a -> b) -> LookAhead a -> LookAhead b
|
||||
mapLookAhead fn look =
|
||||
{ anchor = fn look.anchor
|
||||
, time = look.time
|
||||
, resting = look.resting
|
||||
}
|
||||
|
||||
|
||||
type alias Lerp anchor motion =
|
||||
Time.Absolute
|
||||
-> anchor
|
||||
-> anchor
|
||||
-> Time.Absolute
|
||||
-> Time.Absolute
|
||||
-> Maybe (LookAhead anchor)
|
||||
-> motion
|
||||
-> motion
|
||||
|
||||
|
||||
type Previous event
|
||||
= Previous (Occurring event)
|
||||
| PreviouslyInterrupted Time.Absolute
|
||||
|
||||
|
||||
mapTable : (Occurring a -> Occurring b) -> Timetable a -> Timetable b
|
||||
mapTable fn (Timetable lines) =
|
||||
Timetable (List.map (mapLine fn) lines)
|
||||
|
||||
|
||||
mapLine : (Occurring a -> Occurring b) -> Line a -> Line b
|
||||
mapLine fn (Line t startEvent els) =
|
||||
Line t (fn startEvent) (List.map fn els)
|
||||
|
||||
|
||||
mapLineWith : (Occurring a -> state -> ( Occurring b, state )) -> state -> Line a -> ( Line b, state )
|
||||
mapLineWith fn initial (Line start startingEvent remaining) =
|
||||
let
|
||||
onLine occur ( events, state ) =
|
||||
let
|
||||
( newOccur, newState ) =
|
||||
fn occur state
|
||||
in
|
||||
( newOccur :: events, newState )
|
||||
|
||||
( newStartingEvent, startingState ) =
|
||||
fn startingEvent initial
|
||||
in
|
||||
case List.foldl onLine ( [], startingState ) remaining of
|
||||
( reversedEvents, newState ) ->
|
||||
( Line start newStartingEvent (List.reverse reversedEvents), newState )
|
||||
|
||||
|
||||
getEvent : Occurring event -> event
|
||||
getEvent (Occurring ev _ _) =
|
||||
ev
|
||||
@ -249,16 +148,6 @@ extendEventDwell extendBy ((Event at ev maybeDwell) as thisEvent) =
|
||||
Event at ev (addToDwell extendBy maybeDwell)
|
||||
|
||||
|
||||
hasDwell : Occurring event -> Bool
|
||||
hasDwell (Occurring _ (Quantity.Quantity start) (Quantity.Quantity end)) =
|
||||
(start - end) /= 0
|
||||
|
||||
|
||||
isResting : Occurring event -> Bool
|
||||
isResting (Occurring _ (Quantity.Quantity start) (Quantity.Quantity end)) =
|
||||
(end - start) == 0
|
||||
|
||||
|
||||
startTime : Occurring event -> Time.Absolute
|
||||
startTime (Occurring _ time _) =
|
||||
time
|
||||
@ -269,26 +158,6 @@ endTime (Occurring _ _ end) =
|
||||
end
|
||||
|
||||
|
||||
type Description event
|
||||
= DescribeStartTransition Time.Posix
|
||||
| DescribeEvent Time.Posix event
|
||||
| DescribeInterruption
|
||||
{ interruption : Time.Posix
|
||||
, target : event
|
||||
, newTarget : event
|
||||
, newTargetTime : Time.Posix
|
||||
}
|
||||
|
||||
|
||||
getEvents : Timeline event -> List (List ( Time.Posix, event ))
|
||||
getEvents (Timeline timeline) =
|
||||
case timeline.events of
|
||||
Timetable lines ->
|
||||
lines
|
||||
|> List.map (\(Line _ start ev) -> start :: ev)
|
||||
|> List.map (List.map (\(Occurring evt time _) -> ( Time.toPosix time, evt )))
|
||||
|
||||
|
||||
atTime : Time.Posix -> Timeline event -> Timeline event
|
||||
atTime now (Timeline timeline) =
|
||||
Timeline { timeline | now = Time.absolute now }
|
||||
@ -334,11 +203,6 @@ updateWith withGC possiblyNow (Timeline timeline) =
|
||||
clean : Bool -> TimelineDetails event -> TimelineDetails event
|
||||
clean runGC details =
|
||||
let
|
||||
events =
|
||||
case details.events of
|
||||
Timetable evs ->
|
||||
evs
|
||||
|
||||
running =
|
||||
case details.events of
|
||||
Timetable lines ->
|
||||
@ -349,6 +213,12 @@ clean runGC details =
|
||||
running
|
||||
, events =
|
||||
if runGC then
|
||||
let
|
||||
events =
|
||||
case details.events of
|
||||
Timetable evs ->
|
||||
evs
|
||||
in
|
||||
Timetable (garbageCollectOldEvents details.now [] events)
|
||||
|
||||
else
|
||||
@ -597,7 +467,7 @@ scaleEvent scale (Event dur event maybeDur) =
|
||||
|
||||
-}
|
||||
scheduleMatchesExisting : Schedule event -> Line event -> Bool
|
||||
scheduleMatchesExisting (Schedule scheduleDelay event schedulUpcoming) (Line lineStart lineStartEvent lineUpcoming) =
|
||||
scheduleMatchesExisting (Schedule _ event schedulUpcoming) (Line _ lineStartEvent lineUpcoming) =
|
||||
let
|
||||
equalStartEvent =
|
||||
scheduledEventEqual event lineStartEvent
|
||||
@ -619,7 +489,7 @@ scheduleMatchesExisting (Schedule scheduleDelay event schedulUpcoming) (Line lin
|
||||
|
||||
|
||||
scheduledEventEqual : Event event -> Occurring event -> Bool
|
||||
scheduledEventEqual (Event dur schedEvent maybeDwell) (Occurring occurEvent occurStart occurEnd) =
|
||||
scheduledEventEqual (Event _ schedEvent _) (Occurring occurEvent _ _) =
|
||||
schedEvent == occurEvent
|
||||
|
||||
|
||||
@ -685,17 +555,17 @@ interrupt timeline scheduled =
|
||||
|
||||
interruptLines : Time.Absolute -> Schedule event -> List (Line event) -> List (Line event) -> Maybe (List (Line event))
|
||||
interruptLines now scheduled pastLines lines =
|
||||
let
|
||||
startInterruption =
|
||||
case scheduled of
|
||||
Schedule scheduleDelay _ _ ->
|
||||
Time.advanceBy scheduleDelay now
|
||||
in
|
||||
case lines of
|
||||
[] ->
|
||||
Nothing
|
||||
|
||||
startLine :: remaining ->
|
||||
let
|
||||
startInterruption =
|
||||
case scheduled of
|
||||
Schedule scheduleDelay _ _ ->
|
||||
Time.advanceBy scheduleDelay now
|
||||
in
|
||||
if interruptionHappensLater startInterruption remaining then
|
||||
interruptLines now scheduled (startLine :: pastLines) remaining
|
||||
|
||||
@ -731,14 +601,14 @@ interruptionHappensLater startInterruption remaining =
|
||||
|
||||
interruptLine : Time.Absolute -> Schedule event -> Line event -> List (Line event) -> Maybe (List (Line event))
|
||||
interruptLine now scheduled line future =
|
||||
let
|
||||
startInterruption =
|
||||
case scheduled of
|
||||
Schedule scheduleDelay _ _ ->
|
||||
Time.advanceBy scheduleDelay now
|
||||
in
|
||||
case line of
|
||||
Line start startEvent trailing ->
|
||||
let
|
||||
startInterruption =
|
||||
case scheduled of
|
||||
Schedule scheduleDelay _ _ ->
|
||||
Time.advanceBy scheduleDelay now
|
||||
in
|
||||
if Time.thisAfterOrEqualThat startInterruption start then
|
||||
-- this line starts before the interruption
|
||||
case future of
|
||||
@ -803,18 +673,19 @@ getTransitionAt interruptionTime prev trailing =
|
||||
|
||||
|
||||
interruptAtExactly : Time.Absolute -> Schedule event -> LastTwoEvents event -> Line event
|
||||
interruptAtExactly now scheduled ((LastTwoEvents penultimateTime penultimate lastEventTime lastEvent) as last) =
|
||||
interruptAtExactly now scheduled (LastTwoEvents penultimateTime penultimate lastEventTime _) =
|
||||
case scheduled of
|
||||
Schedule delay_ startingEvent reverseQueued ->
|
||||
let
|
||||
amountProgress =
|
||||
Time.progress penultimateTime
|
||||
lastEventTime
|
||||
(Time.advanceBy delay_ now)
|
||||
|
||||
newStartingEvent =
|
||||
-- we apply the discount if we are returning to a state
|
||||
if penultimate == getScheduledEvent startingEvent then
|
||||
let
|
||||
amountProgress =
|
||||
Time.progress penultimateTime
|
||||
lastEventTime
|
||||
(Time.advanceBy delay_ now)
|
||||
in
|
||||
startingEvent
|
||||
|> adjustScheduledDuration (Quantity.multiplyBy amountProgress)
|
||||
|
||||
@ -1008,33 +879,6 @@ addToDwell duration maybeDwell =
|
||||
Just (Quantity.plus duration existing)
|
||||
|
||||
|
||||
rescale : Time.Absolute -> Float -> List (Line event) -> List (Line event)
|
||||
rescale now scale lines =
|
||||
if scale == 1 then
|
||||
lines
|
||||
|
||||
else
|
||||
List.map (rescaleLine now scale) lines
|
||||
|
||||
|
||||
rescaleLine : Time.Absolute -> Float -> Line event -> Line event
|
||||
rescaleLine now scale (Line lineStart firstEvent remain) =
|
||||
Line
|
||||
(rescaleTime now scale lineStart)
|
||||
(rescaleEvent now scale firstEvent)
|
||||
(List.map (rescaleEvent now scale) remain)
|
||||
|
||||
|
||||
rescaleTime : Time.Absolute -> Float -> Time.Absolute -> Time.Absolute
|
||||
rescaleTime (Quantity.Quantity now) scale (Quantity.Quantity time) =
|
||||
Time.millis (now + ((time - now) * scale))
|
||||
|
||||
|
||||
rescaleEvent : Time.Absolute -> Float -> Occurring event -> Occurring event
|
||||
rescaleEvent now scale (Occurring event start end) =
|
||||
Occurring event (rescaleTime now scale start) (rescaleTime now scale end)
|
||||
|
||||
|
||||
foldpAll :
|
||||
(state -> anchor)
|
||||
-> (anchor -> motion)
|
||||
@ -1052,7 +896,7 @@ foldpAll lookup toStart transitionTo (Timeline timelineDetails) =
|
||||
[] ->
|
||||
start
|
||||
|
||||
(Line lineStart firstEvent remain) :: _ ->
|
||||
(Line lineStart _ _) :: _ ->
|
||||
visitAll2
|
||||
lookup
|
||||
transitionTo
|
||||
@ -1238,176 +1082,6 @@ visitAll2 toAnchor transitionTo details prev queue future state =
|
||||
new
|
||||
|
||||
|
||||
type alias FramesSummary motion =
|
||||
{ frames : List (Frame motion)
|
||||
, duration : Time.Duration
|
||||
, dwell :
|
||||
Maybe
|
||||
{ period : Period
|
||||
, frames : List (Frame motion)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
type Frame motion
|
||||
= Frame Float motion
|
||||
|
||||
|
||||
type alias Summary event =
|
||||
{ events : List (SummaryEvent event)
|
||||
, now : Time.Absolute
|
||||
, startTime : Time.Absolute
|
||||
}
|
||||
|
||||
|
||||
type SummaryEvent event
|
||||
= EventSummary event Time.Absolute ActualDuration
|
||||
| InterruptionSummary
|
||||
{ target : event
|
||||
, targetTime : Time.Absolute
|
||||
, interruptedAt : Time.Absolute
|
||||
, newTarget : event
|
||||
, newTargetTime : Time.Absolute
|
||||
, newTargetDuration : ActualDuration
|
||||
}
|
||||
|
||||
|
||||
type ActualDuration
|
||||
= OpenDuration
|
||||
| KnownDuration Time.Duration
|
||||
|
||||
|
||||
{-| -}
|
||||
type Frames item
|
||||
= Single item
|
||||
| Hold Int item
|
||||
| Walk item (List (Frames item))
|
||||
| WithRest (Resting item) (Frames item)
|
||||
|
||||
|
||||
{-| -}
|
||||
type Resting item
|
||||
= Cycle Period (List (Frames item))
|
||||
|
||||
|
||||
captureTimeline :
|
||||
(state -> anchor)
|
||||
-> Timeline state
|
||||
-> Summary anchor
|
||||
captureTimeline lookup (Timeline timelineDetails) =
|
||||
case timelineDetails.events of
|
||||
Timetable timetable ->
|
||||
case timetable of
|
||||
[] ->
|
||||
-- I believe this case is fleeting because as soon as we have a real time,
|
||||
-- we add a line to the timetable.
|
||||
-- However, maybe it is awkwardly rendered once?
|
||||
{ events =
|
||||
[ EventSummary
|
||||
(lookup timelineDetails.initial)
|
||||
timelineDetails.now
|
||||
OpenDuration
|
||||
]
|
||||
, now = timelineDetails.now
|
||||
, startTime = timelineDetails.now
|
||||
}
|
||||
|
||||
(Line start startEv remain) :: remainingLines ->
|
||||
let
|
||||
events =
|
||||
captureTimelineHelper lookup
|
||||
(startEv :: remain)
|
||||
remainingLines
|
||||
[]
|
||||
in
|
||||
{ events = List.reverse events
|
||||
, now = timelineDetails.now
|
||||
, startTime = start
|
||||
}
|
||||
|
||||
|
||||
{-| Summarize all the events on the current timeline.
|
||||
|
||||
Note, this does not take into account time adjustments!
|
||||
|
||||
Essentially this is only used for sprite animation, which currently dont have leaveLate or arriveEarly.
|
||||
|
||||
-}
|
||||
captureTimelineHelper :
|
||||
(state -> anchor)
|
||||
-> List (Occurring state)
|
||||
-> List (Line state)
|
||||
-> List (SummaryEvent anchor)
|
||||
-> List (SummaryEvent anchor)
|
||||
captureTimelineHelper lookup events futureLines summary =
|
||||
-- futureStart starts a new line.
|
||||
-- if an interruption occurs, we want to interpolate to the point of the interruption
|
||||
-- then transition over to the new line.
|
||||
case events of
|
||||
[] ->
|
||||
case futureLines of
|
||||
[] ->
|
||||
summary
|
||||
|
||||
(Line futureStart futureStartEv futureRemain) :: restOfFuture ->
|
||||
captureTimelineHelper lookup (futureStartEv :: futureRemain) restOfFuture summary
|
||||
|
||||
(Occurring event start eventEnd) :: remain ->
|
||||
case futureLines of
|
||||
[] ->
|
||||
case remain of
|
||||
[] ->
|
||||
let
|
||||
newEvent =
|
||||
EventSummary (lookup event) start OpenDuration
|
||||
in
|
||||
newEvent :: summary
|
||||
|
||||
_ ->
|
||||
let
|
||||
newEvent =
|
||||
EventSummary (lookup event) start (KnownDuration (Time.duration start eventEnd))
|
||||
in
|
||||
captureTimelineHelper lookup remain futureLines (newEvent :: summary)
|
||||
|
||||
(Line futureStart futureStartEv futureRemain) :: restOfFuture ->
|
||||
if Time.thisBeforeOrEqualThat futureStart start then
|
||||
-- interruption
|
||||
let
|
||||
newEvent =
|
||||
InterruptionSummary
|
||||
{ target = lookup event
|
||||
, targetTime = start
|
||||
, interruptedAt = futureStart
|
||||
, newTarget = lookup (getEvent futureStartEv)
|
||||
, newTargetTime = startTime futureStartEv
|
||||
, newTargetDuration =
|
||||
case futureRemain of
|
||||
[] ->
|
||||
case restOfFuture of
|
||||
[] ->
|
||||
OpenDuration
|
||||
|
||||
_ ->
|
||||
KnownDuration (Time.duration start eventEnd)
|
||||
|
||||
_ ->
|
||||
KnownDuration (Time.duration start eventEnd)
|
||||
}
|
||||
in
|
||||
captureTimelineHelper lookup futureRemain restOfFuture (newEvent :: summary)
|
||||
|
||||
else
|
||||
-- queue up new events
|
||||
let
|
||||
newEvent =
|
||||
EventSummary (lookup event)
|
||||
start
|
||||
(KnownDuration (Time.duration start eventEnd))
|
||||
in
|
||||
captureTimelineHelper lookup remain futureLines (newEvent :: summary)
|
||||
|
||||
|
||||
|
||||
{- BOOKKEEPING -}
|
||||
|
||||
@ -1418,10 +1092,10 @@ type Status
|
||||
|
||||
|
||||
status : Timeline event -> Status
|
||||
status ((Timeline details) as timeline) =
|
||||
status timeline =
|
||||
foldpAll identity
|
||||
(\_ -> Dwelling Time.zeroDuration)
|
||||
(\lookup prev target now start end future state ->
|
||||
(\_ _ _ now start end _ _ ->
|
||||
if Time.thisAfterThat now end then
|
||||
Dwelling (Time.duration now end)
|
||||
|
||||
@ -1466,7 +1140,7 @@ arrived : Timeline event -> event
|
||||
arrived ((Timeline details) as timeline) =
|
||||
foldpAll identity
|
||||
(\_ -> details.initial)
|
||||
(\lookup prev target now start end future state ->
|
||||
(\_ _ target now _ end _ state ->
|
||||
-- This is the current event when
|
||||
-- we have started toward an event or arrived at it.
|
||||
-- A tricky aspect is that css timelines are only updated on transition
|
||||
@ -1484,7 +1158,7 @@ current : Timeline event -> event
|
||||
current ((Timeline details) as timeline) =
|
||||
foldpAll identity
|
||||
(\_ -> details.initial)
|
||||
(\lookup prev target now start end future state ->
|
||||
(\_ _ target now start end future state ->
|
||||
-- This is the current event when
|
||||
-- we have started toward an event or arrived at it.
|
||||
-- A tricky aspect is that css timelines are only updated on transition
|
||||
@ -1508,7 +1182,7 @@ previous : Timeline event -> event
|
||||
previous ((Timeline details) as timeline) =
|
||||
foldpAll identity
|
||||
(\_ -> details.initial)
|
||||
(\lookup prev target now start end future state ->
|
||||
(\_ _ target now _ _ future state ->
|
||||
if Time.thisAfterThat now (endTime target) then
|
||||
case future of
|
||||
[] ->
|
||||
@ -1527,7 +1201,7 @@ arrivedAt : (event -> Bool) -> Time.Posix -> Timeline event -> Bool
|
||||
arrivedAt matches newTime ((Timeline details) as tl) =
|
||||
foldpAll identity
|
||||
(\_ -> False)
|
||||
(\lookup prev target now start end future state ->
|
||||
(\_ _ target _ _ end _ state ->
|
||||
state
|
||||
|| (matches (getEvent target)
|
||||
&& Time.thisBeforeOrEqualThat details.now end
|
||||
@ -1553,7 +1227,7 @@ matchesEvent matches (Event _ event _) =
|
||||
|
||||
|
||||
anyScheduled : (event -> Bool) -> Schedule event -> Bool
|
||||
anyScheduled matches (Schedule dur startEvent remainingEvents) =
|
||||
anyScheduled matches (Schedule _ startEvent remainingEvents) =
|
||||
if matchesEvent matches startEvent then
|
||||
True
|
||||
|
||||
@ -1576,149 +1250,10 @@ upcoming matches ((Timeline details) as tl) =
|
||||
else
|
||||
foldpAll identity
|
||||
(\_ -> False)
|
||||
(\lookup prev target now start end future state ->
|
||||
(\_ _ target now _ end _ state ->
|
||||
state
|
||||
|| (matches (getEvent target)
|
||||
&& Time.thisBeforeThat now end
|
||||
)
|
||||
)
|
||||
tl
|
||||
|
||||
|
||||
|
||||
{- ANIMATOR -}
|
||||
{- The animator checks to see if any timelines are running and also has the ability to update the animation state.
|
||||
|
||||
|
||||
Different animators can do different things
|
||||
|
||||
|
||||
- Normal -> always on
|
||||
- Inline -> on when moving (require anotation of dwelling events)
|
||||
- CSS -> single update when timeline is updated
|
||||
|
||||
|
||||
-}
|
||||
|
||||
|
||||
{-| -}
|
||||
type Animator model
|
||||
= Animator (model -> Running) (Time.Posix -> model -> model)
|
||||
|
||||
|
||||
type alias Running =
|
||||
{ running : Bool
|
||||
, ping : Maybe { delay : Float, target : Time.Posix }
|
||||
}
|
||||
|
||||
|
||||
combineRunning : Running -> Running -> Running
|
||||
combineRunning one two =
|
||||
{ running = one.running || two.running
|
||||
, ping =
|
||||
case two.ping of
|
||||
Nothing ->
|
||||
one.ping
|
||||
|
||||
Just twoPing ->
|
||||
case one.ping of
|
||||
Nothing ->
|
||||
Just twoPing
|
||||
|
||||
Just onePing ->
|
||||
if onePing.delay < twoPing.delay then
|
||||
Just onePing
|
||||
|
||||
else
|
||||
Just twoPing
|
||||
}
|
||||
|
||||
|
||||
sendPing : Timeline event -> Maybe { delay : Float, target : Time.Posix }
|
||||
sendPing ((Timeline details) as timeline) =
|
||||
Maybe.andThen (encodeStamp details.now) (findNextTransitionTime timeline)
|
||||
|
||||
|
||||
findNextTransitionTime : Timeline event -> Maybe Time.Absolute
|
||||
findNextTransitionTime ((Timeline details) as timeline) =
|
||||
foldpAll identity
|
||||
(\_ -> Nothing)
|
||||
(\lookup prev target now start end future state ->
|
||||
if Time.thisBeforeThat now end && (startTime target == end) then
|
||||
Just (startTime target)
|
||||
|
||||
else
|
||||
state
|
||||
)
|
||||
timeline
|
||||
|
||||
|
||||
{-| This is to account for a bug in `Time.every` where it uses the provided delay time as a key to keep track of a time.
|
||||
|
||||
To get around this, we encode the current time as a really small part of the given time.
|
||||
|
||||
-}
|
||||
encodeStamp : Time.Absolute -> Time.Absolute -> Maybe { delay : Float, target : Time.Posix }
|
||||
encodeStamp now target =
|
||||
let
|
||||
millis =
|
||||
Time.inMilliseconds target - nowInMillis
|
||||
|
||||
nowInMillis =
|
||||
Time.inMilliseconds now
|
||||
|
||||
nowTail =
|
||||
nowInMillis / 1000000
|
||||
|
||||
pingDelay =
|
||||
millis + (1 / nowTail)
|
||||
in
|
||||
if pingDelay <= 0 then
|
||||
Nothing
|
||||
|
||||
else
|
||||
Just
|
||||
{ delay = pingDelay
|
||||
, target =
|
||||
Time.millisToPosix (round (Time.inMilliseconds target + 1))
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
needsUpdate : Timeline event -> Bool
|
||||
needsUpdate ((Timeline timeline) as tl) =
|
||||
case timeline.queued of
|
||||
Nothing ->
|
||||
case timeline.interruption of
|
||||
[] ->
|
||||
timeline.running
|
||||
|
||||
_ ->
|
||||
True
|
||||
|
||||
Just _ ->
|
||||
True
|
||||
|
||||
|
||||
{-| -}
|
||||
hasChanged : Timeline event -> Bool
|
||||
hasChanged (Timeline timeline) =
|
||||
case timeline.queued of
|
||||
Nothing ->
|
||||
case timeline.interruption of
|
||||
[] ->
|
||||
False
|
||||
|
||||
_ ->
|
||||
True
|
||||
|
||||
Just _ ->
|
||||
True
|
||||
|
||||
|
||||
{-| -}
|
||||
justInitialized : Timeline event -> Bool
|
||||
justInitialized (Timeline timeline) =
|
||||
case timeline.now of
|
||||
Quantity.Quantity qty ->
|
||||
qty == 0
|
||||
|
@ -1,10 +1,10 @@
|
||||
module InternalAnim.Transition exposing
|
||||
( Transition(..)
|
||||
, linear, standard, wobble, bezier
|
||||
, initialVelocity, atX
|
||||
, split, before
|
||||
, hash, keyframes
|
||||
, atX2, isStandard, takeAfter, withVelocities
|
||||
, atX
|
||||
, isStandard
|
||||
, takeAfter, withVelocities
|
||||
)
|
||||
|
||||
{-|
|
||||
@ -13,12 +13,14 @@ module InternalAnim.Transition exposing
|
||||
|
||||
@docs linear, standard, wobble, bezier
|
||||
|
||||
@docs initialVelocity, atX
|
||||
|
||||
@docs split, before
|
||||
|
||||
@docs hash, keyframes
|
||||
|
||||
@docs atX
|
||||
|
||||
@docs isStandard
|
||||
|
||||
@docs takeAfter, withVelocities
|
||||
|
||||
Current bezier formats for elm-animator
|
||||
|
||||
Standard:
|
||||
@ -62,11 +64,7 @@ Goals:
|
||||
|
||||
import Bezier
|
||||
import Bezier.Spring as Spring
|
||||
import InternalAnim.Duration as Duration
|
||||
import InternalAnim.Hash as Hash
|
||||
import InternalAnim.Quantity as Quantity
|
||||
import InternalAnim.Time as Time
|
||||
import InternalAnim.Units as Units
|
||||
|
||||
|
||||
{-| A transition are all the bezier curves between A and B that we want to transition through.
|
||||
@ -197,76 +195,15 @@ isStandard trans =
|
||||
False
|
||||
|
||||
|
||||
type alias TimeDomain =
|
||||
{ start : PointInTime
|
||||
, end : PointInTime
|
||||
}
|
||||
|
||||
|
||||
type alias PointInTime =
|
||||
{ x : Time.Absolute
|
||||
, y : Units.Pixels
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
atX :
|
||||
Float
|
||||
-> TimeDomain
|
||||
-> Units.PixelsPerSecond
|
||||
-> Units.PixelsPerSecond
|
||||
-> Transition
|
||||
->
|
||||
{ position : Units.Pixels
|
||||
, velocity : Units.PixelsPerSecond
|
||||
}
|
||||
atX progress domain introVelocity exitVelocity transition =
|
||||
case transition of
|
||||
Transition spline ->
|
||||
if domain.start.x == domain.end.x then
|
||||
{ position = domain.end.y
|
||||
, velocity =
|
||||
exitVelocity
|
||||
}
|
||||
|
||||
else
|
||||
spline
|
||||
|> inTimeDomain domain introVelocity exitVelocity
|
||||
|> posVel (toTimeProgress domain progress)
|
||||
|
||||
Wobble wob ->
|
||||
let
|
||||
totalX =
|
||||
Time.inMilliseconds domain.end.x - Time.inMilliseconds domain.start.x
|
||||
|
||||
params =
|
||||
Spring.new
|
||||
{ wobble = wob.wobble
|
||||
, quickness = wob.quickness
|
||||
, settleMax = totalX
|
||||
}
|
||||
in
|
||||
Spring.at
|
||||
{ spring = params
|
||||
, initial =
|
||||
{ position = Units.inPixels domain.start.y
|
||||
, velocity = Units.inPixelsPerMs introVelocity
|
||||
}
|
||||
, target = Units.inPixels domain.end.y
|
||||
}
|
||||
(totalX * progress)
|
||||
|> wrapUnits
|
||||
|
||||
|
||||
{-| -}
|
||||
atX2 :
|
||||
Float
|
||||
-> Transition
|
||||
->
|
||||
{ position : Bezier.Point
|
||||
, velocity : Bezier.Point
|
||||
}
|
||||
atX2 progress transition =
|
||||
atX progress transition =
|
||||
case transition of
|
||||
Transition spline ->
|
||||
let
|
||||
@ -327,146 +264,6 @@ withVelocities intro exit transition =
|
||||
}
|
||||
|
||||
|
||||
toTimeProgress :
|
||||
TimeDomain
|
||||
-> Float
|
||||
-> Float
|
||||
toTimeProgress domain factor =
|
||||
let
|
||||
start =
|
||||
Time.inMilliseconds domain.start.x
|
||||
|
||||
end =
|
||||
Time.inMilliseconds domain.end.x
|
||||
in
|
||||
((end - start) * factor) + start
|
||||
|
||||
|
||||
wrapUnits state =
|
||||
{ position =
|
||||
Units.pixels state.position
|
||||
, velocity =
|
||||
Units.pixelsPerSecond (state.velocity * 1000)
|
||||
}
|
||||
|
||||
|
||||
posVel :
|
||||
Float
|
||||
-> Bezier.Spline
|
||||
->
|
||||
{ position : Units.Pixels
|
||||
, velocity : Units.PixelsPerSecond
|
||||
}
|
||||
posVel progress spline =
|
||||
let
|
||||
current =
|
||||
Bezier.atX progress spline
|
||||
|
||||
firstDeriv =
|
||||
Bezier.firstDerivative spline current.t
|
||||
in
|
||||
{ position =
|
||||
Units.pixels current.point.y
|
||||
, velocity =
|
||||
Units.pixelsPerSecond ((firstDeriv.y / firstDeriv.x) * 1000)
|
||||
}
|
||||
|
||||
|
||||
zeroVelocity : Float
|
||||
zeroVelocity =
|
||||
0
|
||||
|
||||
|
||||
initialVelocity : Transition -> Float
|
||||
initialVelocity transition =
|
||||
case transition of
|
||||
Transition spline ->
|
||||
let
|
||||
firstDeriv =
|
||||
-- at t == 0, the first derivative vector will always be 0,0
|
||||
-- so we cheat in slightly.
|
||||
Bezier.firstDerivative spline 0.001
|
||||
in
|
||||
if firstDeriv.x == 0 then
|
||||
zeroVelocity
|
||||
|
||||
else
|
||||
firstDeriv.y / firstDeriv.x
|
||||
|
||||
Wobble wob ->
|
||||
zeroVelocity
|
||||
|
||||
|
||||
type alias Domain =
|
||||
{ start : Bezier.Point
|
||||
, end : Bezier.Point
|
||||
}
|
||||
|
||||
|
||||
third : Float
|
||||
third =
|
||||
1 / 3
|
||||
|
||||
|
||||
negativeThird : Float
|
||||
negativeThird =
|
||||
-1 / 3
|
||||
|
||||
|
||||
{-| Note, we only rotate the control point to match the desired velocity.
|
||||
|
||||
However, there is the question of the magnitude of the control point.
|
||||
|
||||
I _think_ the magnitude is roughly equivalent to momentum.
|
||||
|
||||
It's possible that we override the built-in control points when there is a non-0 intro/exit Velocity.
|
||||
|
||||
Maybe it's a constant like 1/3 or something....
|
||||
|
||||
-}
|
||||
toDomain : Domain -> Float -> Float -> Bezier.Spline -> Bezier.Spline
|
||||
toDomain domain introVelocity exitVelocity spline =
|
||||
let
|
||||
{ two, three } =
|
||||
toBezierPoints spline
|
||||
|
||||
totalX =
|
||||
domain.end.x - domain.start.x
|
||||
|
||||
totalY =
|
||||
domain.end.y - domain.start.y
|
||||
|
||||
ctrl1 =
|
||||
let
|
||||
angle =
|
||||
atan2 introVelocity 1
|
||||
in
|
||||
{ x =
|
||||
(totalX * two.x) + domain.start.x
|
||||
, y =
|
||||
(totalY * two.y) + domain.start.y
|
||||
}
|
||||
|> rotateAround angle domain.start
|
||||
|
||||
ctrl2 =
|
||||
let
|
||||
angle =
|
||||
atan2 exitVelocity 1
|
||||
in
|
||||
{ x =
|
||||
(totalX * three.x) + domain.start.x
|
||||
, y =
|
||||
(totalY * three.y) + domain.start.y
|
||||
}
|
||||
|> rotateAround angle domain.end
|
||||
in
|
||||
Bezier.fromPoints
|
||||
domain.start
|
||||
ctrl1
|
||||
ctrl2
|
||||
domain.end
|
||||
|
||||
|
||||
toBezierPoints : Bezier.Spline -> { one : Bezier.Point, two : Bezier.Point, three : Bezier.Point, four : Bezier.Point }
|
||||
toBezierPoints spline =
|
||||
{ one = Bezier.first spline
|
||||
@ -476,136 +273,6 @@ toBezierPoints spline =
|
||||
}
|
||||
|
||||
|
||||
inTimeDomain : TimeDomain -> Units.PixelsPerSecond -> Units.PixelsPerSecond -> Bezier.Spline -> Bezier.Spline
|
||||
inTimeDomain domain introVelocity exitVelocity spline =
|
||||
let
|
||||
{ one, two, three, four } =
|
||||
toBezierPoints spline
|
||||
|
||||
totalX =
|
||||
Time.inMilliseconds domain.end.x - Time.inMilliseconds domain.start.x
|
||||
|
||||
totalY =
|
||||
Units.inPixels domain.end.y - Units.inPixels domain.start.y
|
||||
|
||||
ctrl1 =
|
||||
let
|
||||
angle =
|
||||
atan2 (Units.inPixelsPerMs introVelocity) 1
|
||||
in
|
||||
{ x =
|
||||
(totalX * two.x) + Time.inMilliseconds domain.start.x
|
||||
, y =
|
||||
(totalY * two.y) + Units.inPixels domain.start.y
|
||||
}
|
||||
|> rotateAroundTimePoint angle domain.start
|
||||
|
||||
ctrl2 =
|
||||
let
|
||||
angle =
|
||||
atan2 (Units.inPixelsPerMs exitVelocity) 1
|
||||
in
|
||||
{ x =
|
||||
(totalX * three.x) + Time.inMilliseconds domain.start.x
|
||||
, y =
|
||||
(totalY * three.y) + Units.inPixels domain.start.y
|
||||
}
|
||||
|> rotateAroundTimePoint angle domain.end
|
||||
in
|
||||
Bezier.fromPoints
|
||||
{ x = Time.inMilliseconds domain.start.x
|
||||
, y = Units.inPixels domain.start.y
|
||||
}
|
||||
ctrl1
|
||||
ctrl2
|
||||
{ x = Time.inMilliseconds domain.end.x
|
||||
, y = Units.inPixels domain.end.y
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
rotateAroundTimePoint : Float -> PointInTime -> Bezier.Point -> Bezier.Point
|
||||
rotateAroundTimePoint radians center point =
|
||||
let
|
||||
centerX =
|
||||
Time.inMilliseconds center.x
|
||||
|
||||
centerY =
|
||||
Units.inPixels center.y
|
||||
in
|
||||
{ x = cos radians * (point.x - centerX) - sin radians * (point.y - centerY) + centerX
|
||||
, y = sin radians * (point.x - centerX) + cos radians * (point.y - centerY) + centerY
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
rotateAround : Float -> Bezier.Point -> Bezier.Point -> Bezier.Point
|
||||
rotateAround radians center point =
|
||||
{ x = cos radians * (point.x - center.x) - sin radians * (point.y - center.y) + center.x
|
||||
, y = sin radians * (point.x - center.x) + cos radians * (point.y - center.y) + center.y
|
||||
}
|
||||
|
||||
|
||||
translateBy : Bezier.Point -> Bezier.Point -> Bezier.Point
|
||||
translateBy one two =
|
||||
{ x = one.x + two.x
|
||||
, y = one.y + two.y
|
||||
}
|
||||
|
||||
|
||||
scaleBy : Float -> Bezier.Point -> Bezier.Point
|
||||
scaleBy k v =
|
||||
{ x = k * v.x
|
||||
, y = k * v.y
|
||||
}
|
||||
|
||||
|
||||
scaleAbout : Bezier.Point -> Float -> Bezier.Point -> Bezier.Point
|
||||
scaleAbout p0 k p =
|
||||
{ x = p0.x + k * (p.x - p0.x)
|
||||
, y = p0.y + k * (p.y - p0.y)
|
||||
}
|
||||
|
||||
|
||||
{-| We are vector v and want the component in the direction d.
|
||||
-}
|
||||
componentIn : Bezier.Point -> Bezier.Point -> Float
|
||||
componentIn d v =
|
||||
v.x * d.x + v.y * d.y
|
||||
|
||||
|
||||
zeroPoint : Bezier.Point
|
||||
zeroPoint =
|
||||
{ x = 0
|
||||
, y = 0
|
||||
}
|
||||
|
||||
|
||||
scaleTo : Float -> Bezier.Point -> Bezier.Point
|
||||
scaleTo q v =
|
||||
let
|
||||
largestComponent =
|
||||
max (abs v.x) (abs v.y)
|
||||
in
|
||||
if largestComponent == 0 then
|
||||
zeroPoint
|
||||
|
||||
else
|
||||
let
|
||||
scaledX =
|
||||
v.x / largestComponent
|
||||
|
||||
scaledY =
|
||||
v.y / largestComponent
|
||||
|
||||
scaledLength =
|
||||
sqrt (scaledX * scaledX + scaledY * scaledY)
|
||||
in
|
||||
{ x = q * scaledX / scaledLength
|
||||
, y = q * scaledY / scaledLength
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
hash : Transition -> String
|
||||
hash transition =
|
||||
@ -788,20 +455,6 @@ keyframeListFromNonNormalizedBezier steps toString str =
|
||||
)
|
||||
|
||||
|
||||
{-| -}
|
||||
before : Float -> Transition -> Transition
|
||||
before t transition =
|
||||
transition
|
||||
|
||||
|
||||
{-| -}
|
||||
split : Float -> Transition -> { before : Transition, after : Transition }
|
||||
split t transition =
|
||||
{ before = transition
|
||||
, after = transition
|
||||
}
|
||||
|
||||
|
||||
takeAfter : Float -> Transition -> Transition
|
||||
takeAfter t transition =
|
||||
case transition of
|
||||
|
@ -2,11 +2,9 @@ module InternalAnim.Units exposing
|
||||
( Pixels
|
||||
, PixelsPerSecond
|
||||
, inPixels
|
||||
, inPixelsPerMs
|
||||
, inPixelsPerSecond
|
||||
, pixels
|
||||
, pixelsPerSecond
|
||||
, zero
|
||||
)
|
||||
|
||||
{-| -}
|
||||
@ -22,30 +20,6 @@ type InPixels
|
||||
= InPixels
|
||||
|
||||
|
||||
zero =
|
||||
Quantity.Quantity 0
|
||||
|
||||
|
||||
type Unit guard
|
||||
= Unit Float
|
||||
|
||||
|
||||
type alias Position =
|
||||
Unit Pos
|
||||
|
||||
|
||||
type Pos
|
||||
= Pos
|
||||
|
||||
|
||||
type alias Velocity =
|
||||
Unit Vel
|
||||
|
||||
|
||||
type Vel
|
||||
= Vel
|
||||
|
||||
|
||||
type alias Pixels =
|
||||
Quantity.Quantity Float InPixels
|
||||
|
||||
@ -59,11 +33,6 @@ inPixelsPerSecond (Quantity.Quantity pps) =
|
||||
pps
|
||||
|
||||
|
||||
inPixelsPerMs : PixelsPerSecond -> Float
|
||||
inPixelsPerMs (Quantity.Quantity pps) =
|
||||
1000 * pps
|
||||
|
||||
|
||||
inPixels : Pixels -> Float
|
||||
inPixels (Quantity.Quantity pixs) =
|
||||
pixs
|
||||
|
Loading…
Reference in New Issue
Block a user