Die Bestandteile
OrbTk
stellt eine interactive functional reactive
API bereit. Es hängt dabei elementar vom Rust crate DCES
ab, welches
ein Entity Component System bereitstellt. Die Interaction mit DCES
wird vom
Entity Component Manager
(ECM) übernommen. Einem Wrapper API, das
OrbTk
widgets transparent in ECM
Enititäten und OrbTk
Attribute (properties) in
ECM
Komponenten (components) übersetzt und verwaltet.
DCES
ist wie OrbTk
selbst nativ in Rust geschrieben.
The 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;
Wenn Du eine OrbTK Anwendung erstellst kombinierst Du letztlich
widgets
. Widgets sind die Kern-Bausteine von Benutzer Schnittstellen
in OrbTK. Sie sind deklarativ, d.h. sie beschreiben die Struktur von
Entitäten mit deren zugeordneten Komponenten (deren properties). Sie
werden für die Umsetzung eine bestimmen Aufgabe verwendet und sollten
alle graphischen Elemente in einem geordneten Benugtzer Interface für
die Anwendung bereitstellen (der widget-tree
). Ein widget-tree wird
in einen eindeutig adressierbaren widget-container
eingebunden.
In diesem Zusammenhang ist die Wiederverwendbarkeit ein wesentliche Anforderung. Bei der Implementierung eines benötigten UI-Elements ist es legitim und einfach, wenn auf bereits existierende widgets zurückgegriffen werden kann. Es ist unerheblich, wenn dies ihrerseits auf eine beliebige Anzahl von core widgets zurückgreifen.
Das derzeitige Modell ist dynamisch strukturiert und bietet die Freiheit:
- verwende eine beliebige Anzahl von vorhandenen widget Typen aus der Bibliothek
- implementiere Deinen eigenen, neue, spezialisierten widget Typ
Illustrieren wir das mit folgendem simplen Beispiel, in dem Du in
Deiner Anwendung einen FontIconBlock
benötigst. Um einen solchen
widget-tree
zu erzeugen kannst Du entweder ein neues Widget
MyFontIconBlock
erstellen, das als Kind-Widget einen Container
verwendet, der seinerseits eine TextBox
und einen Button
einbindet. Oder aber Du greifst einfach auf die Bibliotheks-Version von
FontIconBlock. zu.
Du findest den Quellcode der verfügbaren widget Bibliothek im Workspace orbtk_widgets.
Widget trait
Ein widget muss zwingend ein Widget trait erzeugen. Hierzu hilft das Makro
widget!()
.
Ein widget besteht zunächst aus einem Namen (z.B. Button
) und
einer Liste von Eigenschaften die angebunden sind (z.B text: String
,
background: Brush
oder count: u32
). Wird die build()
Methode in
einem widget aufgerufen, wird dieses widget zusammen mit seinen
Komponenten im Entity Component System registriert. Diese
Registrierung weist ihm als Entity
einen eindeutigen Index-Wert zu.
Dieser Entity
werden nun die zugewiesenen Components
, als
Komponenten-Namen zugeordnet und ebenfalls im ECS gespeichert.
Der widget-container stellt hierbei die erforderliche builder Struktur.
Widget Template
Jedes widget muss zwingend das Template
trait implementieren. Das
template definiert neben dem Strukturaufbau auch die Standardwerte
der zugewiesenen widget Eigenschaften (seine properties).
Nehmen wir zum Beispiel einen Button, der sich aus einem Container
Widget, einem StackPanel Widget und einem TextBox Widget
zusammensetzt.
Ein ganz wesentlicher Konzeptbaustein von OrbTK ist dabei die
Trenneung eines views
, also der beschreibenden Natur eines
Widget-Baums, von den Methoden die auf Benutzereingaben in der GUI
reagieren und sie verarbeiten (der Widget state
). Diese Trennung ist
der Schlüssel für eine schnelle, flexible und erweiterbare Struktur
innerhalb von OrbTk.
Widget state
graph TD; State-->init; State-->update; State-->cleanup; update-->message; message-->layout; layout-->update_post_layout;
Widgets verwenden traits, die erst eine interaktive Verarbeitung
ermöglichen. Wie bezeichnen sie den Widget state
. Innerhalb der
state
Methoden definieren wir den Verarbeitungs- und
Kontroll-Quellcode, der an eine eindeutige Aufgabe geknüpft ist.
Es ist nicht erforderlich, einen state
für ein Widget zu
definieren. Existiert kein state
zu einem Widget beschneidest Du
damit aber die Möglichkeit eigenschaften während der Laufzeit
programmatisch zu verändern. Der view
diese Widgets bleibt damit
statisch.
Definierst du einen Widget state
, ererbt dieser neben den
implementierten Systemen auch die Werte der zugewiesenen Eigenschaften
(current values
of properties). Um den programmatischen Zugriff auf
die Werte zu erhalten, muss jeder state die Default
oder AsAny
traits erzeugen, bzw. ableiten (via derive
macro). Du kannst für
einen state
beliebige assoziierte Funktionen (methods
) erzeugen,
die sowohl auf Ereignisse der zugeordneten Systeme reagieren
(z.B. messages
, events
) oder auch die aktuellen Werte der
Eigenschaften verändern können. Die Eigenschaften (properties
) sind
im ECM gespeichert, deren Aufbau sich an der Baum Struktur orientiert
(parent, children or level entities).
Systems
Während Widgets die data structure einer OrbTk
Application
definiert und organisiert, verwenden wir systems um das Verhalten
bei der Bearbeitung zu steuern.
- Layouts
- Events
- Behaviors
- Messages
Layouts
Layouts lösen das Problem, wo und wie Widgets innerhalb einer UI plaziert werden. Das erfordert die kontinuierliche Anpassung und dynamische Berechnung ihrer Größenanforderungen, verbunden mit der Plazierung des Ergebnisses zur Dastellung auf dem Ausgabegerät.
Warum brauchen wir Layouts?
Nun, betrachten wir ein eingängiges Beispiel, das in jeder modernen Applikation umgesetzt werden muss: Mehrere Sprachvarianten sind erforderlich! Und der Wechsel der gewählten Sprachvariante soll zur Laufzeit erfolgen. Wir können sicher davon ausgehen, das sich jeder verwendete Bezeichner für Felder und Beschreibungen in den jeweiligen Sprachen unterscheidet. Wortlängen und Glyphenbreiten in den Schriften sind anders. Natürlich ist ebenso die gewählte Schriftart bei der Berechnung der Größe zu berücksichtigen. Was würde passieren, wenn Du beispielsweise die Größe einer Entität statisch festlegst? Wir würden z.B. einen Button mit einer festen Größe kodieren. Wie reagierst Du nun auf Kontext-Veränderungen von untergeordneten Entitäten (childs)? Wie gehst Du damit um, dass sich z.B ein Button-Bezeichner, den der Anwender wahrscheinlich zentriert im Button Rahmen erwartet verändert?
Puh, Du als der Programmierer müsstest an alle möglichen GUI Darstellungen denken, programmatisch auf jede denkbare Spracheveränderung reagieren. Ein Alptraum! Nein, wir brauchen einen tragfähigeren Ansatz.
Unsere Lösung
OrbTk
verwendet ein layout
System. Dieses System unterstützen die
Möglichkeit, die Größe einer Entität anhand der natürlichen
Dimensionen des Inhalts aufzubereiten. Damit ist es im Toolkit möglich
den gesammten Entitätenbaum im Layout dynamisch anzupassen. Ändert
sich die Applikationslogik und damit die Notwendigkeit einzelne
Entitäten hinzuzufügen, zu verändern oder auszublenden wird dies für
den gesamten Baum in einem dynamischen Layout Prozess umgesetzt. Dabei
werden die individuellen Vorgaben der einzelnen Entitäten
berücksichtigt (constaints
).
Die individuellen Vorgaben der Entitäten werden über Eigenschaften
(properties
) als Komponenten im DCES
gespeichert (components
).
Das Konzept folgt einem zwei Phasen Modell. Ein Layout wird daher auch in
zwei Arbeitsschritten verarbeitet:
Measuring
PhaseArrangement
Phase
Measuring
Die Measuring
Phase erlaubt uns, die gewünschte Größe einer
boxed
Entität zu berechnet (desired size). Die gewünschte Größe
ist eine Struktur, die die maximalen Werte für Breite und Höhe einer
Entität angibt. Diese Werte werden innerhalb des DCES
persistent
gespeichert. Wenn die Verarbeitung eine Wertänderung der gewünschten
Größe feststellt (die gespeicherte und die aktuelle Größe
unterscheiden sich), wird die Kennzeichnung dirty
in der Struktur aktualisiert.
Arrangement
Die Plazierung (Arranging
) erfolgt in einem weiteren separaten
Schritt. Der Vorgang arbeitet den Baum der angesprochenen Elemente in
einer Schleife ab. Dabei verwendet er die bounds es jeweiligen
Elements. Ein bound beschreibt die finalisierte Position der
Ausrichtung des Elements (Höhe, Breite) und speichert diese im DECS
.
Ein Verarbeitungs-Prozess wird nur dann initiiert, wenn ein Element
innerhalb des Baums eine neue Anordnung erzwingt. Alle Elemente werden
nur dann mit den neuen Positionen im Ausgabe-Puffer (render buffer
) neu
angeordnet, wenn ihr aktiver Status die als dirty
gekennzeichnet ist.
Layout Methods
OrbTk
unterstützt unterschiedliche Layout Methoden. Dies sind darauf
optimiert, spezifische Anforderungen der unterschiedlichen
Widget-Typen zu berücksichtigen:
- Absolute
- Fixed size
- Grid
- Padding
- Popup
- Stack
Du findest deren Quellcode im Workspace orbtk_core
im Unterverzeichnis layout
.
Weitere Informationen zu diesen Methoden werden im
Kapitel: Orbtk Core besprochen.
Events
- bottom-up
Ein Ereignis wandert bei der Verarbeitung vom Auftreten am Blatt des
Enitätenbaums (leaf entity
) zum Stamm (root entity
). Also von
Unten nach Oben - oder von Aussen nach Innen.
- top-down
Ein Ereignis wandert bei der Verarbeitung vom Auftreten am Stamm
(root entity
) zu den Blätter des Enitätenbaums (leaf entity
). Also
von Oben nach Unten - oder von Innen nach Außen.
Behaviours
Es existieren diffenzierte Methoden für die Bearbeitung logisch gruppierter Ereignisse. Hierzu zählen derzeit die Ereignis-Klassen
- Mouse Behaviors
- Selection Behaviors
- Text Behaviors
Die Ereignisse können sowohl von Eingabe-Geräten (z.B. Tastatur, Maus) aber auch aus der funktionalen Logik heraus erzeugt werden (z.B. durch Fokus Änderung, Textanpassungen, etc.)
Messages
Über das Konzept von MessageAdaptern können Nachrichten zwischen beliebigen
Sender- und Empfänger-Widget gesendet und ausgelesen werden. Die
verwendeten Methoden sind thread save
.
Jedes Widget kann im State code eine message
Methode
definieren. Hier ist der Entwickler frei, welche MessageAdapter
berücksichtigt werden sollen. Die Verarbeitungslogik für ausgelesende
Nachrichten ist somit wahlfrei.
Framework Elements
Alle Elemente des ObtTk
Frameworks sind als Sub-Module innerhalb des API organisiert.