Transition system seems to be working pretty well so far in 0.5.0, but just like CSS, transitions only cover some motion-related scenarios, not all.
Animations, unlike transitions, are not state-dependent; that is, a behavior might trigger an animation to turn on/off, but its keyframes are predefined, unlike transitions which always act between the previous/current value. Animations can also loop and reverse.
With the existing behavior and state system, it's completely possible to have an animation force itself on top and even correctly revert to the previous state (e.g. some non-animated hover state) afterward. What we don't have right now is a system for actually defining the animations. Declaring them all inline seems too awkward; it's fine for individual per-property transitions, but animations act on multiple properties and may transition through many states (keyframes).
So clearly we need a Keyframe type, and it should probably work with the duck-typing system as long as it's annotated. But animations are typically understood as a presentation concern, not data. It's better not to have to define them in code.
Some possible ideas, and I haven't decided on the best one yet:
- A new JSON format following a similar strategy to that of sprite definitions. Presumably these JSONs would go in the
views directory (or a separate animations directory?) and require a specific extension like .animation.json in case more future "view data" formats are added. This would require new logic in the AssetRegistry but, if done carefully, could be designed in a way to reduce the effort needed for future such formats.
- Specified inline in the
.sml file like templates. Requires support in the DOM-related components and may involve changes to the view node system (tends to be expensive and one-off).
- Specified in external/included
.sml or other markup files. This would also be an opportunity to support standalone templates (.sml files with only includable templates, no views themselves) which was considered but not yet implemented.
The big question is JSON vs. SML-style markup. Not all of the arguments for markup hold up for this particular use case.
Comparison of the same animation in a hypothetical JSON vs. SML syntax:
JSON
{
"animations": {
"bar": {
"loop": true,
"reverse": false,
"keyframes": {
"0%": {
"opacity": 0.1,
"transform": "scale: 0",
},
"20%": {
"transform": "scale: 0.25",
},
"50%": {
"opacity" 0.5,
"transform": "scale: 0.75",
"transform-easing": "EaseOutQuad",
},
"100%": {
"opacity" 1,
"transform": "scale: 1",
},
}
}
}
}
StarML
<foo animate="bar" />
<animation name="bar" loop="true" reverse="false" default-easing="Linear">
<keyframe progress="0%" opacity="0.1" transform="scale: 0" />
<keyframe progress="20%" transform="scale: 0.25" />
<keyframe progress="50%" opacity="0.5" transform="scale: 0.75" transform-easing="EaseOutQuad" />
<keyframe progress="100%" opacity="1" transform="scale: 1" />
</animation>
<animation name="baz" ... />
The case for JSON:
- Animations are genuinely a hierarchical dictionary of properties and values, i.e. keyframes at the first level and animation properties at the second level. They don't really benefit from further "flattening" as the view hierarchy itself does.
- It's one less thing for users to learn, since any user of StardewUI is pretty much guaranteed to already understand how JSON works.
- No extra work required for syntax highlighting, and can add a JSON schema for code completion (though this is limited, since views differ in their animatable properties; schema can only hold the properties common to all views).
- It is probably the least effort to actually implement.
- There are opportunities for adding more hierarchical levels, e.g. expanding
transform properties as separate attributes, or animating fields that are actually hierarchical. This would be much more difficult to implement using child nodes in markup.
The case against JSON/for markup:
- The JSON is still more verbose, even though the difference is nowhere near as stark as a view hierarchy.
- The markup version makes it easier to see what's happening property by property - although writing the JSON in a more compact (single-line per keyframe) format might get close.
- It's easier to understand what the literal values mean in a markup context since users already write them in regular views. Also, having to deal with value conversion makes the JSON version not completely trivial.
- There's some ambiguity over what can be serialized with static types (most
IView properties) vs. what has to use a JObject. We might have to use a JObject for the entire keyframe even though most of the time, most of its contents could be statically typed.
- The obvious, logical choice for importing JSON animations would be to reference them as attributes, e.g.
<foo animate={@Mods/Abc/Animations/Bar}, but this makes it tricky to switch animations or turn them on and off. So we would either have to go with some less-intuitive implementation for referencing the animations (global registry by name? per mod? what is the scoping?) or finally get around to implementing "dynamic assets" where the asset path is itself a binding value.
Unfortunately a lot of tradeoffs to work through. I kind of lean toward JSON at the moment but it's definitely not a slam dunk.
Transition system seems to be working pretty well so far in 0.5.0, but just like CSS, transitions only cover some motion-related scenarios, not all.
Animations, unlike transitions, are not state-dependent; that is, a behavior might trigger an animation to turn on/off, but its keyframes are predefined, unlike transitions which always act between the previous/current value. Animations can also loop and reverse.
With the existing behavior and state system, it's completely possible to have an animation force itself on top and even correctly revert to the previous state (e.g. some non-animated hover state) afterward. What we don't have right now is a system for actually defining the animations. Declaring them all inline seems too awkward; it's fine for individual per-property transitions, but animations act on multiple properties and may transition through many states (keyframes).
So clearly we need a
Keyframetype, and it should probably work with the duck-typing system as long as it's annotated. But animations are typically understood as a presentation concern, not data. It's better not to have to define them in code.Some possible ideas, and I haven't decided on the best one yet:
viewsdirectory (or a separateanimationsdirectory?) and require a specific extension like.animation.jsonin case more future "view data" formats are added. This would require new logic in theAssetRegistrybut, if done carefully, could be designed in a way to reduce the effort needed for future such formats..smlfile like templates. Requires support in the DOM-related components and may involve changes to the view node system (tends to be expensive and one-off)..smlor other markup files. This would also be an opportunity to support standalone templates (.smlfiles with only includable templates, no views themselves) which was considered but not yet implemented.The big question is JSON vs. SML-style markup. Not all of the arguments for markup hold up for this particular use case.
Comparison of the same animation in a hypothetical JSON vs. SML syntax:
JSON
{ "animations": { "bar": { "loop": true, "reverse": false, "keyframes": { "0%": { "opacity": 0.1, "transform": "scale: 0", }, "20%": { "transform": "scale: 0.25", }, "50%": { "opacity" 0.5, "transform": "scale: 0.75", "transform-easing": "EaseOutQuad", }, "100%": { "opacity" 1, "transform": "scale: 1", }, } } } }StarML
The case for JSON:
transformproperties as separate attributes, or animating fields that are actually hierarchical. This would be much more difficult to implement using child nodes in markup.The case against JSON/for markup:
IViewproperties) vs. what has to use aJObject. We might have to use aJObjectfor the entire keyframe even though most of the time, most of its contents could be statically typed.<foo animate={@Mods/Abc/Animations/Bar}, but this makes it tricky to switch animations or turn them on and off. So we would either have to go with some less-intuitive implementation for referencing the animations (global registry by name? per mod? what is the scoping?) or finally get around to implementing "dynamic assets" where the asset path is itself a binding value.Unfortunately a lot of tradeoffs to work through. I kind of lean toward JSON at the moment but it's definitely not a slam dunk.