Skip to content

Variable asset bindings #90

@focustense

Description

@focustense

A number of cases have come up where a mod's sprites are managed by Stardew UI, but for various reasons it's not practical or not ideal to use simple {@Path/To/Asset} references.

One recent case was Star Control's tabs; since the tabs have many other properties (selections, transforms, etc.) it means the tab data needs to be a view model. Having more than a few tabs like this means the tab model is much easier to manage in a list/repeater than by having separate properties and elements for each tab; and eventually it became linked to a generic pager which had to use a repeater by definition.

But Star Control is just one of many examples and there are other cases even in Stardew UI's own example gallery.

The two theoretical workarounds are:

(a) Have the mod load the sprite directly and expose it as a property. This is rough for a lot of reasons: mods don't have access to Sprite, so they have to use their own type. StardewUI won't even honor content loads for sprite paths when the target type isn't Sprite anyway. And it's an awkward dance of trying to decide whether to use Game1.content.Load vs. IContentHelper.Load and how to handle exceptions therein. What actually ends up happening is that the mod only loads the texture, and specifies the SourceRect manually, duplicating this information in both places.

(b) Don't put the sprite directly in the model, and instead have an enum, string, number, etc. that subsequently becomes the parameter on a *switch element in the view. This is what Star Control is doing. However, it looks very silly, same as putting a switch statement inside a foreach loop in code and casing on the loop variable. Each branch is going to get hit exactly once, so it simply wastes resources to do it that way when a binding could work - and moreover, *switch requires multiple levels of hierarchy meaning we add unnecessary layers to the view just to get this behavior.

A much better answer here is to simply have a binding type that resolves to an asset name, instead of actually being an asset name.

In other words, the model looks like this:

public class FooViewModel
{
    public string ImageAssetName { get; set; }
}

and the view looks something like:

<image sprite={@<ImageAssetName} />

or possibly the other way around:

<image sprite={<@ImageAssetName} />

The rationale here being that < is the syntax for "input binding" (direction modifier) and the combination of the < and @ together essentially force the parser to understand it as a model binding that resolves to an asset name, as opposed to a literal asset name. In practice, the parser/lexer would probably just look for those specific tokens, since other directions make no sense for an asset.

Not the most intuitive thing ever, but I think it would be easy to get used to, and better than defining an entirely new attribute flavor or doing something bizarre like @@ or forcing double braces or anything else that's wildly different from existing grammar.

The hard part is probably not going to be the parser, it's going to be combining the two behaviors on the binding side, since asset bindings and model bindings are totally divergent. One specific area that is likely to trip things up is that model bindings do not have an "implied output type" whereas asset bindings do, so adding asset-typed behavior to the model-binding side (AttributeBinding etc.) might require not only an extra layer for assets but changes to how the binding types themselves are derived.

Anyway, while it's not going to be trivial to implement, it solves a problem that has been a little frustrating since early on, without causing any breaking changes in the process.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions