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;
Workflow 1-1: Verarbeitung-Methoden Ansicht
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;
Workflow 1-2: Verarbeitungs-Methoden State
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:
MeasuringPhaseArrangementPhase
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.