Slate Architecture

Unreal Engine 4.9
Slate用户界面架构
On this page:
  • How to Read This Page
  • Motivation
  • Core Tenets
  • Polling Data Flow and Delegates
    • Attributes and Arguments
  • Performance Considerations
  • Invalidation vs Polling
  • Child Slots
  • Widget Roles
  • Layout
    • Pass 1: Cache Desired Size
    • Pass 2: ArrangeChildren
  • Drawing Slate : OnPaint
  • Anatomy of a SWidget
  • Composition
  • Declarative Syntax

How to Read This Page

Core ideas guiding Slate design are found here. They are found in no particular order. There is no rigid structure or grand theory here; it is just a collection of principles that arise from UI tinkering experience.

You may find it helpful to re-read this page from time to time as you gain experience with Slate.

Motivation

The motivations for Slate arise from observations about off-the-shelf UI solutions available at the time. Some follow here:

  • Building up UIs from widgets is already easy in most toolkits. What is hard:

    • UI Design and iteration.

    • Controlling the flow of data: usually thought of as binding between widgets (view) and underlying data (model).

    • Learning a foreign language for describing UIs.

  • IMGUI : Immediate Mode Graphics User Interface

    • Pros:

      • Programmers like that UI description is "close" to the code; easy to get at the data.

      • Invalidation is usually a non-issue; just poll data directly.

      • Easy to procedurally build interfaces.

    • Cons:

      • Adding animation and styling is harder.

      • UI description is imperative code, so no chance to make it data-driven.

  • Desired Slate Characteristics:

    • Easy access to model's code and data.

    • Support procedural UI generation.

    • UI description should be hard to screw up.

    • Must support animation and styling.

Core Tenets

Design for developer efficiency where possible. A programmer's time is expensive; CPUs are fast and cheap.

  • Avoid opaque caches and duplicated state. Historically, UIs cache state and require explicit invalidation. Slate uses following approaches (from preferred to least preferred):

    1. Polling

    2. Transparent caches

    3. Opaque caches with low-grain invalidation

  • When UI structure is changing, prefer polling to notification. (When notification is necessary, prefer low-grain notifications to fine-grain notifications.)

  • Avoid feedback loops. E.g. all layout is computed from programmer settings; never rely on previous layout state.

    • Only exceptions are when UI state becomes the model; e.g. ScrollBars visualize UI state.

    • This is done for correctness and programmer sanity rather than performance.

  • Plan for the messy, ad-hoc UIs that require a ton of one-offs to work. Generalize these into nice systems later, when the use-cases are understood.

Polling Data Flow and Delegates

UIs visualize and manipulate Models. Slate uses delegates as a flexible conduit for widgets that need to read and write the Model's data. Slate widgets read the Model's data when they need to display it. When a user performs some actions, Slate widgets invoke the write delegate in order to modify the data.

Consider STextBlock - a Slate widget that displays text. The STextBlock must be told where to get the data to display. The data could be set statically. However, a more flexible way to accomplish this is a delegate (a function specified by the user). The STextBlock uses a delegate called Text for this purpose.

+------------+                         +--------------+
| STextBlock |                         |           {s}|
+------------+                         | Model Data   |
|            |       /--------\        +--------------+
|     o Text +<---=--+ReadData+---=----+  framerate   |
|            |       \--------/        +--------------+
+------------+                                               
[REGION:caption]The STextBlock reads the framerate as a string.
[/REGION]

Consider also that in the example above the Text is a string, while the framerate is most likely stored as a float or an integer. The use of delegates offers us the flexibility to perform the conversion whenever the value is read. This immediately conjures up performance concerns which are addressed in the Performance Considerations section below.

SEditableText is a Slate widget responsible for both input and output. Just like STextBlock, it uses the Text delegate for visualizing data. The user types some text into the editable text field, and when they hit enter, SEditableText invokes the OnTextChanged delegate. The assumption is that the programmer has attached the functionality appropriate for validating the input and mutating the Model's data to OnTextChanged.

+-----------------+                         +--------------+
| SEditableText   |                         |           {s}|
+-----------------+                         | Model Data   |
|                 |      /----------\       +--------------+
|         o Text  +<--=--+ ReadData +---=---+              |
|                 |      \----------/       |  item name   |
|                 |      /-----------\      |              |
| o OnTextChanged +---=--+ WriteData +--=-->+              |
|                 |      \-----------/      +--------------+
+-----------------+
[REGION:caption]The SEditable text reads the <code>item name</code>. When the user hits enter, the new text is sent to OnTextChanged where it will be validated and assigned to <code>item name</code> if appropriate.
[/REGION]

