# Die Sprache

## Datei

Ereignisse können auch Inline-Skripte ausführen. `scriptLanguage: "lua"` wählt Lua auf Dateiebene; ohne Angabe gilt JavaScript. Siehe [Inline-Skripte](inline-scripts.md).

Eine Datei enthält in beliebiger Zahl und Reihenfolge:

| Oberste Ebene | |
|---|---|
| `Dialog Name { ... }` | ein Dialog mit eigener Größe (`size`), die App öffnet den Rahmen |
| `Screen Name { ... }` | der Inhalt einer Seite; Geometrie und Seite kommen von der App |
| `component Name : Basistyp { ... }` | ein wiederverwendbares Element |
| `record Name { typ feld ... }` | ein Datentyp der App, z. B. eine Zeile einer Liste |

- **Kommentare:** `//` und `/* */`.
- **Trennung:** Anweisungen enden am Zeilenende oder mit `;`. Innerhalb von `()`, `[]` und `? :` darf man umbrechen.
- **Sichtbarkeit:** Komponenten und Datensätze gelten für alle Dateien desselben `aura_add_ui` (bzw. desselben Loader-Verzeichnisses mit Unterordnern). Die Dateinamen müssen eindeutig sein, auch über Ordner hinweg, weil der erzeugte C++-Code nach ihnen heißt.

## Elemente

```qml
Typ {
    id: elementName
    property: wert
    Kind { ... }
}
```

- **Typ:** ein eingebauter Typ (Tabelle unten), ein Typ der App (`aura-elements.json`, siehe unten) oder eine Komponente.
- **`id`:** Name, über den andere Ausdrücke das Element ansprechen und unter dem es im Struct für die App steht. Ohne `name` wird die ID auch zum `objectName`. Reserviert sind `root`, `parent`, `theme`, `screen`, `props` und `self`.
- **Kinder** liegen in der Zeichenreihenfolge über den vorherigen.

`id: button` benennt ein Element; die ID ist optional und ein Bezeichner ohne Anführungszeichen. Die bisherige Kind-Syntax `Rect button { ... }` bleibt gültig. Definitionsnamen stehen weiterhin im Kopf: `Dialog Hello`, `Screen Foo` und `component Foo : Rect` benennen Definitionen, unabhängig von einer optionalen ID der Wurzel.

```qml
Dialog Hello {
    id: helloDialog
    property int count
    Text {
        text: str(helloDialog.count) // expliziter Zugriff auf die Dialog-Property
    }
    Rect {
        width: 42
        Text { text: str(parent.width) + ": " + str(helloDialog.count) }
    }
}
```

`parent` bezeichnet immer das unmittelbar umgebende Element und sucht Properties nicht in weiteren Vorfahren. `parent.count` funktioniert nur, wenn der direkte Elternknoten `count` deklariert (etwa eine Dialog- oder Komponentenwurzel). Für die Dialog-Property in tiefer verschachtelten Kindern dient `helloDialog.count`.

