Die App-Seite
Hallo Welt
Das kleinste AURA-Programm liegt in examples/hello/ und baut nur gegen aura-*:
add_executable(aura-hello main.cpp)
aura_add_ui(aura-hello FILES hello.aura) # Standardkontext
target_link_libraries(aura-hello PRIVATE aura::ui)
#include <aura.h>
#include "aura_ui.h" // generiert aus hello.aura
int main()
{
AuraApplication app({.title = "Hello"});
if (!app.init()) {
return 1;
}
AuraBasicContext<> ctx(app);
auto ui = views::Hello::build(app.addFrame(), ctx);
return app.run();
}
aura-hello --render datei.png zeichnet ohne Fenster (ctest aura_hello). Ohne registrierte Schriften nimmt Text die Standardschrift (unter Linux über fontconfig).
aura.h: AURA ohne Namespaces
#include <aura.h> (liegt bei aura-core) bringt alle öffentlichen Klassen mit Präfix Aura, wie
Qt mit Q: AuraApplication, AuraTextNode, AuraImage, AuraTaskRunner, AuraWebApp,
AuraScriptContext, AuraRemoteController, ... Eingebunden werden nur die Module, gegen die das
Programm linkt (__has_include). Regeln:
- Klassen sind Aliase (
using AuraTextNode = aura::ui::TextNode;), auch Templates (AuraProperty<int>,AuraBasicContext<MyTheme>). Die echten Namen (aura::ui::...) gelten weiter, beides lässt sich mischen; Compiler-Meldungen zeigen die echten. - Wo der Name allein zu allgemein ist, steht das Modul dazwischen:
AuraScriptContext,AuraScriptValue,AuraWebCaller,AuraRemoteTarget,AuraHtmlBlock. - Funktionsgruppen sind Klassen mit statischen Methoden:
AuraLog::info(),AuraStrings::trimmed(),AuraSystem::uptimeSeconds(),AuraTar::folderFiles(),AuraCrypto::sha256(),AuraHttp::send(),AuraTls::issueCertificate(),AuraWeb::jsonResponse(),AuraUiThread::post(),AuraUi::keyFromName(),AuraHtml::parse(),AuraInspect::render(),AuraBarcodes::ean13(),AuraRemote::routes(),AuraWebView::setup(). AuraImage(aura::ui::Image) ist eine echte Wertklasse wieQImage: teilt die Pixel, Methoden liefern neue Bilder (AuraImage::load("a.jpg", 1600).rotated(1).save("b.jpg")), wird alssk_sp<SkImage>gezeichnet.- HEIF/HEIC (iPhone-Fotos) liest es mit libheif, wenn Skia das Format nicht kennt
(
AURA_WITH_HEIF, Standard an; libheif und ihr HEVC-Decoder libde265 vom System per pkg-config, dynamisch gelinkt, LGPL-3.0; ohne libheif baut AURA ohne HEIF). Die Drehung der Datei (irot/imir) wendet libheif an. Bei kleinen Größen nimmt es das eingebettete Vorschaubild: unter 400 px oder höchstens 16 px über dem Vorschaubild, notfalls hochskaliert (src/heif.h). Ein Gerät ohne libheif im System brauchtlibheif.soundlibde265.so(mit dem libheif-Plugin-Verzeichnis) neben dem Programm.
- HEIF/HEIC (iPhone-Fotos) liest es mit libheif, wenn Skia das Format nicht kennt
(
AuraJsonundAuraXmlsind vollständige Wrapper, keine Ableitungen: nlohmann/json und pugixml sind Implementierungsdetails (pugixml taucht in keinem Header auf, nlohmann nur privat inaura/core/json.h). Lesen wirft nie: Fehlendes oder ein anderer Typ liefert den Fallback (j["a"]["b"].toInt(5),node.intAttr("lanes", 1)).AuraJsonist ein Wert; Elemente sind Referenzen hinein:AuraJsonRef(schreibbar, ausj["key"]und der Iteration eines nicht-konstantenAuraJson) undAuraJsonView(nur lesen). Wiestd::string_viewdürfen sie ihrAuraJsonnicht überleben;AuraJson(j["a"])kopiert. Ein fehlendes Element entsteht erst beim Schreiben (j["a"]["b"] = 1legtaan,j["a"]["b"].toInt()nicht).parse()/load()liefern bei Fehlern null und die Meldung überstd::string* error.{{"key", 1}}ist ein Objekt,{1, 2}ein Array,AuraJson{x}ein Array mitx-AuraJson(x)kopiert.- YAML ist ein zweites Format desselben Werts (rapidyaml,
aura/core/yaml.cpp), z. B. für Konfigurationsdaten:AuraJson::parseYaml()/loadYaml()(YAML 1.2 Core-Schema:nobleibt ein String, Anker und<<werden aufgelöst),j.yaml()undj.saveYaml(file).saveYamländert eine vorhandene YAML-Datei an Ort und Stelle, wenn sich nur Werte geändert haben: Kommentare, Reihenfolge, Einrückung und Quotes bleiben. Kommen Schlüssel oder Einträge hinzu oder fallen weg, schreibt es die Datei neu, ohne Kommentare und mit sortierten Schlüsseln (AuraJson-Objekte sind sortiert).AuraJson::updateYaml(text, j)ist der Schritt dazwischen. AuraXmlbesitzt das Dokument,AuraXmlNode/AuraXmlAttributesind kleine Handles hinein (null, wenn nicht da).children("name"),attributes(),select("//xpath"),toString(indent, Raw | NoEscapes);AuraXml::parse()/load()liefernstd::optional,read()/readFile()füllen ein vorhandenes Dokument.
- Der Präfix
Auragehört AURA: Programme benennen ihre eigenen Klassen anders. - aura-lang und aura-preview (Werkzeuge) gehören nicht dazu.
tests/test_aura_h.cppprüft jeden Namen; eine neue öffentliche Klasse kommt gleich inaura.h.
Der Kontext
Der Kontexttyp (CONTEXT_TYPE) leitet von aura::ui::Context (AuraContext) ab (aura/ui/context.h). Beide Modi rufen dieselben Namen auf:
| Kontext | AURA | Standard in Context |
|---|---|---|
width, height |
screen.width, screen.height |
0 |
animator |
animate:, enter:, exit:, spin:, Rive |
keiner: Werte springen |
tr(id, text) |
tr("id", "Text") |
text |
fontSize(px) |
font:, fit:, fontSize() |
px |
image(name), svg(name) |
source: |
file(name) über Resources |
file(name) |
source: von Rive |
erste Datei in assetDirs |
propertyBool/Number/String(id) |
props.bool/int/string("ID") |
false, 0, "" |
themeValue(name) |
theme.<name> (nur geladen) |
unbekannt |
theme (Member der App) |
theme.<name> (kompiliert) |
– |
- Kompiliert:
theme.<name>greift direkt auf das Memberthemedes Kontexttyps zu; der C++-Compiler prüft es. - Geladen:
theme.<name>geht überthemeValue(). Die Namen stehen einmal in eineraura::ui::ThemeTable<Theme>(aura/ui/theme_table.h):
static const aura::ui::ThemeTable<Theme> table{
{"mainColor", &Theme::mainColor}, // Farbe
{"textFont", &Theme::textFont}, // Schrift
{"largeRadius", Theme::largeRadius}, // Konstante
};
Neue Theme-Werte kommen in Theme und in diese Tabelle. Fehlt der Eintrag in der Tabelle, meldet der Loader „unknown theme value“ und nimmt die kompilierte Fassung. Die VS-Code-Erweiterung bekommt die Namen für die Vervollständigung von der laufenden Vorschau (Host::themeValues()).
Ohne eigenen Kontext: aura_add_ui ohne CONTEXT_INCLUDE nimmt aura::ui::BasicContext<> (aura/ui/basic_context.h): Größe und Animator aus der Application, Bilder aus assetDirs, ein Theme vom Typ des Template-Arguments (BasicContext<MyTheme> mit themeTable).
aura::ui::BasicContext<> ctx(app);
ctx.assetDirs = {"assets"};
Daten aus dem Hintergrund
Was dauert (Netz, Dekodieren), läuft auf einem aura::TaskRunner der Anwendung; das Ergebnis kommt im UI-Thread an und geht über einen Setter in die UI. Für Aufgaben, bei denen nur die neueste zählt, gibt es latest():
// _orderLookup (aura::Latest): ein neuer Auftrag stoppt den alten
_tasks.latest(_orderLookup,
// im Pool
[url](std::stop_token stop) { return fetchHtml(url, stop); },
// im UI-Thread, nur das neueste Ergebnis
[this](const std::string& html) { _ui->setResult(html); });
Ein „lädt noch“ ist einfach Zustand (property bool waiting, if waiting { … }). Ein Setter mit unverändertem Wert tut nichts, die App muss also nicht selbst vergleichen.
Laufzeit: Timer, Prozesse, Zeit, Geräte
Bausteine von aura-core ohne Grafik (auch in aura.h als AuraTimer, AuraProcess, AuraTime, …):
-
aura::ElapsedTimer(aura/core/timer.h, wieQElapsedTimer):start(),elapsed()in ms,restart(),hasExpired(ms)auf der monotonen Uhr. -
aura::Timer(wieQTimer):timeoutim UI-Thread nach dem Intervall, einmalig (singleShot = true) oder wiederholt.start(ms)beginnt neu,pause()hält die Restzeit,resume()läuft mit ihr weiter,stop()beendet; Löschen stoppt ihn. Ein wiederholter Timer plant vom geplanten Zeitpunkt weiter (kein Wegdriften), hinkt er um mehr als ein Intervall, vom Jetzt.Timer::after(ms, f)ist der einmalige ohne Objekt. Timer laufen, woaura::runPosted()läuft; die Application-Schleife wacht pünktlich für den nächsten auf.cpp_updates.timeout = [this]() { updateLists(); }; // AuraTimer _updates (Member: geht mit dem Screen) _updates.start(std::chrono::seconds(1)); -
aura::runProcess(argv, options)(aura/core/process.h, wieQProcess):posix_spawn, die Pipes mitO_CLOEXEC, im Kind alle anderen Deskriptoren geschlossen (nichts vom Programm leckt in das Kind), eigene Prozessgruppe, Standard-Signale. Optionen: Eingabe, Umgebung, Arbeitsverzeichnis, Timeout (SIGTERM, 2 s später SIGKILL), Obergrenze der Ausgabe, Ausgabe stückweise (onOutput). Es blockiert (Worker-Thread);runProcessAsync(runner, …, done)meldet das Ergebnis im UI-Thread. -
Zeit (
aura/core/datetime.h):setTimeZone("Europe/Berlin")(prüft die Zone, sonst Europe/Berlin),localTime(),fromLocal(),formatDateTime()mit den Formaten vonQDateTime::toString(HH:mm:ss dd.MM.yyyy,d. MMMM yyyymitDateNames::german(),h:mm AP,'Text'),toIsoString()/parseIsoDateTime()(lokal,Z,+01:00). ÜberTZ/localtime_r, weilstd::chronos Zonen-Datenbank mit der alten String-ABI der Skia-Pakete nicht geht. -
Geräte (Linux, ohne udev):
aura::UeventMonitor(aura/core/hotplug.h): Kernel-Uevents über Netlink (SOCK_CLOEXEC, großer Puffer, gekürzte Nachrichten erkannt) auf einem eigenen Thread, im UI-ThreadonEventmitaction,subsystem,devtype,devname,properties;usbIdOf()liestPRODUCT(VID/PID).present("usb")liefert die schon vorhandenen Geräte aus sysfs.aura::scsiDevices(),testUnitReady(),aura::MediumWatcher(aura/core/scsi.h): SCSI-Laufwerke aus sysfs (ein Kartenleser mit vier Slots sind vier LUNs) und ob ein Medium drin ist, per TEST UNIT READY alle 200 ms (100 ms, solange eins erscheint);onChangemit Absent, Appearing (gerade eingesteckt), Present.aura::UsbmuxClient(aura/core/usbmux.h): iPhones über usbmuxd ohne libimobiledevice:onAttached/onDetached(UDID, Produkt),list(),connect(deviceId, port)für eine rohe Verbindung zu einem Geräteport (lockdownd 62078). Pairing (TLS) und AFC bauen darauf auf und sind nicht Teil davon.
Beobachtbare Modelle
Daten, die mehrere Stellen zeigen, hält das Modell beobachtbar (aura/core/observable.h, ohne Grafik):
// Application data and generated UI setters stay separate.
aura::List<std::string> items;
aura::Subscription changed = items.observe([&](const auto& values) {
ui->setItems(values); // generated for: property strings items
});
items.erase(index);
aura::Property<T>: ein Wert mitget()/set();set()mit gleichem Wert meldet nichts.aura::List<T>: eine Liste; jede Änderung meldet die ganze Liste, wie einrepeatin AURA die geänderten Einträge neu baut.aura::Subscription:observe()gibt sie zurück; sie beendet die Beobachtung, wenn sie zerstört wird. Als Member des Beobachters hält sie genau so lange wie er; sie darf das Modell überleben.- Thread: Modelle ändert nur der UI-Thread; Ergebnisse aus dem Hintergrund kommen über
TaskRunner::post()bzw.latest()dorthin.
Was in AURA steht und was in C++
| AURA | C++ (App) |
|---|---|
| Aufbau, Aussehen, Texte, Anordnung, Layouts | woher die Daten kommen, was ein Signal bewirkt |
| Zustände über Properties (gewählt, gedrückt, Fehler) | Ablauf (Arbeitsabläufe und Sitzungen) |
Listen über Datensätze, Bilder der App (image) |
was die App selbst zeichnet (Fotos mit Beschnitt), in benannte Slots |
Rahmen von Dialogen und Flyouts als Komponenten, Animationen (animate, enter, loop, Ripple) |
mehrstufige Übergänge, Video, Scroll-Griff |
Listen: Listen der App sind Datensätze mit repeat in AURA; die App liefert die Daten und reagiert auf Signale mit einem Index oder einer Aktion:
Ausgerichtete Spalten ohne Tabellen-Layout: Die Preisliste misst die Spaltentitel unsichtbar mit (Text titleProbe { visible: false }) und macht jede Zelle so breit; eine Row mit Abstand ergibt die Spalten.
docs/aura.md · Markdown herunterladen