koenbok / Framer

Framer - Design Everything
https://framer.com
MIT License
5.84k stars 479 forks source link

Proposal: Make states deterministic #384

Open nvh opened 8 years ago

nvh commented 8 years ago

This is a spin off from #378, there was some discussion on it there, most notable @IanBellomy's lengthy reply.

We've decided to decouple the two issues and will continue the discussion here.

In the previous state API the order state changes occurred influenced the resulting position of the layer. Consider the following example:

background = new BackgroundLayer
layer = new Layer

layer.states.add
    right: 
        x: Align.right
    bottom:
        y: Align.bottom
    left: 
        x: Align.left
    top:
        y: Align.top

background.onClick ->
    layer.states.next()

(Also available here: http://share.framerjs.com/ff6vwjpn0wet/)

Because not all states define all properties, you can end up with some in-between state (try clicking quickly in the example). Therefor we would like to make states deterministic. That is: every state defines every property. If you omit a property during the definition of the state, we use the initial state as the default value for that property.

Examples of this can be found here: http://share.framerjs.com/kthdx2kmp00m/ The red layer shows deterministic states, but all derived from the initial states, this layer will always end up in one of the corners, but never reach the bottom right corner The blue layer has deterministic states, and defines the x and y for every state, resulting in the intended behaviour.

nvh commented 8 years ago

Just a thought, not sure if it's going somewhere: What if we don't make states themselves deterministic, but store the order in which they are applied, so we can still recreate the resulting state?

Possible problems I can see with this:

edwinvanrijkom commented 8 years ago

On making things better, plz don't overlook the default state (for currently that one gets created the moment the first custom state gets added, the default state's props being set to whatever values the properties are set to at that exact moment).

Now back to the swimming pool ;-)

nvh commented 8 years ago

Thanks! We're going to add an initial state that is exactly for that purpose. It will be created during construction of the layer

jordandobson commented 8 years ago

I think that makes a lot of sense and it's exactly what I would expect.

On Fri, Jul 22, 2016 at 3:26 AM Niels van Hoorn notifications@github.com wrote:

Just a thought, not sure if it's going somewhere: What if we don't make states themselves deterministic, but store the order in which they are applied, so we can still recreate the resulting state?

Possible problems I can see with this:

  • Switching states mid-animation
  • Properties being edited outside of states (like as the result of a drag)

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/koenbok/Framer/issues/384#issuecomment-234484387, or mute the thread https://github.com/notifications/unsubscribe-auth/AACbYupOyd3bkyVXb0tpSkd2nsKWVk3Iks5qYH60gaJpZM4JSiuS .

IanBellomy commented 8 years ago

If supporting comments is a priority, I wonder if there's much wiggle room. Entirely deterministic states aren't a huge drag really, so long as there are work arounds (like nested layers).

What if we don't make states themselves deterministic, but store the order in which they are applied, so we can still recreate the resulting state? (@nvh)

This sounds interesting. I may be in over my head at this point though—it smells like a trip into state-machine theory.

If I understand your proposal right, switching to a state would be similar to an animation call where a state's data serves as the target, e.g.

layer.animate(properties:stateObject)

Handily, this allows expressive multi-dimensional state spaces:

box = new Layer
box.states =
    selected:
        scale:1.5
    active:
        opacity:1       
    inactive:
        opacity:0.5

box.setState "active"
box.setState "selected" 

The first catch is that nextState() and previousState() are out the window in this situation. (Unless states have a group property or similar?)

The second catch is that there's no singular current state. Some additional methods may be necessary:

box.isInState(STATE_NAME) # returns Boolean
box.getCurrentStateValues() # returns object containing the combined properties of the state history.

Implementation of a method like isInState() smells a little tricky too. You can't just do an indexOf(STATE_NAME) on the history. At some point one state will (or should) 'overwrite' another.

For example, if the history looks like this:

["active","inactive","selected"]

The layer should be in states selected and inactive, but not active. I suspect that trying to infer this from state properties could result in ambiguous or undesirable behavior.

box = new Layer
box.states =
    selected:
        scale:1.5
    active:
        opacity:1       
    inactive:
        opacity:0.5
    hidden:
        scale:0 

box.setState "active"
box.setState "selected" 
box.setState "hidden"   # Because 'hidden' includes the same property (scale) as 'selected', would something like isInState("selected") return false? 

Though maybe you could pop the "hidden" state off the stack? (This sounds a little like how I remember hierarchical state machines working...)

edit:(I meant: Maybe situations like this wouldn't matter as much if you could just pop a state off the stack.)

box = new Layer
box.states =
    selected:
        scale:1.5
    active:
        opacity:1       
    inactive:
        opacity:0.5
    hidden:
        scale:0 
    revealed:
        scale:"Not 0? Depends on whether active of inactive?"

This last example almost begs a style sheet approach. Though, maybe a user could just manage this themselves?

box.hide = ->
    @states.revealed.scale = @scale
    @setState "hidden"

These are pretty common use cases to me, but I'm not sure what your user base is up to, or what kinds of things you'd like to help people to build.

These issues also sound like things I've seen in writings about state machines, but I'm unfamiliar with the literature. (I learned development on the streets!) Perhaps there are common solutions or approaches that you had in mind?