State, records and bindings
State
All dialog, screen, and component property declarations are mutable state, including those with defaults. The application sets them during construction and afterwards through generated setters:
component PriceTab : Item {
property bool selected
Text labelText {
color: selected ? theme.interactiveColor : theme.textColor
}
Rect bar { visible: selected; size: labelText.width + 30, 6 }
}
tab->setSelected(i == index);
What follows a change:
- Every element statement group that references the state,
- every group referring to an affected element (through its ID,
parentorplace); in this example, that includesbar's width because it depends onlabelText, - component instance arguments (
TestNav { up: focus > 0 }calls the instance's setter), repeatandifblocks whose list or condition references the state (rebuilding; see below),- the layout (
Column/Row/Grid) containing a changed element.
Equal values: A setter given the state's existing value does nothing (records and lists are compared with ==). The application can therefore set system information every second, for example; only changed content is rebuilt.
What does not follow: One-time properties (name, source, onTap, pressSink, spin) and constants (const). A reevaluated group also resets its geometry; elements moved by the application itself (such as a scroll thumb) should therefore have no position in the .aura file.
Named states (state) and when
A state with fixed values (like QML states), plus blocks that change properties while a condition holds:
component SaveButton : Rect {
state mode: idle, busy, done // der erste Wert ist der Start
color: theme.mainColor
Text label { text: tr("ui_save", "Speichern") }
Svg 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: Astringstate containing one of these values. Conditions use values without quotes (mode == busy); a typo (mode == bsy) fails the build. An element can have several states.- In C++: An
enum classin the struct and a type-safe setter (button->setMode(SaveButton::Mode::busy)), plus the fieldmodecontaining the value's name. For dialogs and screens, the state appears at the end of Args, initialized to its first value (args.view = "thanks"). An instance sets it withmode: busy. when condition { … }: While the condition holds, its properties apply to the element itself and (asid.name) to elements inside it. The condition can be any expression: a state,root.pressed, an argument. If severalwhenblocks affect the same property, the later one wins.- Values outside
when: A property changed by awhenneeds a value outside it; onlyvisible,opacity,scaleandrotationhave defaults.width/heightchange the corresponding part ofsize. One-time properties (place,name,onTap,source,pressSink,spin) do not change. - Implementation: The parser rewrites every property in a
whenas a conditional value (label.text: mode == "busy" ? … : …). Everything then uses normal bindings, identically in both modes; structural changes still useif. - Pressed:
when root.pressedorwhen id.pressedon the element itself changes its content (color, text, opacity), not its geometry (which would reset apressSink).
Animations (animate, enter)
Changing properties move smoothly to their new values rather than jumping; appearing elements can fade in:
Rect 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 thanks {
enter: opacity 0 300 // beim Erscheinen von 0 zum Wert
exit: opacity 0 300 // beim Abbau zu 0, dann weg
}
}
-
animate: property ms [easing], …: Every value change animates, whether from an application setter, awhenorroot.pressed. A new change during an animation starts from the current interpolated value. The initial value during construction is set directly. -
enter: property start ms [easing], …: The first value animates from the starting value, for example on an element just built by anif. -
exit: property end ms [easing], …: When aniforrepeatremoves the element, the property first animates to its ending value, then the element disappears. It does not accept taps meanwhile; the new element is underneath (crossfade). Applies to elements directly inside anif/repeat. -
Without an explicit value: If the element does not set
opacity,scale,rotationorunfolditself,enter:animates to its own default value (Popover { enter: unfold 0 300 linear }unfolds it;enter: opacity 0 300fades it in). -
loop: property from to ms [easing] [pause ms], …: Oscillates indefinitely: fromfromtotoinms, back in the same time, then pauses forpausems (default easing:inOutQuad).loop: rotation -6 6 800swings an arrow;loop: scale 1 0.95 2000 pause 1000pulses an icon. Without an animator, the value stays still. Endless animations (loop,spin) keep drawing active only while their element can be seen (visible and nontransparent all the way to the root). -
delay ms(at the end of ananimate,enterorexitentry): Each change waits this long before starting; the value remains unchanged during the wait. This sequences steps even across multiple elements:qmlRect sidebar { x: menu ? -width : 0; animate: x 250 inOutCubic } Rect topNavigation { y: menu ? 60 : -60; animate: y 250 inOutCubic delay 250 } // nach der Seitenleiste Scroll list { y: menu ? 130 : 1060; animate: y 750 outBack delay 250 } // fliegt herein, schwingt über -
when condition(at the end of ananimateentry): A change animates only if the condition is true at that moment; otherwise the value is applied immediately. This makes a transition run in only one direction, and lets the application disable animation for a particular change:qmlRect videoLayer { opacity: videoMode ? 1 : 0; animate: opacity 1000 inOutQuad when !videoMode } // nur ausblenden Rect sidebar { x: videoMode ? 0 : -width; animate: x 250 inOutCubic when animated } // property bool animated -
via value ms [easing](after duration and easing in ananimateentry): A change first moves tovalue(in the entry's duration), then on to the new value. This lets an element overshoot its target and return:animate: y 750 inOutCubic via 60 - 50 250 inOutCubic when animated && !videoMode. -
onSettled: signal(any element): An element's animation (animate:) reached its value, even if it was applied immediately (not on the initial value). Use this for subsequent work, such as stopping a player after its video fades out. -
Scrolling: A
Scroll'scontentYis a property likey: bound to state and combined withanimate: contentY 600 inOutQuint, it scrolls the list to a section (the application sets the target). A user's swipe does not stop the animation, but a new animation stops the swipe. In C++,aura::scrollTo(scroll, y, animator)scrolls (duration based on distance, overshooting the top and returning). -
Properties:
x,y,opacity,scale,rotation,color(Rect, Text),unfold(Popover),contentY(Scroll);loopdoes not supportcolor. Easings:linear,inOutQuad,inOutCubic,inOutQuint,outQuad,outBounce,outBackandinOutBack(overshoot by about 10%, QMLEasing.OutBack). -
Without an animator (screenshots,
aura-view --render, pixel tests), the final value is applied immediately. In the VS Code preview, animations run with Play/Pause controls. -
Implementation:
animate/enterattach transitions to the node during construction (aura::transition()); property assignments useaura::animateTo()/animateColor(). Identical in both modes.
Previews
A preview shows a dialog, screen or component with preset values in the preview tools (VS Code, aura-view), similar to SwiftUI's #Preview:
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 €" }
]
}
- Values: The target's properties and states (state values without quotes). Values not specified by a preview are empty (
"",0,false, no entries); components retain their defaults. Placeholders (test-<name>) appear only in “Standard”. - Size: A component whose size follows its parent (
size: parent.width, …,height: parent.height) receives its size in the preview throughsize: width, height, as on an instance.background: colordraws a color behind it (e.g.theme.mainColorfor white header buttons). Both apply only to component previews. - Literals: Only previews accept records as
{ field: value, … }; missing fields are empty. Animageis a filename, as inImage { source: … }. - Validation:
auracvalidates the target, names and state values; no code is generated. The application never sees previews. - In the preview tools: An element's previews appear in the “Preview” dropdown; “Standard” shows it with placeholders. States and
boolproperties of the displayed element can be toggled live there. - Which components: Data rows and cells (
CartRow,PriceRow,MenuTile, …) and elements with variants (BackButton,HeaderButton,SaveButton, …) have previews withtest-data. Frames and structural elements (DialogFrame,Flyout,Ripple) do not; they are visible in their dialogs.
Records
A record describes application data made up of several values. A list of records replaces the loop in which the application previously built a component for each entry:
// 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 productRows {
repeat line, i in lines {
CartRow 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 } }
}
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 */ };
- Fields:
real,int,bool,string,color,image,strings, and previously defined records orDatensatz[]. An image in a record (image preview,Image { image: line.preview }) replaces the placeholder to which the application previously attached an image; if it arrives later (photos still being decoded), the application resets the list and only that row is rebuilt. - In C++: A struct in the UI namespace (
views::CartLine), with fields in source order, so it can be initialized with{...}. - Origin: Records always come from the application. There are no record literals for defaults or
constdeclarations. - In signals:
signal chosen(CartLine line)orsignal all(CartLine[] lines)passes records and lists, received in C++ asconst CartLine&orconst std::vector<CartLine>&. In dynamic mode,fromValue()converts them back. - Per-entry behavior: Records carry no functions. A component emits a signal with the index or record; the application decides what to do.
Rebuilding repeat and if
If a repeat list or an if condition references state, changing that state rebuilds the block:
ui->setLines(lines); // update the record list
- In place: An invisible anchor node (
objectNameaura_block) marks the position among siblings. New nodes are inserted immediately after it, preserving draw order and layout. - Repetition entries: When the list changes, the block compares its entries by position with the previous list (records use
operator==). Equal entries keep their nodes and bindings; only changed, added and removed entries are built or removed. Appending an entry therefore builds only that entry; changing a quantity rebuilds only its row. Comparison uses position because loop variables (iinonTap: remove(i)) are captured in entries. Inserting an entry at the beginning therefore rebuilds all subsequent entries. - State inside entries: If state referenced by an
iforrepeatinside entries changes, the repetition rebuilds all entries. State in ordinary entry properties is simply reevaluated, as elsewhere. - IDs: After the setter, struct fields (vectors, pointers, component structs) point to current nodes in list order. The application attaches anything needed by new nodes after the setter; retained entries keep their existing additions.
ui->onRebuilt: Runs after a block is rebuilt or alazyrepetition builds or removes entries (not during initial construction). The application attaches additions to new nodes here if rebuilding was not triggered by its own setter.- Old nodes are removed from the tree and deleted only on the next rebuild. A tap handler whose signal triggers rebuilding therefore does not run on deleted memory.
- Bindings for nodes in a block belong to their iteration (
aura::Bindingwith a guard) and end with it; this also applies topressedgroups. - Nested blocks: Inside a repetition, the outermost repetition is always rebuilt as a whole. It therefore also follows the state of lists and conditions inside it. An
ifoutside repetitions rebuilds independently, including when nested inside anotherif. - Cost: An
ifcompletely rebuilds its branch; a repetition rebuilds only changed entries (see above).
Lifetime
Dependencies (aura::Bindings) are attached to the root node; the struct holds only a weak reference. A setter called after closing does nothing.
docs/aura.md · Download Markdown