Zustand, Datensätze und Bindungen
Zustand
Alle propertys eines Dialogs, Screens oder einer Komponente sind veränderbarer Zustand, auch mit Standardwert. Die App setzt sie beim Aufbau und danach über generierte Setter:
component PriceTab : Item {
property bool selected
Text labelText {
color: selected ? theme.interactiveColor : theme.textColor
}
Rect bar { visible: selected; size: labelText.width + 30, 6 }
}
tab->setSelected(i == index);
Was einer Änderung folgt:
- jede Anweisungsgruppe eines Elements, die den Zustand nennt,
- jede Gruppe, die sich auf ein so betroffenes Element bezieht (über seine ID,
parentoderplace); im Beispiel also auch die Breite vonbar, weil sie vonlabelTextabhängt, - Argumente von Komponenten-Instanzen (
TestNav { up: focus > 0 }ruft den Setter der Instanz), repeatundif, deren Liste oder Bedingung den Zustand nennt (Neuaufbau, siehe unten),- das Layout (
Column/Row/Grid), in dem ein geändertes Element liegt.
Gleicher Wert: Ein Setter mit dem Wert, den der Zustand schon hat, tut nichts (Datensätze und Listen werden mit == verglichen). Die App darf also z. B. die Systeminformationen jede Sekunde setzen; neu gebaut wird nur, was sich geändert hat.
Was nicht folgt: einmalige Properties (name, source, onTap, pressSink, spin) und Konstanten (const). Eine neu ausgewertete Gruppe setzt auch ihre Geometrie neu; was die App selbst bewegt (z. B. den Scroll-Griff), bekommt deshalb keine Lage in der .aura-Datei.
Zustände (state) und when
Ein Zustand mit festen Werten (wie QML states), dazu Blöcke, die Properties ändern, solange eine Bedingung gilt:
component SaveButton : Rect {
state mode: idle, busy, done // der erste Wert ist der Start
color: theme.mainColor
Text label { text: tr("ui_save", "Speichern") }
Svg spinner { source: "icon_spinner.svg"; visible: false; spin: 1000 }
when mode == busy {
opacity: 0.5 // das Element mit dem when
// ein Element darin, über seine ID
label.text: tr("ui_saving", "Speichert …")
spinner.visible: true
}
when root.pressed { color: theme.mainColorDark }
}
state name: a, b, c: ein Zustand vom Typstring, der einen der Werte hat. In Bedingungen stehen die Werte ohne Anführungszeichen (mode == busy); ein Tippfehler (mode == bsy) bricht den Build ab. Mehrere Zustände pro Element sind möglich.- In C++: ein
enum classim Struct und ein typsicherer Setter (button->setMode(SaveButton::Mode::busy)), dazu das Feldmodemit dem Namen des Werts. Bei Dialogen und Screens steht der Zustand am Ende der Args, mit dem ersten Wert vorbelegt (args.view = "thanks"). Eine Instanz setzt ihn mitmode: busy. when bedingung { … }: Solange die Bedingung gilt, gelten die Properties darin, für das Element selbst und (alsid.name) für Elemente in ihm. Die Bedingung ist ein beliebiger Ausdruck: ein Zustand,root.pressed, ein Argument. Gelten mehrerewhenfür dieselbe Property, gewinnt das spätere.- Werte außerhalb von
when: Eine Property, die einwhenändert, braucht einen Wert außerhalb davon; nurvisible,opacity,scaleundrotationhaben eine Vorgabe.width/heightändern den passenden Teil vonsize. Einmalige Properties (place,name,onTap,source,pressSink,spin) ändern sich nicht. - Umsetzung: Der Parser schreibt jede Property eines
whenin einen bedingten Wert um (label.text: mode == "busy" ? … : …). Danach läuft alles über die normalen Bindungen, in beiden Modi gleich; Strukturänderungen gehen weiter überif. - Gedrückt:
when root.pressedbzw.when id.pressedam Element selbst ändert dessen Inhalt (Farbe, Text, Deckkraft), nicht seine Geometrie (die würde einpressSinkzurücksetzen).
Animationen (animate, enter)
Eigenschaften, die sich ändern, gleiten zum neuen Wert, statt zu springen; Elemente, die erscheinen, können sich einblenden:
Rect panel {
opacity: 1
// Eigenschaft, ms, Easing (ohne: outQuad)
animate: opacity 200, color 150 linear
when mode == busy { opacity: 0.5; color: theme.lightGrey }
}
if view == thanks {
Rect thanks {
enter: opacity 0 300 // beim Erscheinen von 0 zum Wert
exit: opacity 0 300 // beim Abbau zu 0, dann weg
}
}
-
animate: eigenschaft ms [easing], …: Jede Änderung des Werts gleitet, ob durch einen Setter der App, einwhenoderroot.pressed. Eine neue Änderung mitten in einer Animation startet dort, wo der Wert gerade ist. Der erste Wert beim Aufbau wird direkt gesetzt. -
enter: eigenschaft start ms [easing], …: Der erste Wert gleitet vom Startwert aus, z. B. ein Element, das einifgerade aufgebaut hat. -
exit: eigenschaft ende ms [easing], …: Nimmt einifoderrepeatdas Element weg, gleitet die Eigenschaft erst zum Endwert, danach verschwindet das Element. Solange nimmt es keine Taps an; das neue Element liegt darunter (Überblenden). Gilt für Elemente direkt imif/repeat. -
Ohne eigenen Wert: Setzt das Element
opacity,scale,rotationoderunfoldnicht selbst, gleitet beienter:sein eigener Wert herein (Popover { enter: unfold 0 300 linear }klappt auf,enter: opacity 0 300blendet ein). -
loop: eigenschaft von bis ms [easing] [pause ms], …: pendelt endlos: inmsvonvonnachbis, ebenso zurück, dannpausems Ruhe (Standard-EasinginOutQuad).loop: rotation -6 6 800schwingt einen Pfeil,loop: scale 1 0.95 2000 pause 1000lässt ein Symbol pulsieren. Ohne Animator bleibt der Wert stehen. Endlose Animationen (loop,spin) halten das Neuzeichnen nur in Gang, solange ihr Element zu sehen ist (sichtbar und nicht durchsichtig bis zur Wurzel). -
delay ms(am Ende einesanimate-,enter- oderexit-Eintrags): Jede Änderung wartet so lange, bevor sie losläuft; der Wert bleibt so lange stehen. So laufen Schritte nacheinander, auch über mehrere Elemente:qmlRect sidebar { x: menu ? -width : 0; animate: x 250 inOutCubic } Rect topNavigation { y: menu ? 60 : -60; animate: y 250 inOutCubic delay 250 } // nach der Seitenleiste Scroll list { y: menu ? 130 : 1060; animate: y 750 outBack delay 250 } // fliegt herein, schwingt über -
when bedingung(am Ende einesanimate-Eintrags): Die Änderung gleitet nur, wenn die Bedingung in dem Moment gilt, sonst ist der Wert sofort da. So läuft ein Übergang nur in eine Richtung, und die App kann für eine Änderung die Animation abschalten:qmlRect videoLayer { opacity: videoMode ? 1 : 0; animate: opacity 1000 inOutQuad when !videoMode } // nur ausblenden Rect sidebar { x: videoMode ? 0 : -width; animate: x 250 inOutCubic when animated } // property bool animated -
via wert ms [easing](nach Dauer und Easing einesanimate-Eintrags): Eine Änderung läuft erst zuwert(in der Dauer des Eintrags), dann weiter zum neuen Wert. So fliegt etwas über sein Ziel hinaus und kommt zurück:animate: y 750 inOutCubic via 60 - 50 250 inOutCubic when animated && !videoMode. -
onSettled: signal(jedes Element): Eine Animation des Elements (animate:) ist an ihrem Wert angekommen, auch wenn er sofort da war (nicht beim ersten Wert). Für das, was danach passieren soll, etwa den Player stoppen, nachdem das Video ausgeblendet ist. -
Scrollen:
contentYeinesScrollist eine Eigenschaft wiey: gebunden an den Zustand und mitanimate: contentY 600 inOutQuintscrollt die Liste zu einem Abschnitt (die App setzt den Zielwert). Ein Wischen stoppt die Animation nicht, eine neue Animation das Wischen schon. In C++ scrolltaura::scrollTo(scroll, y, animator)mit einer an die Entfernung angepassten Dauer und einem Überschwingen am oberen Rand. -
Eigenschaften:
x,y,opacity,scale,rotation,color(Rect, Text),unfold(Popover),contentY(Scroll);loopohnecolor. Easings:linear,inOutQuad,inOutCubic,inOutQuint,outQuad,outBounce,outBackundinOutBack(schwingen um etwa 10 % über, QMLEasing.OutBack). -
Ohne Animator (Screenshots,
aura-view --render, Pixeltests) steht sofort der Endwert da. In der VS-Code-Vorschau laufen die Animationen mit Play/Pause. -
Umsetzung:
animate/enterhängen beim Aufbau Übergänge an den Knoten (aura::transition()), die Zuweisungen dieser Eigenschaften laufen überaura::animateTo()/animateColor(). Beide Modi gleich.
Previews
Eine Preview zeigt einen Dialog, Screen oder eine Komponente mit vorbelegten Werten in der Vorschau (VS Code, aura-view), ähnlich #Preview in SwiftUI:
preview "Busy" for SaveButton {
mode: busy
}
preview "Zwei Produkte" for ShoppingCart {
lines: [
{ description: "test-Notebook", quantity: 12,
unitPrice: "0,29 €", linePrice: "3,48 €" },
{ description: "test-Pencil", quantity: 1,
unitPrice: "9,99 €", linePrice: "9,99 €" }
]
}
- Werte: Properties und Zustände des Ziels (Zustandswerte ohne Anführungszeichen). Was eine Preview nicht nennt, ist leer (
"",0,false, keine Einträge); Komponenten behalten ihre Vorgaben. Platzhalter (test-<name>) gibt es nur in „Standard“. - Größe: Eine Komponente, die ihre Größe vom Elternelement bekommt (
size: parent.width, …,height: parent.height), bekommt sie in der Preview mitsize: breite, höhe, wie an einer Instanz.background: farbelegt eine Farbe dahinter (z. B.theme.mainColorfür die weißen Header-Buttons). Beides nur in Previews von Komponenten. - Literale: Nur hier gibt es Datensätze als
{ feld: wert, … }; fehlende Felder sind leer. Einimageist ein Dateiname wie beiImage { source: … }. - Prüfung:
auracprüft Ziel, Namen und Zustandswerte; erzeugt wird nichts. Die App sieht Previews nie. - In der Vorschau: Die Previews eines Elements stehen im Dropdown „Preview“, „Standard“ zeigt es mit Platzhaltern. Zustände und
bool-Properties des gezeigten Elements lassen sich dort live umschalten. - Welche Komponenten: Datenzeilen und -zellen (
CartRow,PriceRow,MenuTile, …) und Elemente mit Varianten (BackButton,HeaderButton,SaveButton, …) haben Previews, mittest--Daten. Rahmen und Strukturelemente (DialogFrame,Flyout,Ripple) nicht, sie sieht man im Dialog.
Datensätze
Daten der App, die aus mehreren Werten bestehen, beschreibt ein record. Eine Liste davon ersetzt die Schleife, in der die App früher je Eintrag eine Komponente gebaut hat:
// cart_screen.aura
record CartLine {
string description
string discount
int quantity
string unitPrice
string linePrice
}
Screen ShoppingCart {
property CartLine[] lines
signal edit(int index)
signal remove(int index)
Column productRows {
repeat line, i in lines {
CartRow row {
index: i
line: line
onEdit: edit(i)
onRemove: remove(i)
}
}
}
}
component CartRow : Rect {
property int index
property CartLine line
signal edit
signal remove
Text { text: line.description }
if line.discount != "" { Text { text: line.discount } }
}
std::vector<views::CartLine> lines{
{"Notebook", "", 2, "3.00", "6.00"}
};
auto ui = views::ShoppingCart::build(frame, ctx, {lines});
ui->edit = [](double index) { /* handle the selected row */ };
- Felder:
real,int,bool,string,color,image,stringsund Datensätze bzw.Datensatz[], die vorher definiert sind. Ein Bild im Datensatz (image preview,Image { image: line.preview }) ersetzt den Platzhalter, in den die App früher ein Bild hängte; kommt es später (die Fotos werden noch dekodiert), setzt die App die Liste neu und nur diese Zeile wird neu gebaut. - In C++: ein Struct im Namespace der UIs (
views::CartLine) mit den Feldern in Quelltext-Reihenfolge, also mit{...}zu füllen. - Herkunft: Datensätze kommen immer von der App. Datensatz-Literale für Standardwerte oder
const-Deklarationen gibt es nicht. - In Signalen:
signal chosen(CartLine line)bzw.signal all(CartLine[] lines)übergeben Datensätze und Listen, in C++ alsconst CartLine&bzw.const std::vector<CartLine>&. Im dynamischen Modus wandeltfromValue()sie zurück. - Verhalten pro Eintrag: Ein Datensatz trägt keine Funktionen. Die Komponente meldet ein Signal mit dem Index oder dem Datensatz, die App entscheidet, was zu tun ist.
Neuaufbau von repeat und if
Nennt die Liste eines repeat oder die Bedingung eines if Zustand, baut eine Änderung den Block neu:
ui->setLines(lines); // update the record list
- An Ort und Stelle: Ein unsichtbarer Anker-Node (
objectNameaura_block) markiert den Platz unter den Geschwistern. Die neuen Nodes kommen direkt hinter ihn, Zeichenreihenfolge und Layout bleiben also richtig. - Einträge einer Wiederholung: Ändert sich die Liste, vergleicht der Block die Einträge nach Position mit der letzten Liste (Datensätze mit
operator==). Gleiche Einträge behalten ihre Nodes und Bindungen; nur geänderte, neue und weggefallene werden gebaut bzw. entfernt. Ein angehängter Eintrag baut also nur sich selbst, eine geänderte Menge nur ihre Zeile. Der Vergleich geht über die Position, weil die Schleifenvariablen (iinonTap: remove(i)) in den Einträgen stecken. Ein Eintrag am Anfang einzufügen baut daher alle folgenden neu. - Zustand in den Einträgen: Ändert sich Zustand, den ein
ifoderrepeatin den Einträgen nennt, baut die Wiederholung alle Einträge neu. Zustand in gewöhnlichen Properties der Einträge wird wie überall nur neu ausgewertet. - IDs: Die Felder im Struct (Vektoren, Zeiger, Komponenten-Structs) zeigen nach dem Setter auf die aktuellen Nodes, in Listenreihenfolge. Was die App an neu gebaute Nodes hängen muss, hängt sie nach dem Setter an; behaltene Einträge haben ihres noch.
ui->onRebuilt: läuft, nachdem ein Block neu gebaut wurde oder einlazy-repeatEinträge gebaut oder abgebaut hat (nicht beim ersten Aufbau). Hier hängt die App ihre Zusätze an neue Nodes, wenn der Neuaufbau nicht von ihrem eigenen Setter kam.- Alte Nodes nimmt der Block aus dem Baum und löscht sie erst beim nächsten Neuaufbau. Ein Tap-Handler, dessen Signal den Neuaufbau auslöst, läuft also nicht auf gelöschtem Speicher.
- Bindungen der Nodes im Block gehören zu ihrem Durchlauf (
aura::Bindingmit Guard) und enden mit ihm; das gilt auch fürpressed-Gruppen. - Verschachtelt: In einer Wiederholung baut immer die äußerste Wiederholung als Ganzes neu. Sie folgt dann auch dem Zustand der Listen und Bedingungen in ihr. Ein
ifaußerhalb von Wiederholungen baut für sich neu, auch in einem anderenif. - Kosten: Ein
ifbaut seinen Zweig ganz neu, eine Wiederholung nur die geänderten Einträge (siehe oben).
Lebensdauer
Die Abhängigkeiten (aura::Bindings) hängen am Wurzel-Node, das Struct verweist nur schwach darauf. Ein Setter nach dem Schließen tut nichts.
docs/aura.md · Markdown herunterladen