| Typ | aura-ui | eigene Properties |
|---|---|---|
| `Item` | `Node` | – |
| `Rect` | `RectNode` | `color`, `radius`, `corners: topLeft, topRight, bottomRight, bottomLeft`, `border: breite, farbe`, `gradient: oben, unten` |
| `Text` | `TextNode` | `text`, `font: familie, größe[, gewicht]`, `weight`, `color`, `align: left\|center\|right[, top\|center\|bottom]`, `wrap: breite[, ausrichtung]`, `fit: true\|mindestgröße`, `lineHeight` (QML-Faktor), `letterSpacing` (px), `pixelSize`/`minimumPixelSize` (px, ohne `Context::fontSize`), `underline` |
| `Image` | `ImageNode` | `source: "name.png"` (`Context::image`) oder `image: wert` (ein Bild der App, Typ `image`), `fill: stretch\|crop\|fit\|pad`, `radius`, `corners` |
| `Svg` | `SvgNode` | `source: "name.svg"`, `fill: farbe` |
| `Scroll` | `ScrollNode` | `contentHeight`, `contentY`; mit `direction: horizontal` waagerecht: `contentWidth`, `contentX`. Steckt ein waagerechter in einem senkrechten, entscheidet die Richtung des Fingers |
| `QrCode` | `QrCodeNode` | `text` (Inhalt), `color` |
| `Html` | `HtmlNode` | `text` (HTML wie QML RichText), `font: familie, größe`, `color`, `lineHeight`, `margin` (documentMargin); ohne `height` so hoch wie der Text bei seiner Breite |
| `Column` | `ColumnNode` | `spacing`, `padding`, `align: left\|center\|right`, `justify`; stapelt die sichtbaren Kinder von oben und wird so hoch wie sie (siehe [Layouts](#layouts-column-row-grid)) |
| `Row` | `RowNode` | `spacing`, `padding`, `align: top\|center\|bottom`, `justify`, `wrap: true`, `direction: leftToRight\|rightToLeft`; reiht die sichtbaren Kinder von links auf und wird so breit wie sie, bzw. von seinem rechten Rand aus |
| `Grid` | `GridNode` | `columns`, `spacing`, `padding`, `cell: breite, höhe`; Zellen in der Größe des ersten sichtbaren Kindes (oder `cell`), wird so hoch wie die Zeilen |
| `Popover` | `PopoverNode` | `color`, `markerPos`, `unfold` (gezeigter Teil der Höhe, 0–1; `enter: unfold 0 300 linear` klappt es auf); Sprechblase mit Marker oben |
| `Ripple` | `RippleNode` | `color` (grau), `opacity` (0,4), `size` (200 × 200); Rückmeldung seines Elternelements: sitzt beim Drücken am Finger und pulsiert (1 ↔ 1,1, je 500 ms), wächst nach dem Tippen in 500 ms auf das Vierfache, erst dann läuft das `onTap` des Elternelements (ohne Animator sofort). Als letztes Kind, damit es oben liegt; das Elternelement schneidet seine Kinder ab (`clip`) |
| `TextField` | `TextFieldNode` (`aura/ui/text_input.h`) | `text` (Startwert, siehe [Texteingabe](#texteingabe)), `font`, `weight`, `color`, `align`, `letterSpacing`, `pixelSize`, `placeholder`, `placeholderColor` (#cccccc), `masked` (`*` je Zeichen), `maxLength` (Zeichen), `characters` (die erlaubten Zeichen, z. B. `"0123456789*#"`), `focus: true`, `onChange: signal`, `onAccept: signal`; eine Zeile mit Schreibmarke, schneidet ab und rollt mit der Schreibmarke |
| `Keyboard` | `KeyboardNode` (`aura/ui/text_input.h`) | `layout: "DE"` (CZ, DE, DK, EN, FI, FR, NL, NO, PL, SE, SI, SK, auch klein; `"alphanum"`: 800 × 240, Ziffern und Großbuchstaben, Backspace, OK, Abbrechen), `color`, `radius`, `border`, `keyColor`, `textColor`, `accentColor` (Umschalttasten), `okColor`, `cancelColor`, `cancelText`, `okKey: false`, `onOk: signal`, `onCancel: signal`; Bildschirmtastatur, 960 × 300, auf die eigene Größe skaliert |
| `Button` | `ButtonNode` (`aura/ui/controls.h`) | `text`, `font`, `weight`, `textColor` (weiß), `color` (#404040), `pressedColor` (sonst `color` dunkler), `radius` (8), `border`; Taste mit Text in der Mitte, Standardgröße 200 × 60 (siehe [Bausteine](#bausteine)) |
| `CheckBox` | `CheckBoxNode` | `text`, `font`, `textColor`, `color` (Kästchen), `radius`, `checked`, `onToggle: signal`; ein Tipp schaltet um |
| `Switch` | `SwitchNode` | wie `CheckBox`, dazu `trackColor` (aus); Schiebeschalter, `color` (an) |
| `RadioButton` | `RadioButtonNode` | wie `CheckBox`, dazu `group`; ein Tipp wählt ihn und nimmt die Wahl der anderen derselben Gruppe weg (ohne `group`: seiner Geschwister). `onToggle` meldet nur der neu gewählte |
| `ComboBox` | `ComboBoxNode` | `options` (Liste von Texten), `index` (-1: keiner), `placeholder`, `font`, `textColor`, `color`, `radius`, `border`, `listColor`, `listTextColor`, `selectedColor`, `visibleRows` (6), `onSelect: signal`; Auswahlliste: ein Tipp öffnet die Liste darunter über der ganzen Szene (oder darüber, wenn unten kein Platz ist), ein Tipp daneben schließt sie |
| `Slider` | `SliderNode` | `value`, `from` (0), `to` (1), `step`, `color`, `trackColor`, `onChange: signal`; Ziehen oder Tippen auf die Bahn |
| `ProgressBar` | `ProgressBarNode` | `value`, `from` (0), `to` (1), `color`, `trackColor`, `radius` (halbe Höhe) |
| `Rive` | `RiveNode` | `source: "goodbye.riv"` (`Context::file`); spielt die Standard-State-Machine des Standard-Artboards (sonst seine erste Animation) endlos über den Animator, eingepasst wie `PreserveAspectFit` |
| `WebView` | `aura::webview::WebViewNode` (libaura-webview) | `url`, `script` (JavaScript, läuft in jeder Seite vor deren eigenen Skripten); Chromium über CEF, bedienbar per Touch, WebChannel zu C++ (`aura.call`, `aura.on`). Nur in Programmen mit `AURA_WITH_WEBVIEW`, siehe [aura-webview.md](aura-webview.md) |

**Elementtypen der App:** Was eine App selbst zeichnet, beschreibt eine `aura-elements.json` neben oder über den `.aura`-Dateien, die es benutzen:

Das folgende Beispiel beschreibt einen von der Anwendung bereitgestellten `BadgeNode`, keinen eingebauten AURA-Elementtyp.

```json
{
	"Badge": {
		"type": "BadgeNode",
		"header": "widgets/badge_node.h",
		"properties": {
			"color": {"type": "color"},
			"label": {"type": "string", "set": "setLabel()"}
		}
	}
}
```

- **Properties:** `type` ist `number`, `string`, `bool` oder `color`. Der erzeugte Code setzt das Member gleichen Namens bzw. das aus `set`; endet `set` auf `()`, ruft er die Funktion auf.
- **Wer sie liest:** `aurac` (die nächste Datei über jeder `.aura`-Datei, oder `--elements`), `aura_add_ui` (als Abhängigkeit), der Loader (alle im Verzeichnis), `aura-view` und die VS-Code-Erweiterung (Prüfung, Vervollständigung).
- **Dynamischer Modus:** Der Loader kennt damit die Properties, erzeugen und setzen kann er den Typ erst, wenn das Programm ihn registriert hat (`aura::ui::registerElementType`). Sonst meldet er „not part of this program“ und nimmt die kompilierte Fassung.

Alle Elemente haben außerdem:

| Property | Bedeutung |
|---|---|
| `name` | `objectName` (Fernsteuerung, Logs); ohne `name` gilt die ID |
| `x`, `y`, `width`, `height`, `size: w, h` | Geometrie |
| `place: ...` | Anker, siehe unten |
| `visible`, `opacity`, `rotation`, `scale`, `clip`, `glow` | wie in aura-ui (`clip` schneidet rechteckig, wie Qt Quick; `glow` leuchtet weiß, z. B. `glow: root.pressed`) |
| `onTap: signal`, `onTap: signal(args)`, `onTap: ignore` | löst ein Signal aus bzw. schluckt den Tap |
| `enabled: false` | keine Eingaben für das Element und seine Kinder (QML `enabled`); Bausteine blenden sich dabei ab |
| `grow: gewicht` | in einem `Column`/`Row`: ein Anteil am freien Platz in Richtung des Layouts (QML `Layout.fillHeight`/`fillWidth`) |
| `stretch: true` | in einem `Column`/`Row`: füllt quer zur Richtung (Breite bzw. Höhe), im `Grid` die Zelle |
| `minWidth`, `maxWidth`, `minHeight`, `maxHeight` | Grenzen für `grow` und `stretch` (0: keine) |
| `onSettled: signal` | eine Animation des Elements (`animate:`) ist angekommen, siehe [Animationen](#animationen-animate-enter) |
| `pressSink: versatz, ms` | sinkt beim Drücken ein |
| `spin: ms` | dreht sich endlos im Uhrzeigersinn, eine Umdrehung in `ms` |
| `loop: eigenschaft von bis ms [easing] [pause ms], …` | pendelt endlos zwischen zwei Werten, siehe [Animationen](#animationen-animate-enter) |

**Text im Detail:**
- **Schriftfamilie:** Ohne `font` gilt `aura::ui::TextNode::defaultFamily`, das die App beim Start setzt; leer ist es die Standardschrift der Schriftsammlung.
- **Schriftgröße:** `font` und `fit: N` gehen durch `Context::fontSize()` der App (Standard: unverändert). `pixelSize` setzt Pixel ohne diesen Weg.
- **Natürliche Größe:** Ein `Text` ohne `width`/`height`/`size` bekommt die Größe seines Inhalts. Mit `wrap: 400` ist die Breite 400 und die Höhe ergibt sich aus dem Text.
- **Rich Text** wie QML StyledText: `<b>`, `<br />` und `<font color="#rrggbb">`.

## Anker (`place`)

`place: left 44, below title 30, centerY -20` wird zu `aura::ui::place(node).left(44).below(*title, 30).centerY(-20)` (siehe [graphics.md](graphics.md)). Die Reihenfolge zählt.

| Direktive | Bedeutung |
|---|---|
| `left`, `right`, `top`, `bottom` [Abstand] | zum Rand des Parents |
| `centerX`, `centerY` [Versatz], `center` | im Parent zentriert, gerundet |
| `below`, `above`, `rightOf`, `leftOf` Element [Abstand] | neben einem Element |
| `alignLeft`, `alignTop`, `alignBottom` Element [Versatz] | an einem Element ausgerichtet |
| `centerXOn`, `centerYOn` Element | auf die Mitte eines Elements |

Als Element zählen IDs, die im Quelltext **vorher** stehen (Geschwister oder Vorfahren), `parent` und bei wiederholten Elementen `id[index]`, z. B. `centerYOn field[0]`.

## Layouts: `Column`, `Row`, `Grid`

Sie ordnen ihre sichtbaren Kinder an, sobald ein Kind hinzukommt. Ändert sich später ein Kind (über eine Bindung oder einen Neuaufbau), ordnen sie neu an. Ihre eigene Größe folgt dem Inhalt (`Column`: Höhe, `Row` von links: Breite, `Grid`: Höhe), sodass andere Elemente sich darauf beziehen können:

```qml
Scroll {
    id: list
    size: parent.width - 4, min(len(lines), 5) * 76
    contentHeight: productRows.height   // nach dem Bau der Zeilen
    Column {
        id: productRows
        width: parent.width
        repeat line, i in lines { CartRow { index: i; line: line } }
    }
}
// Header: rechtsbündig
Row { id: buttons; direction: rightToLeft; spacing: 16; size: parent.width, 60 }
// Grid layout
Grid { id: cells; columns: columns; spacing: 10 }
```

Unsichtbare Kinder belegen keinen Platz. Die Kinder setzen ihre Größe selbst, die Lage in Richtung des Layouts (`Column`: `y`, `Row`: `x`, `Grid`: beides) vergibt das Layout.

**Flexibel:** Mit diesen Properties verteilt ein Layout Platz, statt nur zu stapeln:

```qml
Column {
    size: 400, parent.height
    padding: 20                     // 1 Wert, 2 (oben/unten, links/rechts) oder 4 (oben, rechts, unten, links)
    spacing: 10
    align: center                   // quer: left, center, right (Row: top, center, bottom)

    Text { text: tr("Title", "Titel") }
    Scroll { grow: 1 }              // nimmt die restliche Höhe
    Row {
        stretch: true               // so breit wie die Column (ohne padding)
        height: 60
        justify: spaceBetween       // start, center, end, spaceBetween, spaceAround, spaceEvenly
        DialogButton { text: "Zurück" }
        DialogButton { text: "Weiter"; grow: 1; maxWidth: 300 }
    }
}
Row { id: tags; width: 500; wrap: true; spacing: 8 }    // bricht in Zeilen um (QML Flow), wird so hoch wie sie
```

- **Länge:** Hat ein Kind `grow` oder ist `justify` gesetzt, behält das Layout seine Höhe (`Column`) bzw. Breite (`Row`) und verteilt sie: erst bekommen die Kinder ohne `grow` ihre Größe und die Abstände, den Rest teilen sich die wachsenden Kinder nach Gewicht (begrenzt durch `min…`/`max…`). Sonst wird es so lang wie sein Inhalt, wie bisher.
- **Quer:** Ohne `align`, `stretch` und `padding` auf dieser Seite behalten die Kinder ihr `x` (`Column`) bzw. `y` (`Row`), wie bisher. Sonst setzt das Layout es; ein Layout ohne Breite (Höhe) nimmt dann die des breitesten Kindes.
- **Inhalt folgt:** Ändert ein Layout die Größe eines Elements mit `grow` oder `stretch` (oder einer Komponenten-Instanz), laufen die Properties seiner Nachfahren erneut (`size: parent.width - 4`, `place: right`), danach ordnen die Layouts darin neu an. In C++ setzt `Node::resize()` die Größe und ruft `onResized`.
- **`wrap`** (nur `Row` mit Breite): Kinder in Zeilen, `spacing` auch zwischen den Zeilen; `align` und `justify` gelten je Zeile.

## Ausdrücke

| | |
|---|---|
| Werte | Zahlen (intern `double`, wie in QML), `"Text"` (mit `\n`, `\t`, `\b`), Farben `#rrggbb`/`#aarrggbb`, `true`/`false`, Listen `[a, b]` |
| Operatoren | `+ - * / %`, `== != < > <= >=`, `&& \|\| !`, `a ? b : c`; `+` verbindet auch Texte |
| Namen | IDs (vorher definiert), `parent`, eigene Geometrie (`x`, `y`, `width`, `height`, `implicitWidth`, `implicitHeight`), Schleifenvariablen, `property`s des Dialogs bzw. der Komponente (auch als `root.<name>`) |
| Element-Werte | `id.x`, `.y`, `.width`, `.height`, `.opacity`, `.rotation`, `.scale`, `.pressed`, `.implicitWidth`/`.implicitHeight` (Text; Image: Bildgröße; Html: Höhe), `.contentHeight` (Scroll) |
| Datensätze | `zeile.feld`, `liste[i].feld`, `root.zeile.feld` |
| Listen | `liste[i]`, `len(liste)`, z. B. `["", "ABC", "DEF"][i]` |
| Kontext | `theme.<name>`, `screen.width`, `screen.height`, `props.bool("ID")`, `props.int("ID")`, `props.string("ID")` |
| Funktionen | `tr("id", "Deutsch")`, `str(zahl)`, `replace(text, alt, neu)`, `lower(text)`, `upper(text)`, `len(liste\|text)`, `fontSize(px)` (`Context::fontSize`), `ceil`, `floor`, `round`, `abs`, `min`, `max`, `range(n)` |

Typen werden geprüft: Ein Text, wo eine Zahl stehen muss, oder ein Feld, das der Datensatz nicht hat, bricht den Build ab.

## Wiederholung und Bedingung

```qml
repeat key, index in ["1", "2", "\b"] {
    Item { id: keyItem; x: index % 3 * 94; onTap: keyPressed(key) }
}
repeat i in range(6) { Rect { id: field; x: digitsX + i * 54 } }
repeat line, i in lines { CartRow { id: row; line: line } }

if key == "\b" { Svg { ... } } else { Text { ... } }
```

- **Liste:** eine Liste in eckigen Klammern, `range(n)`, eine `strings`-Property oder eine Liste von Datensätzen.
- **IDs in einer Wiederholung** werden im Struct zu Vektoren (`std::vector<aura::ui::RectNode*> field`). Außerhalb der Schleife greift man mit `field[0]` darauf zu.
- **IDs unter `if`** sind im Struct `nullptr`, solange der Zweig nicht gebaut ist. Properties, die auf eigene Kinder warten (siehe unten), dürfen keine Kinder unter `if` nennen.
- **Zustand:** Hängt die Liste oder die Bedingung vom Zustand ab, wird der Block bei einer Änderung neu gebaut (siehe [Neuaufbau](#neuaufbau-von-repeat-und-if)).
- **`lazy`:** `repeat p, i in photos lazy { … }` baut nur die Einträge, die zu sehen sind, für lange Listen (Tausende Fotos eines Telefons):

  ```qml
  Scroll {
      id: grid
      size: parent.width, parent.height
      contentHeight: cells.height
      Grid {
          id: cells
          columns: 4
          spacing: 10
          cell: 240, 180                    // Pflicht: jede Zelle gleich groß
          width: parent.width
          repeat p, i in photos lazy {
              PhotoCell { id: cell; photo: p; stretch: true; onTap: pick(i) }
          }
      }
  }
  ```

  - **Wo:** direkt in einem `Grid` mit `cell: breite, höhe` (für eine Liste `columns: 1`), das in einem senkrechten `Scroll` liegt; ein Element je Eintrag, nicht in einer anderen Wiederholung.
  - **Was gebaut ist:** die Zeilen im sichtbaren Teil des `Scroll` und je eine davor und danach. Beim Scrollen baut AURA die neuen Einträge und baut die anderen ab (gelöscht vor dem nächsten Bild, ihre Bindungen enden). Das `Grid` setzt jeden Eintrag in die Zelle seines Index und ist so hoch wie alle Zeilen, `contentHeight: cells.height` stimmt also.
  - **IDs:** `ui->cell` hat einen Eintrag je Listeneintrag, `nullptr` für nicht gebaute. Was die App an Zellen hängt (Bilder), hängt sie in `ui->onRebuilt` an die neu gebauten (Zellen ohne ihren Zusatz) und vergisst dabei Zeiger auf abgebaute.
  - **Zustand:** wie bei jeder Wiederholung; eine geänderte Liste behält gleiche Einträge, die anderen kommen neu, wenn sie zu sehen sind.

## Reihenfolge der Properties

Pro Element gilt diese Reihenfolge, genau wie im früheren handgeschriebenen Code:
1. Inhalt (Text, Farben, Schrift, …),
2. die natürliche Größe von Texten,
3. die Geometrie (`x`, `y`, `width`, `height`, `size`, `place`, `contentHeight`) in Quelltext-Reihenfolge.

Deshalb funktioniert `height: back.y - 30 - y` nach `place: left 44, below editBox 30`.

**Bezug auf eigene Kinder:** Eine Property, die eine ID aus dem eigenen Teilbaum nennt, wird erst angewandt, wenn dieses Kind gebaut ist, und zwar direkt danach. Spätere Kinder sehen den Wert schon:

```qml
component PriceRow : Rect {
    height: topicText.height + 12       // nach topicText
    Text { id: topicText; wrap: 290 }
    Text { place: right 15, centerY }   // sieht die Zeilenhöhe
}
```

## Gedrückt-Zustand

Nennt eine Property `id.pressed`, wird sie neu ausgewertet, sobald sich der Zustand ändert. Mit ihr laufen alle Nachfahren von `id` erneut durch, damit Anker mitwandern:

```qml
Item {
    id: keyItem
    onTap: keyPressed(key)
    Rect {
        id: face
        x: keyItem.pressed ? 5 : 0
        width: parent.width - (keyItem.pressed ? 8 : 0)
        Text { size: parent.width, parent.height }   // folgt dem Rect
    }
}
```

`id` selbst wird dabei nicht neu angeordnet, sonst würde etwa ein `pressSink` zurückgesetzt. In Komponenten geht auch `root.pressed` (Taste, Footer-Button).

## Texteingabe

```qml
TextField {
    id: code
    font: theme.textFont, 48
    placeholder: tr("Voucher_Code", "Gutscheincode")
    maxLength: 20
    focus: true
    size: 400, 60
    onChange: codeChanged           // signal codeChanged(string text)
    onAccept: redeem                // Enter
}
Rect { id: ok; onTap: redeem(code.text) }
Keyboard {
    layout: props.string("CurrentLanguage")          // "de", "EN", ...; unbekannt: DE
    place: centerX, bottom 20
    onOk: redeem(code.text)
    onCancel: close
}
```

- **Fokus:** Tasten gehen an das Element mit dem Fokus, sonst wie bisher an `Application::onKey`. Ein Tipp auf ein `TextField` gibt ihm den Fokus und setzt die Schreibmarke an die Stelle. Ein Tipp anderswo (etwa auf eine Taste der Bildschirmtastatur) lässt ihn, wo er ist. `focus: true` gibt ihn beim Aufbau, `Node::focus()` / `Node::clearFocus()` in C++. Ist das Element unsichtbar oder gelöscht, gehen die Tasten wieder an `onKey`.
- **Tasten:** Zeichen fügen an der Schreibmarke ein (bis `maxLength`), Backspace und Entf löschen, ←/→ bewegen die Schreibmarke, Enter löst `onAccept` aus. Escape, Tab, ↑/↓ nimmt das Feld nicht, sie gehen weiter an `onKey`. Die Quellen sind die Tastatur des Fensters, die Fernsteuerung (`Application::injectKey`) und `Keyboard` (`aura::ui::sendKey`).
- **`text`:** der Startwert. Wertet die Bindung neu aus (eine andere Property des Elements hängt am Zustand), bleibt das Getippte stehen; nur ein *geänderter* Wert ersetzt es (`bindValue`). Die App liest den Text mit `ui->code->value()` und setzt ihn mit `setValue()` (ohne `onChange`).
- **`id.text`** gibt in Ausdrücken den aktuellen Text eines `TextField` (bzw. `Text`), z. B. als Argument eines Signals. Properties, die `feld.text` eines `TextField` nennen, laufen nach jedem Tippen neu (`visible: len(code.text) > 0`), wie bei einer Zustands-Property.
- **Signale:** `onChange` (nach jeder Änderung durch Tippen) und `onAccept` geben ein Signal mit einem `string`-Parameter den Text mit, eines ohne Parameter nichts; `onChange: changed(code.text)` geht auch. `Keyboard` hat `onOk` und `onCancel`.
- **Tastatur:** Vier Zeilen Zeichentasten mit bis zu acht Belegungen (normal, Shift, Ctrl = `*#+@`, Alt = `éàó` und ihre Kombinationen). Shift, Ctrl und Alt bleiben an, bis man sie noch einmal tippt. Bilder braucht die Tastatur nicht, die Symbole (Backspace, Enter, Shift, Leertaste) zeichnet sie selbst.
- **Blinken:** Die Schreibmarke blinkt im Takt von 500 ms (ohne Animation, die Hauptschleife zeichnet im Leerlauf ohnehin alle 500 ms). Für Bildschirmfotos und Tests stellt `aura::ui::TextFieldNode::blink = false` sie dauerhaft an.

## Bausteine

```qml
Column {
    padding: 24
    spacing: 16
    CheckBox { id: agb; text: tr("Agb", "AGB gelesen"); checked: accepted; onToggle: accept }    // signal accept(bool on)
    Slider { from: 0; to: 100; step: 5; value: volume; onChange: setVolume }                // signal setVolume(real v)
    ProgressBar { value: uploaded; to: total; stretch: true }
    Button { text: tr("Next", "Weiter"); color: theme.mainColor; enabled: agb.checked; onTap: next }
}
```

- **Auswahl:** `RadioButton { group: "format"; checked: format == "a"; onToggle: chooseA }` für wenige, sichtbare Möglichkeiten, `ComboBox { options: formats; index: chosen; onSelect: choose }` (Signal mit `int`) für viele.
- **Zustand:** `checked`, `index` und `value` sind wie `text` beim `TextField` Startwerte: Was der Kunde eingestellt hat, bleibt, bis sich der gebundene Wert ändert (`bindChecked`, `bindValue`). `id.checked`, `id.index` (ComboBox) und `id.value` geben den aktuellen Stand in Ausdrücken; Properties, die sie nennen, folgen jeder Änderung durch den Kunden (`enabled: agb.checked`, `text: str(volume.value)`).
- **Signale:** `onToggle` gibt einem Signal mit einem `bool`-Parameter den neuen Stand mit, `onChange` des `Slider` einem `real`-Parameter den Wert (nur bei einer Änderung, auch beim Ziehen); ohne Parameter nichts. `Button` hat das gemeinsame `onTap`.
- **Aussehen:** neutral grau; die App färbt sie über Properties oder eine Komponente, z. B. `component MainButton : Button { color: theme.mainColor; font: theme.textFont, 24 }`. `Ripple`, `pressSink` und `glow` gehen wie bei jedem Element.
- **`enabled`** wirkt auf jedes Element und alle Kinder: keine Taps, kein Ziehen.
- **Wie es folgt:** `TextField`, `CheckBox`, `Switch` und `Slider` mit einer ID melden jede Änderung durch den Kunden (`Node::onEdited`) unter dem Bindungsschlüssel `@id`, vor ihrem eigenen Signal. Gilt für einzelne Elemente, nicht für IDs in einem `repeat`.

## Dialog und Screen

```qml
Dialog OrderStatusDialog {
    property strings digits             // Argument der App (Zustand)
    const real digitsX: 140          // Konstante
    signal keyPressed(string key)
    size: 860, 600
    ...
}
```

- **`size: w, h`** (nur Dialog): Größe des Rahmens. Die App fragt sie mit `Name::size(ctx, args)` ab; Argumente dürfen darin vorkommen.
- **Screen:** hat keine Geometrie. Er darf das Aussehen seiner Seite setzen (`color`, `border`, `radius`, `gradient`, `opacity`), z. B. `color: theme.white`.
- **`signal name` / `signal name(string key, int index)`:** Ereignisse für die App. Parametertypen sind `string`, `real`, `int`, `bool` und `color`; `real` und `int` kommen in C++ als `double` an.
- **`property typ name[: Ausdruck]`:** Argument der App und zugleich veränderbarer Zustand mit Setter, optional mit Standardwert. Sie landet in `NameArgs` und wird an `size()` und `build()` übergeben.
- **`const typ name: Ausdruck`:** Konstante ohne Setter und ohne Argument der Instanz, im ganzen Dialog verwendbar (z. B. `const int limit: 5`). Das gilt auch in Screens und Komponenten.
- **Standardwerte:** Ein `property`-Standardwert wird einmal beim Aufbau ausgewertet, wenn kein Argument übergeben wurde; er ist keine dauerhafte Bindung. Explizite Werte wie `0`, `false` oder `""` überschreiben ihn. Spätere Änderungen erfolgen über den Setter.

Property-Typen: `real`, `int`, `bool`, `string`, `color`, `image` (ein Bild der App, in C++ `sk_sp<SkImage>`, leer erlaubt), `strings` (Liste von Texten, in C++ `std::vector<std::string>`), ein Datensatz und `Datensatz[]` (Liste).

## Komponenten

```qml
component DialogButton : Rect {
    property string text        // Argument der Instanz (": wert": Standard)
    color: theme.mainColor
    radius: 4
    pressSink: 4, 250
    Text {
        text: root.text
        fit: true
        align: center, center
        size: parent.width, parent.height
    }
}

component LanguageButton : Rect {
    property string language
    signal select                       // eigenes Signal der Komponente
    onTap: select
    ...
}
```

- **Basistyp:** einer der eingebauten Typen.
- **`property`:** Argument der Instanz, optional mit Standardwert, und Zustand der Komponente (Setter im Struct).
- **Signale weiterleiten:** `onToggle: toggleContrast` an einer Instanz löst beim Komponentensignal `toggle` das Signal `toggleContrast` des umgebenden Dialogs aus, mit denselben Parametern. `onToggle: picked("x")` bzw. `onEdit: edit(i)` übergibt eigene Argumente.
- **Reihenfolge bei einer Instanz** (`DialogButton { id: noButton; ... }`): Zuerst gelten die Properties der Komponentenwurzel, dann die der Instanz (sie gewinnen), erst danach baut die Komponente ihre Kinder. So greifen z. B. `size: parent.width, ...` in den Kindern auf die Größe der Instanz zu.
- **Kinder:** Eine Instanz hat keine Kinder. Bilder der App kommen als `image`-Property; was die App selbst zeichnet (Fotos mit Beschnitt), kommt in ein benanntes `Item` der Komponente.
