# Grafik & Rendering

AURA verwendet SDL3 für Fenster und Eingabe und Skia für 2D-Grafik und Text. Der eigene C++-Szenengraph verbindet Layout, Interaktion, Animation und Medien.

## Grafikbasis

- C++20 und CMake bilden die native Integration.
- Skia zeichnet abgerundete Rechtecke, Text mit HarfBuzz/ICU, SVGs und Bilder.
- Native GPU-Ausgabe und CPU-basiertes Offscreen-Rendering verwenden denselben Szenengraph.
- Bibliotheken, Versionen und Lizenztexte stehen unter [Dependencies & Lizenzen](dependencies.md).

## Aufbau

| Datei | Inhalt |
|---|---|
| `application.h` | Fenster, GL-Kontext, Skia-GPU-Surface, Event- und Render-Schleife, Offscreen-PNG |
| `node.h` | Scenegraph: `Node`, `RectNode`, `ImageNode`, `SvgNode`, `TextNode`, `ScrollNode` |
| `layout.h` | Anker wie in QML (`place(node).left(44).below(title, 30)`), `gridCell` für Raster |
| `animation.h` | Tweens mit Easing (Linear, InOutCubic, InOutQuint, OutQuad, OutBounce); an den Ziel-Node gebunden, stoppen sie mit dessen Löschung |
| `resources.h` | Cache für Bilder, SVGs und die von der Anwendung bereitgestellten Schriften |
| `video.h` | `VideoPlayer` (WebM/VP9, ohne Ton, Playlist mit Endlosschleife) und `VideoNode` |

- **Layouts:** Neben expliziten Positionen und Ankern unterstützt AURA heute `Column`, `Row` und `Grid`. Details stehen in der [Sprachreferenz](language.md).
- **Logische Auflösung:** Die logische Größe wird über `WindowOptions` festgelegt; die Szene wird für die Ausgabe in das Fenster skaliert.
- **Eingabe:**
  - Antippen über `onTap`
  - Ziehen ab 10 px auf dem nächsten `ScrollNode`
  - Wischen mit Abbremsen und Zurückfedern ähnlich wie bei `Flickable` (2500 px/s max., 1500 px/s² Verzögerung)
- **Clipping:** `clip` schneidet rechteckig. Bilder können mit `radius` und `roundedCorners` selbst gerundete Ecken erhalten.
- **Rendering bei Bedarf:** Ohne Animation schläft die Schleife in `SDL_WaitEventTimeout`.

## Layout (`aura/ui/layout.h`)

Die Anker von QML ohne Bindings: Ein Node wird einmal relativ zu seinem Parent oder zu einem Geschwister-Node (gleicher Parent) gesetzt.

```qml
Text {
    id: title
    text: "AURA"
    pixelSize: 28
    place: left 44, top 44
}
Text {
    id: subtitle
    text: "Application UI Runtime Architecture"
    pixelSize: 18
    place: left 44, below title 30
}
```

| Aufruf | Bedeutung |
|---|---|
| `left/right/top/bottom(m)` | Abstand zum Rand des Parents (`right`/`bottom` brauchen die eigene Größe) |
| `centerX/centerY(offset)`, `center()` | im Parent zentriert, auf ganze Pixel gerundet wie die alten Layouts |
| `below/above/rightOf/leftOf(sibling, spacing)` | neben einem Geschwister-Node |
| `alignLeft/alignTop/alignBottom(sibling)`, `centerXOn/centerYOn(sibling)` | an einem Geschwister-Node ausgerichtet |
| `size(w, h)` | Größe setzen (vor allem vor `right`, `bottom` und `center`) |

- Die Reihenfolge zählt: Erst die Größe setzen, dann zentrieren oder rechts/unten anlegen. Der Parent braucht zu diesem Zeitpunkt seine Größe.
- Gruppen bekommen einen eigenen Container-Node beziehungsweise ein `Item` in AURA.
- `gridCell(index, columns, w, h, spacingX, spacingY)` liefert die Zelle eines zeilenweise gefüllten Rasters (QML `Grid`).
- `Text` erhält seine natürliche Größe aus dem Inhalt; `wrap`, `fit` und `Context::fontSize()` steuern Umbruch und Schriftgrößen.
- `Node::pressPosition` ist die Druckstelle (für den Ripple). `Animation::onFinished` verkettet Animationen.
- Buttons können als `Button` oder eigene Komponenten beschrieben werden; `pressSink` ergänzt eine Reaktion auf Drücken.
- **Eingabe:** `aura::ui::InputDriver` (`input.h`) ist der Finger auf einer Szene: gedrückt-Zustand, Tippen beim Loslassen über demselben Node, `onDrag`, Ziehen des nächsten `ScrollNode`. Die `Application` treibt einen mit der Maus, `aura-view` einen eigenen.
- **Entfernen aus einem Handler:** `node->removeLater()` nimmt den Node sofort aus dem Baum und löscht ihn vor dem nächsten Frame (`Node::deleteRemoved()`, das die `Application` aufruft); so darf ein Button seinen eigenen Dialog schließen.