During the next frame, the SEditableText will read from the Model's data. In the example above, item name will have been mutated by the OnTextChanged delegate and will be read for visualization via the Text delegate.

Attributes and Arguments

Using a delegate is not always desirable. Depending on the use case, the arguments to Slate widgets may need to be constant values or functions. We encapsulate this notion via the TAttribute< T > class. An attribute can be set to a constant or to a delegate.

Performance Considerations

After reading the Polling Data Flow and Delegates section, one might have serious concerns about performance.

Consider the following observations:

  • UI complexity is bounded by the number of live widgets.

  • Scrolling content is virtualized whenever possible; this mostly avoids live widgets off-screen.

    • Large numbers of off-screen widgets can easily tank Slate performance.

  • Assumption: users with big screens have beefy machines to drive those screens; they can handle a large number of widgets.

Invalidation vs Polling

Sometimes polling is either not performant or functionally incorrect. This is often the case with non-trivial values that cannot be expressed as a combination of simpler, trivial values. We usually invalidate in scenarios where the structure of the Model changes drastically. It is then reasonable to scrap an existing UI and recreate it. However, doing so assumes state loss, so we should not do it unless necessary.

Invalidation is - as a rule - reserved for infrequent, low-granularity events.

Consider the example of the Blueprint Editor, which displays nodes on a graph. When an update is requested, all the Graph Panel widgets are cleared and re-created. This is preferable to fine-grain invalidation because it is simpler and more maintainable.

Child Slots

All Slate widgets store children in child slots. (As opposed to storing a plain array of child widgets.) Child slots always store a valid widget; by default they store the SNullWidget, which is a widget with no visualization or interaction. Each type of widget can declare its own type of child slot, which caters to its specific needs. Consider that SVerticalSlot arranges its children completely differently than an SCanvas, which is quite different from SUniformGridPanel. The Slots allow each type of panel to ask for a set of per-child settings that affect the arrangement of the children.

Widget Roles

Widgets come in three flavors.

  • Leaf Widgets - widgets with no child slots. e.g. STextBlock displays a piece of text. It has native knowledge of how to draw text.

  • Panels - widgets with a dynamic number of child slots. e.g. SVerticalBox arranges any number of children vertically given some layout rules.

  • Compound Widgets - widgets with a fixed number of explicitly named child slots. e.g. SButton has one slot called Content which contains any widgets inside the button.

Layout

Slate layout is accomplished in two passes. The two-pass breakdown is an optimization, but unfortunately not a transparent one.

  1. Pass 1: Cache Desired Size - the relevant functions are SWidget::CacheDesiredSize and SWidget::ComputeDesiredSize

  2. Pass 2: ArrangeChildren - the relevant function is SWidget::ArrangeChildren

In more detail:

Pass 1: Cache Desired Size

The goal of this pass is to figure out how much space each widget wants to occupy. Widgets with no children (i.e. leaf widgets) are asked to compute and cache their desired size based on their intrinsic properties. Widgets that combine other widgets (i.e. compound widgets and panels) use special logic to determine their desired size as a function of the size of their children. Note that each type of widget is only required to implement ComputeDesiredSize(); the caching and traversal logic are implemented by Slate. Slate guarantees that when ComputeDesiredSize() is called on a widget, its children have already computed and cached their desired size. Thus, this is a bottom-up pass.

In the below example, we examine a horizontal box that arranges two children - a piece of text and an image.

.    |<-----------22---------->|
.    +-------------------------+
.    |     Horizontal Box      |
.    +-------------------------+
.      +----=----+ +----=----+
.      |    ?    | |    ?    |
.      +----+----+ +----+----+
.           ^           ^
.           |           |
.        +--+           +--+
.        |                 |
.   +----+---------+  +----+--+
.   |STextBlock    |  |SImage |
.   +---------+----+  +-------+
.   |<---- 14----->|  |<--8-->|
[REGION:caption]
A horizontal box arranges two children - a text block and an image.
[/REGION]

