Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 61 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,16 @@ my_project/
</component>
```

### The three sigils
### The sigils

| Prefix | Means | Example |
| --- | --- | --- |
| `$name` | An `<api>` property of this element | `<lv_label text="$title"/>` |
| `#name` | A constant from `<consts>` or `globals.xml` | `pad="#space_md"` |
| `{ ... }` | An expression, evaluated once at creation | `hidden="{!icon}"` |
| `@{ ... }` | The same expression as a binding: re-evaluated whenever a subject or variant in it changes | `hidden="@{subject_count == 0}"` |

Inside `{ }` you write bare identifiers, no `$` or `#`.
Inside `{ }` and `@{ }` you write bare identifiers, no `$` or `#`.

### `view` and `extends`

Expand Down Expand Up @@ -123,6 +124,12 @@ Three ways, in order of preference:

<!-- 3. Bound style, applied when a subject matches -->
<bind_style name="style_dark" subject="subject_dark_theme_on" ref_value="1"/>

<!-- 3b. Bound style, applied while an expression is true (no @{} wrapper) -->
<bind_style name="style_warning" if="subject_temp > 10 and subject_temp &lt;= 30"/>

<!-- 2b. Computed local style property -->
<lv_label style_text_color-pressed="@{subject_error ? 0xf00 : 0xaaa}"/>
```

Prefix style names with `style_`. Selectors combine parts and states with `|`.
Expand All @@ -140,6 +147,19 @@ But constants can be used:

Pass the property to a *local* style property instead: `<lv_slider style_border_width-knob="$thickness"/>`.

A `<transition>` child animates a style's properties on state changes. It animates *into* the state of the style holding it, so for both directions add one to the default style too:

```xml
<style name="style_card" bg_color="#color_panel">
<transition props="bg_color" duration="300" easing="ease_out"/>
</style>
<style name="style_card_pressed" bg_color="#color_panel_pressed">
<transition props="bg_color" duration="80"/>
</style>
```

One transition per style, numeric and color properties only, and `<bind_style>` never animates.

## Data binding

Subjects are the interface between the UI and the application. Define them in `globals.xml`:
Expand All @@ -161,15 +181,46 @@ Only `int`, `string` and `float` are supported.
<!-- Conditional: child element binding -->
<bind_flag_if_eq subject="subject_mode" flag="hidden" ref_value="0"/>
<bind_state_if_gt subject="subject_temp" state="checked" ref_value="30"/>

<!-- Generic: any widget attribute bound to any expression -->
<lv_label text="@{'Battery: ' . subject_battery . '%'}"/>
<lv_obj width="@{subject_columns * 100}" style_bg_color="@{subject_on ? 0x0f0 : 0x333}"/>
```

`bind_flag_*` takes a `flag`, `bind_state_*` takes a `state`. Both come in `_eq`, `_not_eq`, `_gt`, `_ge`, `_lt`, `_le`. The `lv_obj-` prefix is optional.

States: `default`, `checked`, `focused`, `focus_key`, `edited`, `hovered`, `pressed`, `scrolled`, `disabled`.
Common flags: `hidden`, `clickable`, `checkable`, `scrollable`, `floating`, `ignore_layout`.

`@{ }` is `{ }` that re-runs whenever a referenced subject or variant changes. It works on **widget** attributes (including `style_*` locals) and on a component instance's **variant** attributes. Not in `<styles>` (initialized once) and not on a component's own props or slots. It must reference at least one subject or variant, and inside it only `type="subject"` props may appear; other props are an error. A failed re-evaluation (e.g. `/0`) keeps the previous value.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: A non-subject $prop inside @{ } is described as "an error", which contradicts both this paragraph's own soft-failure theme and the authoritative docs/syntax/data-binding.mdx, where it is "skipped with a warning, but the widget is still created normally". Consider rewording to "skipped with a warning" so users don't expect a hard failure that doesn't occur.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At AGENTS.md, line 195:

<comment>A non-subject `$prop` inside `@{ }` is described as "an error", which contradicts both this paragraph's own soft-failure theme and the authoritative docs/syntax/data-binding.mdx, where it is "skipped with a warning, but the widget is still created normally". Consider rewording to "skipped with a warning" so users don't expect a hard failure that doesn't occur.</comment>