## Hintergrundarbeit

- `aura::TaskRunner` (`aura/core/tasks.h`) führt Jobs auf einem kleinen Pool aus (Prozessorzahl minus eins, 2 bis 4 Threads), abbrechbar über `std::stop_token`; `stop(id)` hält auch einen Job auf, der noch wartet. `latest(slot, arbeit, ergebnis)` ist für Aufgaben, bei denen nur die neueste zählt (beispielsweise Suche oder Vorschaubilder): ein neuer Job auf demselben `aura::Latest` stoppt den alten, dessen Ergebnis verfällt, ebenso nach `cancel()` oder wenn der Slot nicht mehr existiert. Die Vorschaubilder des Editors werden je Foto als eigener Job dekodiert, also parallel.
- Ergebnisse kommen mit `post()` in den UI-Thread zurück: in die eine Warteschlange des UI-Threads (`aura::post()`), die die Application-Schleife vor jedem Frame abarbeitet (`aura::runPosted()`). Rückrufe eines zerstörten `TaskRunner` verfallen.
- `post()` weckt die Event-Loop über `Application::wakeUp()`. Ohne das Wecken käme das Ergebnis erst mit dem 500-ms-Idle-Takt.
- Der Destruktor stoppt und joint alle Jobs. Callbacks überleben ihren Besitzer also nie.

## Text

- Satz mit SkParagraph und HarfBuzz/ICU.
- `TextNode` unterstützt:
  - Ausrichtung
  - `HorizontalFit`: die Schrift wird bis `minimumPixelSize` verkleinert
  - Umbruch
  - einfacher Rich Text (`<b>`, `<br>`, Entities)
  - gestylte Runs (`setRuns`: Gewicht, kursiv, Farbe, Größe)
  - `lineHeight` wie in QML (Faktor auf die Zeilenhöhe der Schrift). Den Zusatzabstand setzt Qt unter jede Zeile. Skia verteilt ihn, deshalb gibt es `kDisableFirstAscent` und die Korrektur in `implicitHeight()`.
- Fehlen einer Schrift Zeichen, sucht Skia unter Linux über fontconfig in den Systemschriften nach Ersatz, wie Qt.
- Surfaces nutzen Graustufen-Antialiasing **ohne** Gamma- und Kontrastanhebung (`SkSurfaceProps(0, kUnknown, 0, 1)`).
  Mit den Skia-Standardwerten wirkt weiße Schrift auf Rot deutlich fetter als in Qt.

## Video

- `VideoPlayer` demuxt mit libwebm (mkvparser) und dekodiert mit libvpx in einem eigenen Thread.
  - Die Queue fasst 4 Bilder, danach wartet der Thread.
  - libvpx dekodiert mit bis zu 4 Threads.
- Der UI-Thread führt eine Uhr pro Datei und zeigt das jeweils fällige Bild.
  - Der Wechsel zur nächsten Datei ist ein harter Schnitt.
  - Endlosschleife über die Playlist.
- `VideoNode` lädt die I420-Ebenen als drei A8-Texturen hoch (`SkImages::TextureFromYUVAPixmaps`). Die Wandlung nach RGB (BT.601 limited) macht die GPU.
  Ohne GPU, also bei Screenshots, wandelt eine einfache CPU-Routine.
- Die Render-Schleife ist auf 60 fps begrenzt, falls der Treiber kein VSync hat.
  - Mit `--debug` loggt sie alle 5 s die fps.
  - Der Player loggt die angezeigten Videobilder pro Sekunde.
- Die erreichbare Bildrate hängt von Zielgerät, Grafiktreiber, Szene und Video ab.

## Offscreen-Screenshots

`Application::renderToPng(root, width, height, file)` rendert eine Szene ohne Fenster als PNG. Das Hello-Beispiel und die Galerie stellen passende Kommandozeilen bereit:

```bash
build/dev/examples/hello/aura-hello --render hello.png
build/dev/examples/demo/aura-demo --render-all /tmp/aura-gallery
build/dev/tools/aura-view/aura-view examples/hello --render /tmp/hello.png
```

Siehe [aura-view](preview.md) für Preview-Galerien und [Tests](testing.md) für visuelle Vergleiche.