An STextBlock widget would compute its desired size by measuring the string that it is displaying. The SImage widget would determine its size based on the image data it is showing. Assume that the text inside the textblock requires 14 slate units of space, and the image requires 8. The horizontal panel arranges widgets horizontally, and therefore requires 14 + 8 = 22 units of space.

Pass 2: ArrangeChildren

ArrangeChildren is a top-down pass. Slate begin at the top-level windows and asks each window to arrange its children based on the constraints provided by the programmers. When the space allotted for each child is known, Slate can recur and arrange the children's children. The recursion continues until all the children are arranged.

.     |<---Allotted Space 25--->|
.     +-----------------+-------+
.     |     Horizontal Box      |
.     +-------------------------+
.       +----=----+ +----=-----+
.       |Auto Size| |Fill Width|
.       +----+----+ +----+-----+
.            |           |
.         +--+           +--+
.         |                 |
.         v                 v
.     +----+-----+----------+---+
.     |STextBlock| SImage       |
.     +----------+--------------+
.     |<---14--->|<-----11----->|
[REGION:caption]
A horizontal panel arranges two children, a text block and an image.
[/REGION]

In the example above, the panel was allotted 25 units by its parent. The first slot indicates that it wants to use the desired size of the child, and is allotted 14 units of space. The second slot indicates that it wants to fill the available width, and is allotted the balance (11 units of space). Note that in the actual SHorizontalBox widget, the alignment of the SImage within its slot would be driven by the HAlign property, which can be Left, Center, Right, or Fill.

In practice, Slate never performs a full ArrangeChildren pass. Instead, this functionality is used to implement other functionality. Key examples are hit detection and painting.

Drawing Slate : OnPaint

During the paint pass, Slate iterates over all the visible widgets and produces a list of draw elements which will be consumed by the rendering system. This list is produced anew for every frame.

We begin at the top level windows and recur down the hierarchy, appending the draw elements of every widget to the draw list. Widgets tend to do two things during paint: they output actual draw elements or figure out where a child widget should exist and ask the child widget to paint itself. Thus, we can think of a simplified general-case OnPaint function as being:

.   // An arranged child is a widget and its allotted geometry
.   struct ArrangedChild
.   {.       Widget;
.       Geometry;
.   };
.
.   OutputElements OnPaint( AllottedGeometry )
.   {.       // Arrange all the children given our allotted geometry
.       Array<ArrangedChild> ArrangedChildren = ArrangeChildrenGiven( AllottedGeometry );
.
.       // Paint the children
.       for each ( Child in ArrangedChildren )
.       {.           OutputElements.Append( Child.Widget.OnPaint( Child.Geometry ) );
.       }
.
.       // Paint a border
.       OutputElements.Append( DrawBorder() );
.   }

Anatomy of a SWidget

The key functions that define an SWidget's behavior in Slate are:

  • ComputeDesiredSize() - responsible for desired size.

  • ArrangeChildren() - responsible for arrangement of children within the parent's allotted area.

  • OnPaint() - responsible for appearance.

  • Event handlers - these are of the form OnSomething. These are functions that may be invoked on your widget by Slate at various times.

Composition

Composition is the notion that any slot should be able to contain arbitrary widget content. This affords users of Slate a great deal of flexibility. Composition is used whenever possible in core Slate widgets.

If you are ever thinking that you want a widget to take a string argument to be used as a label, ask yourself if it would not be better off taking an SWidget instead.

Specific use-cases often demand that widgets contain children of a specific type, in which case composition requirements no longer apply. These should never be widgets in the core of slate, but rather domain-specific widgets that are not intended for reuse outside of that domain.

Declarative Syntax

We wanted Slate to be accessible directly from code. Experience dictated that we needed a declarative language for describing UI, but we also wanted it to be compile-time checked for binding to C++ functions.

The solution was to build a declarative UI-description language as a subset of C++.

Examples abound in the codebase.

