The Ingredients
OrbTk
provides an interactive functional
reactive API. It depends on the rust
DCES
crate, that provides an Entity Component
System. Interaction with DCES
is managed via the Entity Component Manager
(ECM), a wrapper API, that transparently maps OrbTk
widgets
to ECM
entities and OrbTk
properties to ECM
components.
Widget view
graph TD; View-->Widget-Container_1; View-->Widget-Container_2; Widget-Container_1-->Child-Widget1_1; Widget-Container_1-->Child-Widget1_2; Widget-Container_1-->Child-Widget1_n; Widget-Container_2-->Child-Widget2_1; Widget-Container_2-->Child-Widget2_2; Widget-Container_2-->Child-Widget2_n;
When you construct an OrbTk application, you do essentially combine
widgets
. Widgets are the building blocks of user interfaces in
OrbTk. They are declarative, which means they define the structure of
an entity with associated components (its properties). Widgets are
dedicated to a specific task and should provide all needed visual
components inside your app that offers the ordered user interface (the
widget-tree
). Each widget-tree is stacked into a uniquely addressable
widget-container
.
Re-usability is a major demand in that area. To implement a useful UI element it is convenient to simple make up a tree of already available widgets that may consume any number of core widgets.
The given model is dynamically structured which offers you freedom:
- consume any number of predefined library offered widgets type
- implement your own, new, sophisticated widget type
Just to illustrate a simple case, where you need an FontIconBlock
in
your app. To make up such a widget-tree
, you either construct a new
widget MyFontsIconBlock
, that instantiates a child Container
, that
itself instantiates a TextBox
an a Button
child. Or as an easy
alternative, you take advantage of the library offered version
FontIconBlock.
You will find the source code of available library widgets inside workspace orbtk_widgets.
Widget trait
Each widget need to implement the Widget trait. It is generated by the
widget!()
macro.
A widget consists of a name (eg. Button
) that is bound to a list of
properties (eg. text: String
, background: Brush
or count: u32
). When the build()
method of a widget is called inside the
widget-tree, that widget is added to the Entity Component System with
a unique Entity
(index) that holds corresponding Components
(property
names). The struct of the widget-container serves as its builder pattern.
Widget Template
Each widget has to implement the Template
trait. The template defines
the default values of all assigned widget’s properties as well as its
structure. E.g. a Button consists of a Container widget, a StackPanel
widget and a TextBox widget.
Separating the view
as the descriptive nature of a widget tree from
the code that reacts and handles user input (it’s state
), is an
essential conceptual decision. It is key to enable the fast, flexible
and extendable structure of OrbTk.
Widget state
graph TD; State-->init; State-->update; State-->cleanup; update-->message; message-->layout; layout-->update_post_layout;
Widgets make use of traits, that come in handy to provide
interactivity. We call them the widget state
. Inside the state
routines, we declare the processing and controlling code dedicated to
a given task.
It is not required to define a state
for a widget. But if you
don’t, you cut of the possibility to adapt properties during
runtime. The view
of the widget will stay static.
When defining a state
of a widget, it inherits the values of its
associated properties (current values
), as well as the implemented
system. To gain access, each state has to derive or implement the
Default
and the AsAny
traits. You are free to implement
associated functions to the state
, that react on triggered events or
adapt current values. The properties
are stored via ECM. They are
organized in a tree (parent, children or level entities).
Systems
While widgets define and organize the data structure of an OrbTk
app, systems take care to handle the behavior on how to manipulate
the data.
- Layouts
- Events
- Behaviors
- Messages
Layouts
Layouts are addressing the problem, that each widget inside the UI needs individual placement. This requires a dynamic calculation of its space requirements coupled with is target specific positioning when interacting with the display device.
Why do we need layouts?
Well, lets take an obvious example that meets every modern application: You have to support multiple language variants! Changing the selected localization should be possible at runtime. We do expect, that each needed idiom inside our application will for sure differ in their label length, as well as the sizes of used glyphs in the selected fonts. What would happen, if we would size the entities statically? We would code e.g a button with a given size. How to react on content changes of its child entity (e.g. a label that is expected to be centered inside the button frame)?
Pugh, you as the programmer would need to adapt the GUI views for every supported language and react on pragmatically on any of this size changes. A nightmare! This is nonsense. We have to define and render the stuff the other way around!
Our solution
OrbTk
uses a layout
system. This system support the ability to
determine the natural size of the content elements. It allows a dynamic
approach to layout all entities inside the toolkit. No matter if the
application logic requires to add or subdivide other entities inside
the widget tree. Or if contents is changed through theme adaption or
user interaction: all involved entities are processed and resized as
needed, respecting their individual constraints.
Constraints are defined via entity properties that are
stored as components inside the DCES
. The concept follows a two
phase model. It will process the entity tree in two passes:
Measuring
passArrangement
pass
This is the reason, we call it a functional_reactive
Toolkit.
Measuring
The Measuring
pass allows us to determine the desired size of a
boxed
entity. A desired size is a structure, that holds the maximum
values for the width and height of the entity in question. This values
are stored inside DCES
. If computation recognizes a size change of
the desired size
, which means the stored and the current value of
its property differs, the dirty flag
is set. The measuring will
result in an update of the associated bound values inside the DECS
entity (structure desired size).
Arrangement
The Arrangement
is following in a separate run. It will trigger the
2D rendering task. This task walks down the element tree and consume
the bounds of each entry. A bound
describes the finalized
alignment position of an entity (height, width) and is stored inside
the DCES
. Computation tasks are only triggered, if the values of
a tree element have changed, which will be indicated via the dirty
flag. All referenced elements that are affected by this changed values
need to be rearranged. Their positions are recomputed with the
appropriate values inside the render buffer, since the active state
was marked dirty.
After the arrangement pass, the dirty flag is cleaned, which will omit any further computational needs. Once the state of an entity is marked as dirty again, the pass runs are triggered as desired.
Layout Methods
OrbTk
supports a number of dedicated layout methods, that are
designed to handle the specific demands of a given widget type:
- Absolute
- Fixed size
- Grid
- Padding
- Popup
- Stack
You can find the relevant code inside the orbtk_core
workspace. The methods are inside the sub-directory layout
.
Further information towards this methods are discussed in Chapter: Orbtk_core.
Events
- bottom-up
If the events traverse from a leaf entity up to the root entity.
- top-down
If the events traverse from the root entity down to the leaf entities.
Behaviours
Specialized event handling methods are available to reacts on signals. These currently include the event classes
- Mouse behaviors
- Selection behaviors
- Text behaviors
Signals may be fired from ether input devices (e.g. mouse, keyboard) or inside the functional logic (e.g. changing focus, text, etc).
Messages
An intelligent messaging infrastructure that instantiates subs. The
concept enables the toolkit to send and receive messages between the
linked entities (m senders -> n receivers). The implemented methods are thread save
.
Each widget may implement a message
Method in its state code. The
devoloper is free which method adapters he/she likes to incorpoerate
and how to process the encountered messages.
Framework Elements
The elements are organised as sub-modules inside the API sub-tree.