<file context>
@@ -161,15 +181,46 @@ Only `int`, `string` and `float` are supported.
 States: `default`, `checked`, `focused`, `focus_key`, `edited`, `hovered`, `pressed`, `scrolled`, `disabled`.
 Common flags: `hidden`, `clickable`, `checkable`, `scrollable`, `floating`, `ignore_layout`.
 
+`@{ }` is `{ }` that re-runs whenever a referenced subject or variant changes. It works on **widget** attributes (including `style_*` locals) and on a component instance's **variant** attributes. Not in `<styles>` (initialized once) and not on a component's own props or slots. It must reference at least one subject or variant, and inside it only `type="subject"` props may appear; other props are an error. A failed re-evaluation (e.g. `/0`) keeps the previous value.
+
+To give each instance its own data, declare `<prop name="temp" type="subject"/>` and pass a subject name at the call site: `<room_card temp="subject_kitchen"/>`.
</file context>


To give each instance its own data, declare `<prop name="temp" type="subject"/>` and pass a subject name at the call site: `<room_card temp="subject_kitchen"/>`.

**Binding beats callbacks.** A radio group, a theme switch, or a value readout needs no C at all: write the subject with `subject_set_int_event`, read it with `bind_state_if_eq`.

## Variants

A component's named visual states, declared in `<api>`. Per-instance and reactive, so they are the component-scoped counterpart of global subjects.

```xml
<api>
<variants>
<variant name="size" options="small large" default="small"/>
<variant name="tone" options="normal danger" default="normal"/>
</variants>
</api>
<view extends="lv_button">
<style name="style_normal"/>
<bind_style name="style_danger" subject="tone" ref_value="danger"/>
<bind_style name="style_large" subject="size" ref_value="large"/>
<lv_label text="Subtitle" hidden="@{size == small}"/>
</view>
```

Read a variant with `<bind_style subject="<variant>" ref_value="<option>">` (preferred for anything visual) or in `@{ }`, where the variant name is the current option and an option name is a constant.

Pick an option on the instance, `<my_badge size="large" tone="@{subject_level > 100 ? danger : normal}"/>`, or from C with the exported `my_badge_set_size(obj, MY_BADGE_SIZE_LARGE)` (`lv_xml_set_variant(obj, "size", "large")` at runtime). An unknown option on the instance falls back to `default` with a warning; in `lv_xml_set_variant()` it's refused and the option is left unchanged. Option names must be unique across a component's variants, a variant name shadows a same-named prop/const/subject, and reordering `options` breaks already exported C.

## Events

All are children of a widget, all take `trigger` (`clicked`, `long_pressed`, `value_changed`, ...):
Expand All @@ -196,14 +247,16 @@ Evaluated **once at creation**, not reactive. For anything that changes at runti
<lv_obj style_bg_color="{is_on ? 0x00ff00 : 0x333333}"/>
```

`.` concatenates. Strings use single quotes. There is no `&&` or `||`, comparisons cannot be chained, and ternaries cannot be nested.
`.` concatenates. Strings use single quotes. Comparisons cannot be chained (`a < b < c`) and ternaries cannot be nested.

`&&` and `||` exist, but `&` and `<` must be XML-escaped in an attribute value, so prefer the `and` / `or` keywords: `hidden="{a > 10 and a &lt;= 30}"`. Both sides are always evaluated, there is no short-circuiting.

## Animations

```xml
<animations>
<timeline name="timeline_load">
<animation prop="translate_x" target="self" start="-30" end="0" duration="500"/>
<animation prop="translate_x" target="self" start="-30" end="0" duration="500" easing="ease_out"/>
<animation prop="opa" target="label" start="0" end="255" duration="500" delay="200"/>
<include_timeline target="icon" timeline="show_up" delay="300"/>
</timeline>
Expand All @@ -212,6 +265,8 @@ Evaluated **once at creation**, not reactive. For anything that changes at runti

`target="self"` is the `view`; anything else is matched against a child's `name`. Play with `<play_timeline_event>`.

`easing` (on `<animation>` and `<transition>`) is `linear` (default), `ease_in`, `ease_out`, `ease_in_out`, `overshoot`, `bounce`, `step`, `bezier(x1 y1 x2 y2)` with `x` in `0..1`, or a callback registered with `lv_xml_register_easing_cb()`.

## Slots

Expose an internal object as a place where the caller can add children:
Expand All @@ -237,7 +292,8 @@ The slot target is `<component_name-slot_name>`, and you can set normal object p

