ppy / osu-framework

A game framework written with osu! in mind.
MIT License
1.67k stars 420 forks source link

osu! markup language #3056

Open smoogipoo opened 4 years ago

smoogipoo commented 4 years ago

For previous discussion, see: https://github.com/ppy/osu-framework/issues/30 .

What is OML

OML is a markup language that is added onto existing Drawable hierarchies to extend the layout and styling.

OML layouts have access to the entire hierarchy of the Drawable they extend, and are able to:

  1. Change the styling of the Drawable, including re-positioning and re-sizing of it and any of its children.
  2. Add new children to Drawables which support the operation.
  3. Respond to events and apply other styles to the Drawable.

What OML isn't

OML does not provide capabilities to generate executable code or members that are usable by code. Members generated by the code are usable by layouts, however members generated by the layout are only usable within layouts.

Implementation

Although it is highly preferable, the implementation does not need to exactly match the given examples as long as the general structure and syntax remains legible.

OML is best implemented iteratively as new functionality is required. A minimum viable implementation satisfies the following capabilities:

  1. Adjusting members of a Drawable, with no limitations on what the OML layout has access to.
  2. Adding children and building full hierarchies.
  3. Listening to events and applying style changes.
  4. Adding transforms to a Drawable.

Example:

class Cursor : CompositeDrawable
{
    public bool CursorRotate { get;set; }

    protected readonly Container RotateableContent;

    protected SampleChannel HoverSample;

    public Cursor()
    {
        Size = new Vector2(100);

        InternalChild = RotateableContent = new Container
        {
            RelativeSizeAxes = Axes.Both
        };
    }

    [BackgroundDepndencyLoader]
    private void load(ISampleStore samples)
    {
        HoverSample = samples.Get("test-sample");
    }

    protected override bool OnHover(HoverEvent e)
    {
        if (CursorRotate)
            RotateableContent.RotateTo(180);
    }

    protected override void OnHoverLost(HoverLostEvent e)
    {
        if (CursorRotate)
            RotateableContent.RotateTo(0);
    }
}
# The name of the component which this layout extends.
Extends: Cursor

# Initialisation properties are applied to the component within the background load context (i.e. inside load()).
Position: (50, 50) # Local properties.
RotateableContent: # Properties of children.
  Position: (50, 50)
  # Any property of Drawables can be replaced by the layout.
  RelativeSizeAxes: None
  # Setting "Children" replaces all existing children with a new hierarchy.
  Children:
    # The key of each entry in this array defines the type of child.
    - Box:
        Name: ScaleableBox # New children can also be named for later use (e.g. transforms).
        RelativeSizeAxes: Both
        # Transforms can be added in the same way as "OnHover" below.

# Each input event can only be defined once.
OnHover:
  # Any member can be uniquely acted upon.
  HoverSample: Play # Samples can be played or stopped.
  ScaleableBox: # Drawable members can also be acted upon.
    # Transforms are added in sequences.
    Transforms:
      # Each array item represents a transform sequence.
      -
        - Scale: # Each entry defines a transform of the sequence, with the key as the type of transform.
          From: (1, 1)
          To: (2, 2)
          Duration: 100
        # Each transform in the array starts after the previous transform. This is equivalent to Drawable.Transform1().Then().Transform2().
        - Fade:
            # Transforms may exclude the "From" entry, in which case the current value at the time of application is used.
            To: 0.5
            Duration: 100
    # A separate item defines a new transform sequence. All transform sequences start at the same time.
      -
        - Colour:
            From: Red
            To: Blue
        # A "Delay" entry defines a delay transform for the sequence. This is equivalent to Drawable.Delay(...).Transform1().
        # It may be placed at the start of the sequence if desired.
        - Delay: 100
        - Colour:
            To: Green
  RotateableContent:
    # There is no restriction to what properties of members can be altered within events.
    Children:
      - Box:
          RelativeSizeAxes: Both
          Colour: Yellow
luaneko commented 4 years ago

So in this sense, OML is like a styling language.

Is it possible to define and use custom samples by name, or is it restricted to predefined samples like HoverSample?

  Children:
    # New members of the hierarchy can be created.

Does this replace all children or can it only add? If latter, is it possible to remove existing children?

peppy commented 4 years ago

It's feasible for the user to add a Sample object to their hierarchy then play it.

Removing existing children should not be possible - it should replace.

smoogipoo commented 4 years ago

For the time being, use the hierarchy for samples. I think a further iteration in the layout should allow for retrieval of samples/textures from within the layout.

sr229 commented 3 years ago

My only issue is schema validation, and YAML itself in this proposal.

We should provide at least a schema to allow editors like VS Code to validate the YAML to catch issues way before they are even ran, so designers would be aware of type issues that would otherwise error out in compile. Another thing is YAML itself. I myself deal with cloud-native apps (Kubernetes in particular), and YAMLs are pain to write and can get ugly pretty quick (see how Kubelet configurations can get really fugly when dealing with runtime configs). It would be nice if OML could be extended to other known standards like the famous XAML standard on Uno/WinUI3.