The application side
Hello world
The smallest AURA program lives in examples/hello/ and builds only against aura-*:
add_executable(aura-hello main.cpp)
aura_add_ui(aura-hello FILES hello.aura) # default context
target_link_libraries(aura-hello PRIVATE aura::ui)
#include <aura.h>
#include "aura_ui.h" // generated from 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 file.png draws without a window (ctest aura_hello). Without registered fonts, Text uses the default font (via fontconfig on Linux).
aura.h: AURA without namespaces
#include <aura.h> (in aura-core) provides all public classes with the Aura prefix,
similar to Qt's Q: AuraApplication, AuraTextNode, AuraImage, AuraTaskRunner,
AuraWebApp, AuraScriptContext, AuraRemoteController, ... Only modules that the
program links against are included (__has_include). Rules:
- Classes are aliases (
using AuraTextNode = aura::ui::TextNode;), including templates (AuraProperty<int>,AuraBasicContext<MyTheme>). The original names (aura::ui::...) remain valid and both forms can be mixed; compiler messages show the original names. - Where a name alone would be too general, the module appears in between:
AuraScriptContext,AuraScriptValue,AuraWebCaller,AuraRemoteTarget,AuraHtmlBlock. - Function groups are classes with static methods:
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) is a real value class likeQImage: it shares pixels, its methods return new images (AuraImage::load("a.jpg", 1600).rotated(1).save("b.jpg")), and it is drawn as ansk_sp<SkImage>.- It reads HEIF/HEIC (iPhone photos) using libheif when Skia does not recognize
the format (
AURA_WITH_HEIF, enabled by default; system libheif and its HEVC decoder libde265 are found through pkg-config, dynamically linked, LGPL-3.0; without libheif AURA builds without HEIF). libheif applies file rotation (irot/imir). For small sizes it uses the embedded thumbnail: below 400 px or no more than 16 px larger than the thumbnail, scaling up if necessary (src/heif.h). A device without system libheif needslibheif.soandlibde265.so(with the libheif plugin directory) alongside the program.
- It reads HEIF/HEIC (iPhone photos) using libheif when Skia does not recognize
the format (
AuraJsonandAuraXmlare complete wrappers, not subclasses: nlohmann/json and pugixml are implementation details (pugixml appears in no header, nlohmann only privately inaura/core/json.h). Reading never throws: missing values or different types return the fallback (j["a"]["b"].toInt(5),node.intAttr("lanes", 1)).AuraJsonis a value; its elements are references into it:AuraJsonRef(writable, fromj["key"]and iteration over a non-constAuraJson) andAuraJsonView(read-only). Likestd::string_view, they must not outlive theirAuraJson;AuraJson(j["a"])copies. A missing element is created only when writing (j["a"]["b"] = 1createsa,j["a"]["b"].toInt()does not).parse()/load()return null on errors and the message throughstd::string* error.{{"key", 1}}is an object,{1, 2}an array, andAuraJson{x}an array containingx;AuraJson(x)copies.- YAML is a second format for the same value (rapidyaml,
aura/core/yaml.cpp), for example for configuration data:AuraJson::parseYaml()/loadYaml()(YAML 1.2 Core schema:noremains a string, anchors and<<are resolved),j.yaml(), andj.saveYaml(file).saveYamlupdates an existing YAML file in place when only values have changed: comments, ordering, indentation, and quotes remain. If keys or entries are added or removed, it rewrites the file without comments and with sorted keys (AuraJsonobjects are sorted).AuraJson::updateYaml(text, j)is the intermediate step. AuraXmlowns the document;AuraXmlNode/AuraXmlAttributeare small handles into it (null when absent).children("name"),attributes(),select("//xpath"),toString(indent, Raw | NoEscapes);AuraXml::parse()/load()returnstd::optional, whileread()/readFile()populate an existing document.
- The
Auraprefix belongs to AURA: applications name their own classes differently. - aura-lang and aura-preview (tools) are not included.
tests/test_aura_h.cppchecks every name; add each new public class toaura.himmediately.
The context
The context type (CONTEXT_TYPE) derives from
aura::ui::Context (AuraContext) (aura/ui/context.h). Both modes call the same
names:
| Context | AURA | Default in Context |
|---|---|---|
width, height |
screen.width, screen.height |
0 |
animator |
animate:, enter:, exit:, spin:, Rive |
none: values jump |
tr(id, text) |
tr("id", "Text") |
text |
fontSize(px) |
font:, fit:, fontSize() |
px |
image(name), svg(name) |
source: |
file(name) via Resources |
file(name) |
source: on Rive |
first file in assetDirs |
propertyBool/Number/String(id) |
props.bool/int/string("ID") |
false, 0, "" |
themeValue(name) |
theme.<name> (loaded only) |
unknown |
theme (application member) |
theme.<name> (compiled) |
– |
- Compiled:
theme.<name>directly accesses the context type'sthememember; the C++ compiler checks it. - Loaded:
theme.<name>goes throughthemeValue(). The names are declared once in anaura::ui::ThemeTable<Theme>(aura/ui/theme_table.h):
static const aura::ui::ThemeTable<Theme> table{
{"mainColor", &Theme::mainColor}, // color
{"textFont", &Theme::textFont}, // font
{"largeRadius", Theme::largeRadius}, // constant
};
New theme values go into Theme and into this table. If the table entry is
missing, the loader reports “unknown theme value” and uses the compiled version.
The VS Code extension gets completion names from the running preview
(Host::themeValues()).
Without a custom context: aura_add_ui without CONTEXT_INCLUDE uses
aura::ui::BasicContext<> (aura/ui/basic_context.h): size and animator from the
Application, images from assetDirs, and a theme of the template argument's type
(BasicContext<MyTheme> with themeTable).
aura::ui::BasicContext<> ctx(app);
ctx.assetDirs = {"assets"};
Data from background work
Slow operations (networking, decoding) run on an application-owned aura::TaskRunner; the
result arrives on the UI thread and enters the UI through a setter. For tasks
where only the latest result matters, use latest():
// _orderLookup (aura::Latest): a new request stops the old one
_tasks.latest(_orderLookup,
// in the pool
[url](std::stop_token stop) { return fetchHtml(url, stop); },
// on the UI thread, latest result only
[this](const std::string& html) { _ui->setResult(html); });
A “still loading” indicator is simply state (property bool waiting,
if waiting { … }). A setter with an unchanged value does nothing, so the
application does not need to compare values itself.
Runtime: timers, processes, time, devices
aura-core building blocks without graphics (also available in aura.h as
AuraTimer, AuraProcess, AuraTime, …):
-
aura::ElapsedTimer(aura/core/timer.h, likeQElapsedTimer):start(),elapsed()in ms,restart(),hasExpired(ms)on the monotonic clock. -
aura::Timer(likeQTimer):timeoutruns on the UI thread after the interval, once (singleShot = true) or repeatedly.start(ms)restarts,pause()preserves the remaining time,resume()continues with it,stop()ends the timer; destruction stops it. A repeating timer schedules from the previously scheduled time (no drift); if it falls behind by more than one interval, it schedules from now.Timer::after(ms, f)is the one-shot form without an object. Timers run whereveraura::runPosted()runs; the application loop wakes in time for the next one.cpp_updates.timeout = [this]() { updateLists(); }; // AuraTimer _updates (member: shares the screen's lifetime) _updates.start(std::chrono::seconds(1)); -
aura::runProcess(argv, options)(aura/core/process.h, likeQProcess):posix_spawn, pipes withO_CLOEXEC, all other descriptors closed in the child (nothing leaks from the application into the child), a separate process group, default signals. Options: input, environment, working directory, timeout (SIGTERM, then SIGKILL 2 s later), output limit, incremental output (onOutput). It blocks (worker thread);runProcessAsync(runner, …, done)delivers the result on the UI thread. -
Time (
aura/core/datetime.h):setTimeZone("Europe/Berlin")(validates the zone, otherwise Europe/Berlin),localTime(),fromLocal(),formatDateTime()usingQDateTime::toStringformats (HH:mm:ss dd.MM.yyyy,d. MMMM yyyywithDateNames::german(),h:mm AP,'Text'),toIsoString()/parseIsoDateTime()(local,Z,+01:00). UsesTZ/localtime_rbecausestd::chrono's time-zone database does not work with the old string ABI of the Skia packages. -
Devices (Linux, without udev):
aura::UeventMonitor(aura/core/hotplug.h): kernel uevents via Netlink (SOCK_CLOEXEC, large buffer, truncated messages detected) on a dedicated thread;onEventon the UI thread withaction,subsystem,devtype,devname,properties;usbIdOf()readsPRODUCT(VID/PID).present("usb")returns devices already present in sysfs.aura::scsiDevices(),testUnitReady(),aura::MediumWatcher(aura/core/scsi.h): SCSI drives from sysfs (a four-slot card reader has four LUNs) and media presence, checked with TEST UNIT READY every 200 ms (100 ms while media is appearing);onChangewith Absent, Appearing (just inserted), Present.aura::UsbmuxClient(aura/core/usbmux.h): iPhones via usbmuxd without libimobiledevice:onAttached/onDetached(UDID, product),list(),connect(deviceId, port)for a raw connection to a device port (lockdownd 62078). Pairing (TLS) and AFC build on top of it and are not part of this component.
Observable models
A model keeps data observed in multiple places observable
(aura/core/observable.h, without graphics):
// 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>: a value withget()/set();set()with the same value sends no notification.aura::List<T>: a list; every change notifies with the entire list, just as an AURArepeatrebuilds changed entries.aura::Subscription: returned byobserve(); it ends observation when destroyed. As a member of the observer, it lasts exactly as long as the observer; it may outlive the model.- Thread: only the UI thread changes models; background results arrive
there through
TaskRunner::post()orlatest().
What belongs in AURA and what belongs in C++
| AURA | C++ (application) |
|---|---|
| Structure, appearance, text, positioning, layouts | Data sources and what signals do |
| State through properties (selected, pressed, error) | Flow (workflows and sessions) |
Lists through records, application images (image) |
Custom drawing (cropped photos) in named slots |
Dialog and flyout frames as components, animations (animate, enter, loop, Ripple) |
Multistage transitions, video, scroll handle |
Lists: application lists are records with repeat in AURA; the application
supplies data and responds to signals with an index or an action:
Aligned columns without a table layout: the price list measures column headings
invisibly (Text titleProbe { visible: false }) and makes each cell that width;
a Row with spacing produces the columns.
docs/aura.md · Download Markdown