- Inventing an attribute instead of reading `lvgl_widgets_xml/`.
- Putting `$prop` into a `<style>`. Use a local style property.
- Expecting `{ }` to update at runtime. It does not, that's data binding.
- Expecting `{ }` to update at runtime. It does not, write `@{ }` for that.
- Putting a non-subject `$prop` inside `@{ }`, or `@{ }` on a component's own prop or in a `<style>`. None of them can update.
- Using `bind_state_*` with a `flag=` attribute, or `bind_flag_*` with `state=`.
- `screen_load_event` on a screen that isn't `permanent="true"`.
- Hard-coding `pad="8"` and `bg_color="0x1E232E"` when `#space_md` and `#color_dark_panel` already exist in `globals.xml`.
Expand Down
71 changes: 63 additions & 8 deletions docs/syntax/animations.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ Create smooth, professional animations for your UI components using LVGL's timel

## Overview

XML animations are built on timeline animations that organize multiple animation steps into coordinated sequences.
XML animations are used to animate one or more widgets and their properties. They are built from timeline animations that organize multiple animation steps into coordinated sequences.

Timelines are composed of simple animations. For example: *"change the `bg_opa` of `my_button_2` from 0 to 255 in 500 ms."*

Each Component can define its own timeline animations, which can then be played by the Component itself or by any parent Components.

