# Zustand, Datensätze und Bindungen

## Zustand

Alle `property`s eines Dialogs, Screens oder einer Komponente sind veränderbarer Zustand, auch mit Standardwert. Die App setzt sie beim Aufbau und danach über generierte Setter:

```qml
component PriceTab : Item {
    property bool selected
    Text {
        id: labelText
        color: selected ? theme.interactiveColor : theme.textColor
    }
    Rect { id: bar; visible: selected; size: labelText.width + 30, 6 }
}
```

```cpp
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, `parent` oder `place`); im Beispiel also auch die Breite von `bar`, weil sie von `labelText` abhängt,
- Argumente von Komponenten-Instanzen (`TestNav { up: focus > 0 }` ruft den Setter der Instanz),
- `repeat` und `if`, 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:

```qml
component SaveButton : Rect {
    state mode: idle, busy, done           // der erste Wert ist der Start
    color: theme.mainColor

    Text { id: label; text: tr("ui_save", "Speichern") }
    Svg { id: 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 Typ `string`, 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 class` im Struct und ein typsicherer Setter (`button->setMode(SaveButton::Mode::busy)`), dazu das Feld `mode` mit 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 mit `mode: busy`.
- **`when bedingung { … }`:** Solange die Bedingung gilt, gelten die Properties darin, für das Element selbst und (als `id.name`) für Elemente in ihm. Die Bedingung ist ein beliebiger Ausdruck: ein Zustand, `root.pressed`, ein Argument. Gelten mehrere `when` für dieselbe Property, gewinnt das spätere.
- **Werte außerhalb von `when`:** Eine Property, die ein `when` ändert, braucht einen Wert außerhalb davon; nur `visible`, `opacity`, `scale` und `rotation` haben eine Vorgabe. `width`/`height` ändern den passenden Teil von `size`. Einmalige Properties (`place`, `name`, `onTap`, `source`, `pressSink`, `spin`) ändern sich nicht.
- **Umsetzung:** Der Parser schreibt jede Property eines `when` in einen bedingten Wert um (`label.text: mode == "busy" ? … : …`). Danach läuft alles über die normalen Bindungen, in beiden Modi gleich; Strukturänderungen gehen weiter über `if`.
- **Gedrückt:** `when root.pressed` bzw. `when id.pressed` am Element selbst ändert dessen Inhalt (Farbe, Text, Deckkraft), nicht seine Geometrie (die würde ein `pressSink` zurücksetzen).

## Animationen (`animate`, `enter`)

Eigenschaften, die sich ändern, gleiten zum neuen Wert, statt zu springen; Elemente, die erscheinen, können sich einblenden:

```qml
Rect {
    id: 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 {
        id: 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, ein `when` oder `root.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 ein `if` gerade aufgebaut hat.
- **`exit: eigenschaft ende ms [easing], …`:** Nimmt ein `if` oder `repeat` das 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 im `if`/`repeat`.
- **Ohne eigenen Wert:** Setzt das Element `opacity`, `scale`, `rotation` oder `unfold` nicht selbst, gleitet bei `enter:` sein eigener Wert herein (`Popover { enter: unfold 0 300 linear }` klappt auf, `enter: opacity 0 300` blendet ein).
- **`loop: eigenschaft von bis ms [easing] [pause ms], …`:** pendelt endlos: in `ms` von `von` nach `bis`, ebenso zurück, dann `pause` ms Ruhe (Standard-Easing `inOutQuad`). `loop: rotation -6 6 800` schwingt einen Pfeil, `loop: scale 1 0.95 2000 pause 1000` lä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 eines `animate`-, `enter`- oder `exit`-Eintrags): Jede Änderung wartet so lange, bevor sie losläuft; der Wert bleibt so lange stehen. So laufen Schritte nacheinander, auch über mehrere Elemente:

  ```qml
  Rect { id: sidebar; x: menu ? -width : 0; animate: x 250 inOutCubic }
  Rect { id: topNavigation; y: menu ? 60 : -60; animate: y 250 inOutCubic delay 250 }   // nach der Seitenleiste
  Scroll { id: list; y: menu ? 130 : 1060; animate: y 750 outBack delay 250 }            // fliegt herein, schwingt über
  ```
- **`when bedingung`** (am Ende eines `animate`-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:

  ```qml
  Rect { id: videoLayer; opacity: videoMode ? 1 : 0; animate: opacity 1000 inOutQuad when !videoMode }   // nur ausblenden
  Rect { id: sidebar; x: videoMode ? 0 : -width; animate: x 250 inOutCubic when animated }               // property bool animated
  ```
- **`via wert ms [easing]`** (nach Dauer und Easing eines `animate`-Eintrags): Eine Änderung läuft erst zu `wert` (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:** `contentY` eines `Scroll` ist eine Eigenschaft wie `y`: gebunden an den Zustand und mit `animate: contentY 600 inOutQuint` scrollt die Liste zu einem Abschnitt (die App setzt den Zielwert). Ein Wischen stoppt die Animation nicht, eine neue Animation das Wischen schon. In C++ scrollt `aura::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); `loop` ohne `color`. Easings: `linear`, `inOutQuad`, `inOutCubic`, `inOutQuint`, `outQuad`, `outBounce`, `outBack` und `inOutBack` (schwingen um etwa 10 % über, QML `Easing.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`/`enter` hängen beim Aufbau Übergänge an den Knoten (`aura::transition()`), die Zuweisungen dieser Eigenschaften laufen über `aura::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:

```qml
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 mit `size: breite, höhe`, wie an einer Instanz. `background: farbe` legt eine Farbe dahinter (z. B. `theme.mainColor` fü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. Ein `image` ist ein Dateiname wie bei `Image { source: … }`.
- **Prüfung:** `aurac` prü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, mit `test-`-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:

```qml
// 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 {
        id: productRows
        repeat line, i in lines {
            CartRow {
                id: 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 } }
}
```

```cpp
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`, `strings` und 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++ als `const CartLine&` bzw. `const std::vector<CartLine>&`. Im dynamischen Modus wandelt `fromValue()` 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:

```cpp
ui->setLines(lines); // update the record list
```

- **An Ort und Stelle:** Ein unsichtbarer Anker-Node (`objectName` `aura_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 (`i` in `onTap: 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 `if` oder `repeat` in 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 ein `lazy`-`repeat` Einträ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::Binding` mit Guard) und enden mit ihm; das gilt auch für `pressed`-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 `if` außerhalb von Wiederholungen baut für sich neu, auch in einem anderen `if`.
- **Kosten:** Ein `if` baut 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.