UE4 Slate Architecture相关推荐

  1. UE4 Slate四 SlateUI如何做动画

    原创文章,转载请注明出处. 点击观看上一篇<UE4 Slate三 SlateUI代码讲解> 点击观看下一篇<UE4 Slate五 SlateUI如何自定义样式(Custom Styl ...

  2. UE4 Slate九 控件反射器Widget Reflector介绍

    原创文章,转载请注明出处. 点击观看上一篇<UE4 Slate八 SlateUI使用总结> 点击观看下一篇<UE4 Slate十 SlateViewer介绍> 虚幻引擎 Sla ...

  3. UE4 Slate独立引用程序(摘抄大象无形)

    UE4 Slate独立引用程序(摘抄大象无形) 简介 如何开始 BlankProgram 走的更远 预先准备 增加模块引用 添加头文件应用 修改Main函数为WinMain 添加LOCTEXT_NAM ...

  4. UE4 Slate UI

    UE4 Slate UI 使用<UE4 Slate创建独立窗口APP>的Demo学习使用简单的UE4 Slate UI 布局的使用 SOverlay:重叠布局,在z方向上布局控件. SHo ...

  5. UE4 Slate概述

    Slate用户界面架构 On this page: 概述 声明式语法 构成 风格 输入 输出 布局图元 用户驱动的布局 开发者工具 引擎访问 概述 Slate 是一种完全自定义的.平台无关的用户界面架 ...

  6. UE4 Hello Slate

    2015年8月30日0 文章目录 [隐藏] 1 准备工作 2 SlateWidget 3 HUD 4 最终结果 UE4中通常的游戏内逻辑使用UMG就可以了,当需要一些独特的功能时就会需要用到Slate ...

  7. Unreal4有哪些令你印象深刻拍案叫绝的设计

    作者:梧桐 链接:https://www.zhihu.com/question/42128016/answer/94670767 来源:知乎 著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...

  8. 虚幻4皮肤材质_虚幻周报20200721 | CJ就要开始啦~

    官方知乎号:虚幻引擎 搜集日期:20200713-20200719 整理编辑: 大钊,小辉辉,马古斯,小帅帅 声明:文档搜集来自网上,难免遗漏,请联系我们投稿和添加关注.该文档版权归整理编辑者共同所有 ...

  9. [UE4]UMG、HUI、Slate之间的区别

    原文: https://answers.unrealengine.com/questions/208916/umg-hud-slate.html HUD Canvas is something tha ...

最新文章

  1. 文巾解题 14. 最长公共前缀
  2. Go使用simple-json解析json数组字符串:以Harbor获取镜像tag为例
  3. AR# 58294 Zynq-7000 SoC: PS SPI 控制器文档升级
  4. c语言srand函数怎么用_C语言的main函数到底该怎么写
  5. 云计算时代,数据中心架构三层到大二层的演变
  6. xms跨平台基础框架 - 基于.netcore
  7. mysql.sock 111,错误2002(HY000):无法通过套接字’/var/run/mysqld/mysqld.sock’连接到本地MySQL服务器(111)...
  8. SPI通信原理---STM32F4--HAL
  9. spring mysql 连接池配置_SpringBoot数据库连接池常用配置
  10. Lync Server 2010标准版系列PART3:证书准备
  11. 【Flink】Flink Remote Shuffle 开源:面向流批一体与云原生的 Shuffle 服务
  12. 为什么会有jQuery、Dojo、Ext、Prototype、YUI、Zepto这么多JS包?
  13. 干货!flask登录注册token验证接口开发详解
  14. 高光谱和图像特征相融合的生菜病害识别
  15. 81页智慧城市-大数据决策与支撑平台解决方案
  16. 二级域名 免费+免备案
  17. 全球及中国汽车系统基础芯片(SBC)行业需求现状与发展战略规划研究报告2022年版
  18. 微信小程序傻瓜制作_零基础,傻瓜式制作微信小程序,3分钟完成不是问题!...
  19. 我的电脑中无法新建txt文本文档
  20. python 认证机构_利用Python爬了SIG官网BQB认证公司清单,我有一些重大发现..

热门文章

  1. idea里边创建类的时候和方法自动生成注释
  2. call stack是什么错误_Go语言(golang)的错误(error)处理的推荐方案
  3. VSCode运行Python教程
  4. vissim跟驰模型_MATLAB——基于元胞自动机的单向3车道模型
  5. fullgc触发条件_JVM的内存分配策略以及进入分代的条件
  6. vue给组件传html,如何将 html 模板作为道具传递给 Vue 组件
  7. 骂人的代码_楚河骚话不断粉丝求代码 罕见喷脏怒怼弹幕
  8. catia如何画花键_CATIA到底有多用呢~
  9. java安装后启动程序在哪_java – 有没有办法在安装后使用一些“帮助应用程序”立即启动应用程序?...
  10. win10系统,virtualBox导入centos7.3报错