Style [`<transition>`](./styles#transitions)s can animate widgets too. The difference is what starts them: a timeline is played on demand, while a transition runs when the widget's state changes, for example from default to pressed.

## Defining Timelines

Timelines can be defined inside [`<screen>`](./screens)s and [`<component>`](./components)s.
Timelines can be defined inside [`<screen>`](./screens)s and [`<component>`](./components)s.

Example:

Expand All @@ -31,9 +33,9 @@ Example:

<!-- Shake horizontally -->
<timeline name="shake" repeat_count="infinite" repeat_delay="200">
<animation prop="translate_x" target="self" start="0" end="-30" duration="150"/>
<animation prop="translate_x" target="self" start="-30" end="30" duration="300" delay="150"/>
<animation prop="translate_x" target="self" start="30" end="0" duration="150" delay="450"/>
<animation prop="translate_x" target="self" start="0" end="-30" duration="150" easing="ease_in_out"/>
<animation prop="translate_x" target="self" start="-30" end="30" duration="300" delay="150" easing="ease_in_out"/>
<animation prop="translate_x" target="self" start="30" end="0" duration="150" delay="450" easing="ease_in_out"/>
</timeline>
</animations>

Expand All @@ -54,7 +56,7 @@ Inside `<animations>`, you can define `<timeline>`s with unique names that you c
- `repeat_count` - How many times the whole timeline repeats. Use a number, or `infinite` to loop forever. Default is `1`.
- `repeat_delay` - Delay in milliseconds between repetitions. Default is `0`.

### Simple Animations
### Simple Animations

Within each `timeline`, add individual `<animation>` elements to describe each step. The following properties are supported:

Expand All @@ -63,10 +65,63 @@ Within each `timeline`, add individual `<animation>` elements to describe each s
- `target` - Name of the UI element to animate. `self` refers to the root element of the Component (the `view`).
- `start` - Start value (integer only).
- `end` - End value (integer only).
- `duration` - Duration of the animation in milliseconds.
- `duration` - Duration of the animation in milliseconds. Default is `1000`.
- `delay` - Delay before starting in milliseconds. Default is 0.
- `early_apply` - If `true`, the start value is applied immediately, even during the delay. Default is `false`.
- `easing` - The animation path, e.g. `ease_out`. See below. Default is `linear`.

`start`, `end`, `duration` and `delay` accept constants and [expressions](./evaluate#outside-the-view) of them:

```xml
<consts>
<int name="slide_ms" value="500"/>
<int name="slide_dist" value="30"/>
</consts>

<animation prop="translate_x" target="self" start="{-slide_dist}" end="0"
duration="{slide_ms}" delay="{slide_ms / 2}"/>
```

### Easing

The `easing` property tells how the value should progress from `start` to `end` during the `duration`. These built-in paths can be used:

- `linear` - Constant speed. This is the default.
- `ease_in` - Slow start.
- `ease_out` - Slow end.
- `ease_in_out` - Slow start and end.
- `overshoot` - Goes above the end value and settles back.
- `bounce` - Bounces back a few times at the end.
- `step` - Stays at the start value and jumps to the end value at the very end.

```xml
<animation prop="translate_y" target="self" start="-30" end="0" duration="500" easing="ease_out"/>
```

For full control, `bezier(x1 y1 x2 y2)` describes a cubic bezier curve with its two control points, just like `cubic-bezier()` in CSS:

```xml
<animation prop="translate_y" target="self" start="-30" end="0" duration="500"
easing="bezier(0.34 1.56 0.64 1)"/>
```

`x1` and `x2` need to be in the `0..1` range, while `y1` and `y2` can be outside it to overshoot. At most four decimals are used from each value. Ready to use curves can be picked from e.g. [easings.net](https://easings.net).

Finally, an animation path implemented in C can be referenced by its function name:

```xml
<animation prop="translate_y" target="self" start="-30" end="0" duration="500" easing="my_easing"/>
```

```c
int32_t my_easing(const lv_anim_t * a)
{
/*Return the value to apply now, interpolating between start_value and end_value*/
return lv_map(a->act_time, 0, a->duration, a->start_value, a->end_value);
}
```

The exported code calls this function directly, so it can't be `static` and it has to be visible where the generated file is compiled.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: This change rewrites the C-easing documentation to claim that an easing path is referenced directly by its function name (non-static, no registration, plain C function the generated file calls). That directly contradicts the same repo's AGENTS.md (docs/../AGENTS.md, Animations section), which still states: "easing ... or a callback registered with lv_xml_register_easing_cb()." It also silently removes the previously documented runtime path — the lv_xml_register_easing_cb(NULL, "my_easing", my_easing); call and the note that the name is resolved while XML is being parsed and must be registered before use. Because both documents ship in this PR series, one of them is now stale, and a reader relying on either will get contradictory instructions (especially for runtime XML loading vs. exported code). Please reconcile the two documents so they describe the same mechanism — if the implementation still requires lv_xml_register_easing_cb() for any loading path, keep that guidance; if easing is now always resolved by symbol/function name, update AGENTS.md to match.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/syntax/animations.mdx, line 112:

<comment>This change rewrites the C-easing documentation to claim that an easing path is referenced directly by its function name (non-static, no registration, plain C function the generated file calls). That directly contradicts the same repo's AGENTS.md (docs/../AGENTS.md, Animations section), which still states: "easing ... or a callback registered with `lv_xml_register_easing_cb()`." It also silently removes the previously documented runtime path — the `lv_xml_register_easing_cb(NULL, "my_easing", my_easing);` call and the note that the name is resolved while XML is being parsed and must be registered before use. Because both documents ship in this PR series, one of them is now stale, and a reader relying on either will get contradictory instructions (especially for runtime XML loading vs. exported code). Please reconcile the two documents so they describe the same mechanism — if the implementation still requires `lv_xml_register_easing_cb()` for any loading path, keep that guidance; if easing is now always resolved by symbol/function name, update AGENTS.md to match.</comment>

<file context>
@@ -95,26 +95,21 @@ For full control, `bezier(x1 y1 x2 y2)` describes a cubic bezier curve with its

-Register the callback before the XML that uses it, as the name is resolved while the XML is being parsed. In the exported code the name is emitted as a plain C function reference, so the function has to be visible to the generated file.
+The exported code calls this function directly, so it can't be static and it has to be visible where the generated file is compiled.

Include External Timelines

</file context>


</details>


### Include External Timelines

Expand Down Expand Up @@ -96,4 +151,4 @@ The created timeline instances and their names are saved in the Component's inst

When a `play_timeline_event` is added to a UI element, the target and timeline names are saved as strings. Pointers cannot be used because the event can reference UI elements that will be created only later in the `view`.

Finally, when the play timeline event is triggered, the selected timeline is retrieved by its name from the target and started according to the other parameters (reverse, delay, and so on).
Finally, when the play timeline event is triggered, the selected timeline is retrieved by its name from the target and started according to the other parameters (reverse, delay, and so on).
65 changes: 53 additions & 12 deletions docs/syntax/api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ When a component's XML is converted to C files, only a `create` function is gene

### Referencing Properties

Props are simply forwarded to Widget or Component APIs. For example, if a component has `<prop name="button_label" type="string"/>`, it can be used in a label child as `<lv_label text="$button_label"/>`.
Props are simply forwarded to Widget or Component APIs. For example, if a component has `<prop name="button_label" type="string"/>`, it can be used in a label child as `<lv_label text="{button_label}"/>`.

In the code generated by LVGL's UI Editor, these are passed as arguments in create/set functions.

Expand All @@ -45,6 +45,8 @@ lv_obj_t * my_component_create(lv_obj_t * parent, int32_t prop1, const char * pr

These properties are set once at creation time, and there are no specific `set` functions to modify the property later. LVGL's general API can still be used to modify any widget in the component, but no dedicated API functions are generated.

For a value that has to change after creation, use a [variant](./variants) (a named set of options declared in `<api>`, with a generated setter) or a [subject](./data-binding).

### Slots

With the help of a "slot," any UI element in the component can be easily exposed as a parent where children can be created later.
Expand All @@ -59,8 +61,7 @@ Slots are very useful for creating components like screen templates where users

It's also possible to change the basic `lv_obj` properties on the slots, such as `hidden="true"`, `style_bg_color-pressed="0xff0000"`, `checked="true"`, `width="100"`, etc.


#### Slots Example
The example below shows it in practice:

```xml
<!-- simple_screen.xml -->
Expand All @@ -79,7 +80,7 @@ It's also possible to change the basic `lv_obj` properties on the slots, such as
<!-- main_screen.xml -->
<component>
<styles>
<styles name="style_thin_border" border_width="1" border_color="0x800"/>
<style name="style_thin_border" border_width="1" border_color="0x800"/>
</styles>
<view extends="simple_screen" width="100%">

Expand All @@ -98,6 +99,46 @@ It's also possible to change the basic `lv_obj` properties on the slots, such as
</component>
```

### Variants

Besides `<prop>`s, a Component can declare **variants**: named lists of options that can be changed after creation. Unlike properties, a variant keeps its value per instance, so `<bind_style>` and [data bindings](./data-binding) can follow it at runtime.

`badge.xml`

```xml
<component>
<api>
<variants>
<variant name="tone" options="normal danger" default="normal"/>
</variants>
</api>

<styles>
<style name="style_danger" bg_color="0xff0000" text_color="0xffffff"/>
</styles>

<view extends="lv_obj" width="content" height="content">
<bind_style name="style_danger" if="tone == danger"/>
<lv_label name="label" text="Ready"/>
<lv_image name="alert_icon" src="img_alert" hidden="@{tone == normal}"/>
</view>
</component>
```

`screen1.xml`

```xml
<screen>
<view flex_flow="row">
<badge/> <!-- `normal`, the default -->
<badge tone="danger"/>
<badge tone="@{subject_alarm_count > 0 ? danger : normal}"/>
</view>
</screen>
```

Learn more about variants on their [dedicated page](./variants).

### Limitations

Component APIs support only simple properties that are forwarded. The following Widget API features cannot be used for Components:
Expand Down Expand Up @@ -244,7 +285,7 @@ Used in a view:
<my_widget width="100px">
<my_widget-indicator name="indic1" color="0xff0000" max_value="120" value="30"/>
</my_widget>
</view>
</view>
```

LVGL's UI Editor generates:
Expand All @@ -270,11 +311,11 @@ Used for internal or implicit elements:
Used in a view:

```xml
<view>
<view>
<my_widget width="100px">
<my_widget-control_button name="btn1" index="3" title="Hello"/>
</my_widget>
</view>
</view>
```

LVGL's UI Editor generates:
Expand All @@ -301,11 +342,11 @@ Used for indexed access, like setting values in a table:
Used in a view:

```xml
<view>
<view>
<my_widget width="100px">
<my_widget-item index="3" icon="image1" color="0xff0000"/>
</my_widget>
</view>
</view>
```

LVGL's UI Editor generates:
Expand All @@ -330,15 +371,15 @@ Used to describe custom API functions with a custom name. Custom elements can ha
Used in a view:

```xml
<view>
<view>
<my_widget width="100px">
<my_widget-bind_color subject="subject_1" new_color="0xff0000" ref_value="15"/>
</my_widget>
</view>
</view>
```

LVGL's UI Editor generates:

```c
void my_widget_bind_color(lv_obj_t * parent, lv_subject_t * subject, lv_color_t color, int32_t ref_value);
```
```
Loading
Loading