stride3d / stride3d/stride

Stride.UI styling and control structure overhaul proposal

Open
#2,499 5 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
C#
Stars
7.8k
Forks
1.2k
Avg merge
2d 17h
Merged PRs (30d)
49

Description

Overview

The current style and creation of controls in the UI has a number of limitations and issues which this proposal aims to solve.
This is a large proposal, and a long read, sorry in advanced!

Current system

A brief (?) overview of the current systems so we are all sort of up to speed.

UIElements define texture and color properties to use when they are drawn.

There are ElementRenderer classes which handle drawing the elements.
Users can create their own ElementRenderer class and register it with the UIRenderFeature, either for a type, or for a specific element. To register for a type requires making an additional IElementRendererFactory class.

UILibraries allow for configuring UIElements, either individually (like setting up the textures for a Slider), or groups of UIElements (like a group of buttons). Then use these pre-made sets of elements in UIPages. Like with Prefabs, when the source pre-made element changes in a UILibrary, the changes are propagated to all their usage locations in UIPages.

The intended workflow as I understand it is to use UILibraries to set up elements, including the default controls, like Slider, and Button. Which you would then use in your UIPage instead of the default controls directly. And if you needed some special or more advanced rendering of a control. You would create an ElementRenderer for it, and then in code, register it for the element.

Limitations

The current UI has a number of limitations and missing features.

  1. It is difficult to significantly change the look of a control.
    1. Requires a C# developer.
    2. Requires manually handling layout.
    3. Requires manually finding and registering the renderer with specific elements.
    4. Slow iteration time.
  2. It is prohibitive to make custom controls, requiring a minimum of 3 classes (if it has rendering), and clunky registering. And the API for drawing is clunky.
  3. Supporting pseudo states requires defining at least one texture property for each state.
  4. Difficult to make and style controls that are composed of other controls, as you have to define them in code.
  5. Doesn't support rounded corners, requiring the use textures, slowing down prototyping and iteration, along with development type.
  6. Limited 3D support.

Proposed changes

These are my proposed changes that address the listed limitations, and expands the flexibility of the UI system in Stride.

Styling

Add a StyleContainer class, and add a property for it to UIElement.
A StyleContainer is a collection of selector sets, with each selector set having a collection of property name and value pairs associated with it.

Selectors match the UIElement which the StyleContainer is defined on (only supports pseudo states), and elements that are provided by a virtual GetStylableElements() method in UIElement. By default no elements are returned.

Selectors in a set match in order against the ancestors of an element, up to the element that defines the style. Meaning that you can do a TypeSelector followed by a NameSelector, and it will only match elements with the specified name and with an ancestor of the specified type. This is much like CSS selectors if you are familiar.

When a selector set matches a UIElement, it the values from the name-value pairs associated with the selector set will be applied to the matched UIElement.

Reason
Allows the UI to easily support different styles for pseudo states, and controls, while not requiring defining properties for each pseudo state.
Lets controls that have internal elements still be styled in a UILibrary.

Alternatives
An alternative is to create a full CSS like stylesheet system. I decided against this option because creating a performant robust stylesheet is difficult and time consuming. And the main benefit is already covered by UILibraries, as they allow you to create reusable consistently styled elements. Where you can change the style in a central location and it is propagated to all usage locations.
So adding them would duplicate functionality and step on the toes of UILibraries. And UILibrary workflow is smoother and better suited for games than creating stylesheets anyway.

Pseudo states

Add a PseudoStates property to UIElement. A PseudoStates class is a collection of the current pseudo states of the UIElement, defined as strings. Any UIElement derived class can define its own pseudo states.

The main use for pseudo states is for changing styling.

A UIElement adds or removes string pseudo states to and from the PseudoStates class property as the element's state changes. For example, when a ToggleButton is toggled on it would add a :active pseudo state, and remove it when toggled off.

Reason
Allows any UIElement derived class to add any pseudo states to themselves that they need, and easily style them.
Allows the implementation for the pseudo state selector and usage to be very simple and straight forward, along with updating the styling when a pseudo state is added or removed..

Alternatives
An alternative is to allow using a new [PseudoState] attribute on bool and enum properties in a UIElement to denote them as pseudo states. And have have a selector check for the value based on the property name it is assigned.
The downside of this is that the code is significantly more complex, that what is effectively, a list of strings. And requires the use of reflection to get the value of the properties to check them. Though it can be optimized, it still comes with increased code complexity, and has heavy initialization costs.

UIElement Render(..) method

