Skip to content

Instantly share code, notes, and snippets.

@pervognsen
Created March 9, 2017 21:36
Show Gist options
  • Save pervognsen/279156b894c5d04ca73df7afc12a37ee to your computer and use it in GitHub Desktop.
Save pervognsen/279156b894c5d04ca73df7afc12a37ee to your computer and use it in GitHub Desktop.
Design decisions:
Composition model
Dataflow model
Layout model
Painting model
Styling model
Extension model
Paradigms:
Ad-hoc with few reusable parts (e.g. lots of old games).
Inheritance-oriented OO toolkits. Subclass if you want to override button click behavior. The darkest days of OO in early 90s.
Slots-and-signals OO toolkits. Flatter hierarchies, more composition and reuse. Button click event fires a delegate.
Document/symbolic model. Popularized by HTML and rarely copied. Less well known example: Mathematica Notebooks.
Immediate mode. Ideal for fully dynamic UIs, no persistent identity for widgets, no inversion of control, fully code driven.
Reactive UI toolkits. Born in the functional world. FB's React is a good mainstream example. Surprisingly close to immediate mode.
Where does Slate sit on this spectrum? Mixture of immediate mode/reactive, document model, slots-and-signals, in that order.
Composition model:
Emphasize orthogonal composability to maximize reuse.
A slot is a widget property into which you can plug another widget. SButton content is a slot rather than text/image.
A leaf widget has no slots. STextBlock, SImage, etc. Only need a few types of these.
Some composite widgets have a variable number of slots. SVerticalBox, SCanvas, etc. These are called panels.
Notation:
Can do everything by hand in C++ but there's also an embedded domain-specific language. Confusing at first, but powerful and concise!
Define initialization arguments for new widget type:
SLATE_BEGIN_ARGS( SImage )
: _Image( FCoreStyle::Get().GetDefaultBrush() )
, _ColorAndOpacity( FLinearColor::White )
, _OnMouseButtonDown()
{}
SLATE_ATTRIBUTE( const FSlateBrush*, Image )
SLATE_ATTRIBUTE( FSlateColor, ColorAndOpacity )
SLATE_EVENT( FPointerEventHandler, OnMouseButtonDown )
SLATE_END_ARGS()
Defines SImage::FArguments struct containing these initializer arguments.
Creating and initializing an SImage:
SNew(SImage)
.Image(InStyle->GetBrush("Visibility"))
.ToolTipText(LOCTEXT("EndpointListVisibilityColumnTooltip", "Visibility"))
SNew(SImage).Image(...).ToolTipText(...) means *(new SImage) <<= SImage::FArguments().Image(...).ToolTipText(...).
Populates SImage::FArguments with dot/operator chaining notation ala fluent interfaces.
Uses that FArguments to initialize a new SImage.
Creating and initializing an entire widget tree:
SNew(SVerticalBox)
+SVerticalBox::Slot()
[
SNew(SBox)
.HeightOverride(115.0f)
[
ListViewWidget.ToSharedRef()
]
]
+SVerticalBox::Slot()
.AutoHeight()
[
Toolbar
]
The [] operator adds the argument widget to the default slot in a composite widget (e.g. button content)
The + operator adds the argument widget to the next slot in a panel widget (e.g. vertical box).
That's pretty much all there is to it!
Dataflow model:
Emphasizes dynamic interfaces a la IMGUI and React.
Poll each frame (attribute delegates) rather than cache/invalidate.
Culls widgets to retain performance. If must cache/invalidate, do it coarsely (e.g. Blueprint graph).
Low level input (mouse events) routes function call to widget.
Widget event handler can change state directly or fire custom delegate (e.g. text field changed).
Direct data binding for UObjects with SProperty/SObjectPropertyEntryBox.
Layout model:
Two-pass model. Pretty similar to WPF model. Entirely code-driven. Can be completely custom per widget.
Compute desired size. Context insensitive (no knowledge of parent). Children computed/cached before parents.
Means that parent can compute its desired size in terms of children's without exp-time fixed-point iteration.
Once computed, arrange children given allotted geometry. Parents pass allotted geometry to children.
Painting model:
Traverse widget tree and paint to screen based on allotted geometry computed in layout pass.
Repainted each frame outside of exceptions (e.g. active window move/resize). Remember, dynamic interfaces.
Painting interface is immediate mode. OnPaint() and add FSlateDrawElement to draw list. Maps to batched RHI draw calls.
Could easily implement a high-performance 2D game in custom widget with just OnPaint() and OnKeyDown().
Styling model:
Data-driven control of shared style properties. Sizes, fonts, colors, brushes.
A style is just a key-value dictionary.
Built-in widgets query standard properties (text color, font).
A simple opt-in model. No orthogonal rule-based styling language a la CSS.
Extension model:
Want to be able to inject and extend functionality into existing UI elements. Menu items, toolbar items, etc.
Facilitated by orthogonal slot-based composition model.
Works for both programmer extension and user customization.
Extensible widgets expose named UI extension points.
Example: SMultiBoxWidget named blocks. Used for menu bars and tool bars.
Case studies:
Paper2D is implemented entirely as plugins.
Good self-contained example of almost every kind of widget and injection into existing UI.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment