type alias Model =
{ position : Vector }
type alias Model =
{ position : Animation Vector }
update : Action -> Model -> Model
update action model =
case action of
Move -> { model | position <- Animation.play position }
view : Model -> Form
view model =
circle 20
|> filled red
|> move (model.position.current.x, model.position.current.y)
type alias Animation a =
{ previous : List a
, current : a
, next : List a
}
linear : Int -> Animation Float
myAnimation =
map2 Vector
(map ((*) 10) (linear 20))
(map ((*) 30) (linear 20))
map sqrt (linear 20)
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
what the heck does reset do? The current frame is not the first frame, as the docs claim. If I understand it correctly, it undoes history, then jumps to the current frame, and then continues forward.
But I'm concerned with the notion of generating all frames explicitly up front.
Besides the space cost, frames are not guaranteed to be equally spaced in time. This is why Time.fps and AnimationFrame.frame are a signal of numbers, and those numbers tell you the elapsed time. Sampling on the signals and ignoring the number will lead to unpredictable animations as the framerate changes. Besides, it's much more natural to say "I want a 3 second animation" than "I want a 180 frame animation". Yes, this means keeping functions around instead of applying them up front. But I think that you can still get all the mapping goodness using function composition.
I like the simplicity of linear : Int -> Animation Float so let's see if we can write linear' : Time -> Animation Float as something we can work with. How about animate : Animation a -> Time -> (a, Animation a) which resembles random seeds a bit. Basically, you get a model for the current time, and the new animation saves the time delta so it can compute the next frame. An animation knows the desired run time and the time elapsed, and can interpolate from there. I think this would go well with foldp in a model. Also like random seeds, storing the animation in the model would technically be storing a function or two but it's an opaque type so that's okay.
type alias Function a = a -> a
map : (a -> b) -> Function a -> Function b
map f func b =
-- how do I convert b to a to then apply func then f ?
Hopefully that's helpful. I am resisting the urge to fork your library since I was hoping to finish up other side projects today :)
PS: If you want to dive into the deep end of animation, I recommend spending a few hours on this article.
I like where this is going in that more people are thinking about the complexities here.
I just got back from vacation, so I haven't had a chance yet to follow up on the previous thread, and I'm sure I will have more thoughts on this one as well, but for now I have just a few to contribute:
I noticed you implemented all the different interpolation (animation curve) types.
linear : Int -> Animation Float
More general thoughts:
- I'm skeptical about precomputing the frames, but I haven't played around with that approach yet. I do understand the desire to make the model not contain functions, but it just feels incorrect. Maybe my concern about this is irrelevant in practice.
- I think you'll have trouble duplicating this effect [Move,Move.elm]. Note that if you click while the square is still moving, it smoothly redirects to the new target location. In your FollowMouse example, the redirections keep the position continuous, but has discontinuities in the velocity. The Move demo itself may seem frivolous, but this hints at a whole class of "interruptible" interactions that are partially animated and partially controlled real-time by user input.
I've also been trying to reconsider a completely different approach for animations (and one that Evan originally proposed) where we carefully identify a set of things that can be handled in native-space instead of in elm-space if we can find a simple but still flexible way to do that.
I also suspect you'll have trouble implementing more sophisticated animations like these [physics], which I intend to attempt to duplicate next. We'll need to make things like that easy as well if we want to compete with UIKit (iOS).
In any case, those are a few thoughts since I only have a brief amount of free time this weekend. Thanks for sharing what you've started. I'm curious to see where your path leads.
type Request
= Animate
| StopAnimation
| ...
cubic x = x*x*x
out f x = 1 - f (1-x)
inout f g x = 0.5 * if x < 0.5 then f (2*x) else 2 - g (2 - 2*x)
I think this can work, but I'll wait for feedback and see about getting my other side projects done.
what is the problem we want to solve?
what is the problem we want to solve?Making this website possible in Elm. http://material.cmiscm.com/
1) Mappable animations (we want the ability for the user to extend animations to their favorite types)
2) Physics-based animations. We want springs, we want forces.
3) We want interruptible animations. We want the ability to interrupt animations (maybe by a "merge : Animation a -> Animation a -> Animation a" function) and we'd even like to tweak these things.
From the article you shared, especially the exponential decay example, we may want animations that depend on their own past states or that of other animations. That could be via finding a good way to represent difference equations.
type alias Animation a =
{ render : Time -> a , time : Time } map : (a -> b) -> Animation a -> Animation b map f animation = { animation | render <- animation.render >> f }
map2 : (a -> b -> c) -> Animation a -> Animation b -> Animation c map2 f a b = { render = \time -> f (a.render time) (b.render time) , time = max a.time b.time } andMap : Animation (a -> b) -> Animation a -> Animation b andMap = map2 (<|) linear : Animation Floatlinear = initialize identity initialize : (Time -> a) -> Animation a initialize render = Animation render 0 run : Animation a -> a run animation = animation.render animation.time nextFrame : Time -> Animation a -> Animation a nextFrame delta animation = { animation | time <- animation.time + delta } reset : Animation a -> Animation a reset animation = { animation | time <- 0 }
animate : Animation a -> Signal Time -> Signal a animate animation deltas = Signal.map (\delta -> nextFrame delta animation |> run) deltasmap2 : (a -> b -> c) -> AnimFunc a -> AnimFunc b -> AnimFunc c
map2 f a b = \t -> f (a t) (b t)
Essentially, we can build up a tree using closures rather than storing things in a union type. Polymorphic heterogenous arbitrary-arity mapping - huzzah!
Unfortunately, storing things in the closure has its downside. You can't build things up with small make-a-copy modifiers, like animation |> start 100 |> end 300. And you can't read the values, which is critical if you want to undo or reverse or interrupt an animation.
I'm not sure there's a compromise; this sounds like shallow and deep embedding. (Deep embedding: explicit representation of values as data, a tangible AST. Shallow embedding: everything is a function in the host language, with composition governed by types.) To wit, one of the hallmarks of a shallow embedding is that there's only one meaning of a term, and that is to apply out the function all the way and see what you get. But, just to be sure I'm not missing something, let's try
type alias AnimRecord a = {animated : a, delay : Time} -- with a few more values
type alias AnimFunc a = Time -> AnimRecord a
The result of applying the function with a time is now a record, with the polymorphic value (computed with the values in the closure) and other fields preserving the values from the closure, independent of time (except perhaps the time itself). This means that functions that need to inspect the values can apply an AnimFunc at time 0 to get the closure back, and animation just needs to fish out .animated.
map looks good:
map : (a -> b) -> AnimFunc a -> AnimFunc b
map f a = \t -> let record = a t
in {record| animated <- f record.animated}
But once again we're left with the problem of combining things like duration and delay from two records into one. Duration is possible (how long was at least one constituent animation playing?) but delay is tricky. Actually, no: delay is just the minimum (then at least one thing starts moving) but it's possible that the two animations do not overlap at all, which means duration is somewhat meaningless. ("The duration is 20 seconds but things only move in the first and last second.") And remember, we need to combine these values in a way that makes it possible to reverse, loop, and interrupt the composite animation later.
So to conclude, I surmise that:
stable-frame-1 ==> transition(t, stable-frame-1, stable-frame-2) ==> stable-frame-2
Think of how Keynote animates elements between two slides with relatively
little specification. Something like that would be a great starter, particularly
for folks new to Elm .. and could go a long way too. Does that appeal?