Add Render(..) method to UIElement, which can be overridden to handle the rendering of a UIElement.
As an example usage, Border would override Render to handle drawing the background and border of itself.

Reason
Allows rendering to be self-contained, and would make it easier to create an easier to use API for drawing/rendering.
Required for the other parts of this proposal to function.

Template elements

Add TemplatedUIElement and ElementTemplate classes, both inheriting from UIElement (names subject to change).
TemplatedUIElement would be the new default base class for control elements (Button, Slider, etc.). And it allows for referencing a ElementTemplate element in a UILibrary, and instantiating it as its children when created. If no template is set, it would try to default to a template in a UILibrary set in a new property in the GameSettings.

The ElementTemplate is used to define the elements and configuration of elements used by a TemplatedUIElement and is only meant to be used as a root element inside of UILibrary. It has a type field for specifying the type of TemplatedUIElement it is intended to be used for.

TempaltedUIElement overrides GetStylableElements() and returns all elements from the template it uses. In this way, all child elements can be styled without having to make a new template.

Control elements no longer define texture properties for 'parts' (like the Slider has Thumb, and TrackBackground, etc.). Instead, they are UIElements defined in templates.

Remove the current ElementRenderer system, as it would be redundant with the above systems.

Reason
Allows for easier reuse and combining of controls to create more complex controls.
Allows for deeper customization of the look of a control without requiring the use of C#, making it easier for designers to create the UI.
Allows for adding any type of custom 'part' to the UI without having to hardcode the texture for it.
Allows supporting textures or vector UI.

Brushes

Add a IBrush interface, along with implementing classes SolidColorBrush, TextureBrush, GradientBrush, and MaterialBrush which can be used as properties on UIElements to define how it draws, in place of individual texture and color, properties.

Reason
Is similar to C# WPF style libraries, along with design tools like Figma, so should feel familiar to both programmers and designers.
Allows 'implicitly' supporting colors, and textures, along with any custom shading the user may want, without having to explicitly add properties for them.
Follows common design pattern used in Stride.

Alternatives
Keep defining individual properties for both texture and color, and material. The downside is that it requires defining and manually handling considerably more properties. And isn't extensible if we or a user want to add some additional type of brush in the future.

Initial vector rendering.

Add support for procedural rounded corners through procedural mesh generation.

Reason
Allows the UI to immediately be able to support a range of common UI styles/designs without requiring textures.
Allows nicer UI to be mocked up faster, while not having to leave the editor to create and import textures.
Potentially lower memory footprint due to less textures.
Not difficult to support the Depth property of UIElements.

Alternatives

  1. No rounded corners. Without any rounded corners even prototype UIs look pretty ugly, and it is not usable in any game.
  2. Use material/shader based method with SDFs instead of mesh generation. This is a valid option and I have not quite looked deep enough in to it to know what the performance tradeoffs would be. Though makes it harder to support 3D UI.

Models

Add a Model property along with a toggle to some UI element that handle rendering (Like Border), to support taking a 3D model and rendering that instead of using a rectangle mesh.

Additional properties like either to receive shadows, receive lighting, and cast shadows, and the rotation of the model, could be added as well.

Reason
Allows the use of 3D models in the UI to create cool and unique UIs.
Allows the creating even more immersive diegetic UIs.
Expands on the existing 3D support of the UI.
Would be fairly easy to add, as the UI rendering system already supports rendering 3D geometry. (Not sure the difficulty of support lighting and shadows though).

Alternatives

  1. Dedicated elements for Models. Like a BorderModel element. Downside is that it would require the user to create new templates for every control. But does keep the existing elements 'cleaner'.
  2. A ModelBrush containing Model, Material, and other relevant properties. It can just be used with of any IBrush property. Would require handling it as a 'special case' compared to all other brushes. This is a very valid alternative imo, and might be a better option actually?

Conclusion

These changes allow for artists and designers to quickly and iteratively design and style control, without the need for a C# developer. Makes it easier for developers to add new controls as they won't need to define and manage as many properties. And makes it easier to support more dynamic UI. All while working with the general workflows of Stride.
This would fully replace the current ElementRenderer system.

It also doesn't have to be all done at once, it can be done one section at a time for the most part.
And should be noted that some of these are breaking changes, and there just isn't a way around them nicely.
Please share your thoughts and let me know if parts need clarification!

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the existing UIElement, ElementRenderer, UILibrary, and UIPage systems described in the proposal; no file or test entry point is named. Break the proposal into separately scoped decisions before implementation. Done would require an agreed scope and corresponding UI changes, but this issue does not define a single completion check.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
frontend, game-dev
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
18/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.