mirror of
https://github.com/lvgl/lvgl.git
synced 2026-10-02 10:23:47 +08:00
feat(subject): deprecate lv_obj_bind_flag/state and add lv_obj_bind_<type> (#10541)
Co-authored-by: Gabor Kiss-Vamosi <kisvegabor@gmail.com>
This commit is contained in:
co-authored by
Gabor Kiss-Vamosi
parent
371b94cf9c
commit
e21695f6cb
@@ -72,7 +72,7 @@ and ask anything!
|
||||
- 30+ built-in [Widgets](https://lvgl.io/docs/open/widgets): Button, Label, Slider, Chart, Keyboard, Meter, Arc, Table, and many more.
|
||||
- Flexible [Style system](https://lvgl.io/docs/open/common-widget-features/styles) with 100+ style properties to customize any part of the widgets in any state.
|
||||
- [Flexbox](https://lvgl.io/docs/open/common-widget-features/layouts/flex) and [Grid](https://lvgl.io/docs/open/common-widget-features/layouts/grid)-like layout engines to automatically size and position the widgets responsively.
|
||||
- [Data bindings](https://lvgl.io/docs/open/main-modules/observer) to easily connect the UI with the application.
|
||||
- [Data bindings](https://lvgl.io/docs/open/main-modules/data_binding/index.html) to easily connect the UI with the application.
|
||||
- Supports Mouse, Touchpad, Keypad, Keyboard, External buttons, Encoder [Input devices](https://lvgl.io/docs/open/main-modules/indev).
|
||||
- [Multiple display](https://lvgl.io/docs/open/main-modules/display/overview#how-many-displays-can-lvgl-use) support.
|
||||
|
||||
|
||||
@@ -193,6 +193,13 @@ static void create_control_panel(lv_obj_t * viewer)
|
||||
create_antialiasing_panel(control_panel);
|
||||
}
|
||||
|
||||
/*Disable a manual camera slider while a predefined camera (index > 0) is selected*/
|
||||
static void disable_while_camera_selected_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
lv_obj_t * obj = lv_observer_get_target_obj(observer);
|
||||
lv_obj_set_state(obj, LV_STATE_DISABLED, lv_subject_get_int(subject) > 0);
|
||||
}
|
||||
|
||||
static void create_camera_panel(lv_obj_t * panel, lv_obj_t * viewer)
|
||||
{
|
||||
|
||||
@@ -243,9 +250,9 @@ static void create_camera_panel(lv_obj_t * panel, lv_obj_t * viewer)
|
||||
lv_slider_set_max_value(distance_slider, 25);
|
||||
style_slider(distance_slider, SLIDER_COLOR);
|
||||
|
||||
lv_obj_bind_state_if_gt(yaw_slider, &camera_subject, LV_STATE_DISABLED, 0);
|
||||
lv_obj_bind_state_if_gt(pitch_slider, &camera_subject, LV_STATE_DISABLED, 0);
|
||||
lv_obj_bind_state_if_gt(distance_slider, &camera_subject, LV_STATE_DISABLED, 0);
|
||||
lv_subject_add_observer_obj(&camera_subject, disable_while_camera_selected_observer_cb, yaw_slider, NULL);
|
||||
lv_subject_add_observer_obj(&camera_subject, disable_while_camera_selected_observer_cb, pitch_slider, NULL);
|
||||
lv_subject_add_observer_obj(&camera_subject, disable_while_camera_selected_observer_cb, distance_slider, NULL);
|
||||
}
|
||||
|
||||
static void create_animation_panel(lv_obj_t * panel, lv_obj_t * viewer)
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@
|
||||
- יותר מ-30 [רכיבי ממשק](https://lvgl.io/docs/open/widgets) מובנים: Button, Label, Slider, Chart, Keyboard, Meter, Arc, Table ועוד רבים.
|
||||
- [מערכת סגנונות](https://lvgl.io/docs/open/common-widget-features/styles) גמישה עם יותר מ-100 מאפייני סגנון להתאמה אישית של כל חלק ברכיבי הממשק בכל מצב.
|
||||
- מנועי פריסה בסגנון [Flexbox](https://lvgl.io/docs/open/common-widget-features/layouts/flex) ו-[Grid](https://lvgl.io/docs/open/common-widget-features/layouts/grid) לקביעת גודל ומיקום אוטומטיים ורספונסיביים של רכיבי הממשק.
|
||||
- [קישורי נתונים](https://lvgl.io/docs/open/main-modules/observer) לחיבור קל של ממשק המשתמש עם היישום.
|
||||
- [קישורי נתונים](https://lvgl.io/docs/open/main-modules/data_binding) לחיבור קל של ממשק המשתמש עם היישום.
|
||||
- תמיכה בעכבר, במשטח מגע, בלוח מקשים, במקלדת, בלחצנים חיצוניים וב-Encoder [התקני קלט](https://lvgl.io/docs/open/main-modules/indev).
|
||||
- תמיכה ב[תצוגות מרובות](https://lvgl.io/docs/open/main-modules/display/overview#how-many-displays-can-lvgl-use).
|
||||
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@
|
||||
- 30種類以上の組み込み [ウィジェット](https://lvgl.io/docs/open/widgets): Button、Label、Slider、Chart、Keyboard、Meter、Arc、Tableなど多数。
|
||||
- 100以上のスタイルプロパティを備えた柔軟な [スタイルシステム](https://lvgl.io/docs/open/common-widget-features/styles) で、あらゆる状態のウィジェットのあらゆる部分をカスタマイズできます。
|
||||
- [Flexbox](https://lvgl.io/docs/open/common-widget-features/layouts/flex) と [Grid](https://lvgl.io/docs/open/common-widget-features/layouts/grid) 風のレイアウトエンジンで、ウィジェットのサイズと位置をレスポンシブに自動調整します。
|
||||
- UIとアプリケーションを簡単に接続する [データバインディング](https://lvgl.io/docs/open/main-modules/observer)。
|
||||
- UIとアプリケーションを簡単に接続する [データバインディング](https://lvgl.io/docs/open/main-modules/data_binding/index.html)。
|
||||
- Mouse、Touchpad、Keypad、Keyboard、外部ボタン、Encoderの [入力デバイス](https://lvgl.io/docs/open/main-modules/indev) に対応。
|
||||
- [マルチディスプレイ](https://lvgl.io/docs/open/main-modules/display/overview#how-many-displays-can-lvgl-use) 対応。
|
||||
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@
|
||||
- 30개 이상의 내장 [위젯](https://lvgl.io/docs/open/widgets): Button, Label, Slider, Chart, Keyboard, Meter, Arc, Table 등 다수.
|
||||
- 어떤 상태에서든 위젯의 모든 부분을 커스터마이징할 수 있는 100개 이상의 스타일 속성을 갖춘 유연한 [스타일 시스템](https://lvgl.io/docs/open/common-widget-features/styles).
|
||||
- 위젯의 크기와 위치를 반응형으로 자동 조정하는 [Flexbox](https://lvgl.io/docs/open/common-widget-features/layouts/flex) 및 [Grid](https://lvgl.io/docs/open/common-widget-features/layouts/grid) 유사 레이아웃 엔진.
|
||||
- UI를 애플리케이션과 손쉽게 연결하는 [데이터 바인딩](https://lvgl.io/docs/open/main-modules/observer).
|
||||
- UI를 애플리케이션과 손쉽게 연결하는 [데이터 바인딩](https://lvgl.io/docs/open/main-modules/data_binding).
|
||||
- Mouse, Touchpad, Keypad, Keyboard, 외부 버튼, Encoder [입력 장치](https://lvgl.io/docs/open/main-modules/indev)를 지원합니다.
|
||||
- [다중 디스플레이](https://lvgl.io/docs/open/main-modules/display/overview#how-many-displays-can-lvgl-use) 지원.
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ e perguntar qualquer coisa!
|
||||
- Mais de 30 [Widgets](https://lvgl.io/docs/open/widgets) integrados: Button, Label, Slider, Chart, Keyboard, Meter, Arc, Table e muitos outros.
|
||||
- [Sistema de estilos](https://lvgl.io/docs/open/common-widget-features/styles) flexível com mais de 100 propriedades de estilo para personalizar qualquer parte dos widgets em qualquer estado.
|
||||
- Motores de layout semelhantes a [Flexbox](https://lvgl.io/docs/open/common-widget-features/layouts/flex) e [Grid](https://lvgl.io/docs/open/common-widget-features/layouts/grid) para dimensionar e posicionar os widgets de forma responsiva automaticamente.
|
||||
- [Data bindings](https://lvgl.io/docs/open/main-modules/observer) para conectar facilmente a UI à aplicação.
|
||||
- [Data bindings](https://lvgl.io/docs/open/main-modules/data_binding) para conectar facilmente a UI à aplicação.
|
||||
- Suporta Mouse, Touchpad, Keypad, Keyboard, botões externos e Encoder [dispositivos de entrada](https://lvgl.io/docs/open/main-modules/indev).
|
||||
- Suporte a [múltiplos displays](https://lvgl.io/docs/open/main-modules/display/overview#how-many-displays-can-lvgl-use).
|
||||
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@
|
||||
- 30 多种内置[控件](https://lvgl.io/docs/open/widgets):Button、Label、Slider、Chart、Keyboard、Meter、Arc、Table 等等。
|
||||
- 灵活的[样式系统](https://lvgl.io/docs/open/common-widget-features/styles),拥有 100 多个样式属性,可在任何状态下定制控件的任何部分。
|
||||
- 类似 [Flexbox](https://lvgl.io/docs/open/common-widget-features/layouts/flex) 和 [Grid](https://lvgl.io/docs/open/common-widget-features/layouts/grid) 的布局引擎,可自动地响应式调整控件的尺寸和位置。
|
||||
- [数据绑定](https://lvgl.io/docs/open/main-modules/observer),可轻松将 UI 与应用程序连接起来。
|
||||
- [数据绑定](https://lvgl.io/docs/open/main-modules/data_binding),可轻松将 UI 与应用程序连接起来。
|
||||
- 支持鼠标、触摸板、小键盘、键盘、外部按钮、编码器等[输入设备](https://lvgl.io/docs/open/main-modules/indev)。
|
||||
- 支持[多显示屏](https://lvgl.io/docs/open/main-modules/display/overview#how-many-displays-can-lvgl-use)。
|
||||
|
||||
|
||||
@@ -570,6 +570,33 @@ Calling the following functions with `NULL` as the display parameter is deprecat
|
||||
- <ApiLink name="lv_sysmon_show_memory"/>
|
||||
- <ApiLink name="lv_sysmon_hide_memory"/>
|
||||
|
||||
### lv_observer
|
||||
|
||||
- The `lv_obj_bind_flag_if_*` and `lv_obj_bind_state_if_*` families
|
||||
(`_eq`/`_not_eq`/`_gt`/`_ge`/`_lt`/`_le`) are deprecated. For a boolean subject,
|
||||
bind a widget flag with `lv_obj_bind_bool()` — the dedicated per-flag setters
|
||||
(`lv_obj_set_hidden()`, `lv_obj_set_clickable()`, …) can be passed directly as
|
||||
the callback. For a state, or for any comparison against a reference value, add
|
||||
a custom observer with `lv_subject_add_observer_obj()`:
|
||||
```c
|
||||
/* Before: hide a widget while a 0/1 subject is non-zero */
|
||||
lv_obj_bind_flag_if_not_eq(obj, &subject, LV_OBJ_FLAG_HIDDEN, 0);
|
||||
|
||||
/* After */
|
||||
lv_obj_bind_bool(obj, &subject, lv_obj_set_hidden);
|
||||
|
||||
/* Before: disable a widget while a subject is greater than 80 */
|
||||
lv_obj_bind_state_if_gt(obj, &subject, LV_STATE_DISABLED, 80);
|
||||
|
||||
/* After */
|
||||
static void disabled_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
lv_obj_t * obj = lv_observer_get_target_obj(observer);
|
||||
lv_obj_set_state(obj, LV_STATE_DISABLED, lv_subject_get_int(subject) > 80);
|
||||
}
|
||||
lv_subject_add_observer_obj(&subject, disabled_observer_cb, obj, NULL);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Widgets
|
||||
|
||||
@@ -21,7 +21,7 @@ lv_obj_set_style_bg_color(slider, lv_color_red(), LV_PART_INDICATOR | LV_STATE_F
|
||||
## Binding Local Styles
|
||||
|
||||
By using <ApiLink name="lv_obj_bind_style_prop" />, it's possible to bind a style property
|
||||
to a [Subject](/main-modules/observer/observer)'s value.
|
||||
to a [Subject](/main-modules/data_binding/subjects)'s value.
|
||||
|
||||
It's a great way to map every slider's color or opacity to a subject and control it
|
||||
externally.
|
||||
|
||||
@@ -146,7 +146,7 @@ notified. There are 3 options to do this:
|
||||
## Binding Styles
|
||||
|
||||
By using <ApiLink name="lv_obj_bind_style" />, it's possible to add a style to a Widget
|
||||
but enable it only if a [Subject](/main-modules/observer/observer)'s value is equal to
|
||||
but enable it only if a [Subject](/main-modules/data_binding/subjects)'s value is equal to
|
||||
a reference value.
|
||||
|
||||
It's a great way to implement a light/dark theme switch by normally adding the styles
|
||||
|
||||
@@ -247,4 +247,4 @@ lv_obj_set_y(slider1, 30);
|
||||
lv_subject_set_int(&subject1, 30);
|
||||
```
|
||||
|
||||
Learn more on the documentation page of [Observers](/main-modules/observer/observer).
|
||||
Learn more on the documentation page of [Observers](/main-modules/data_binding/observers).
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
---
|
||||
title: Observer
|
||||
title: Data binding
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"title": "Data binding",
|
||||
"pages": [
|
||||
"overview",
|
||||
"subjects",
|
||||
"observers",
|
||||
"examples"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
---
|
||||
title: Observers
|
||||
description: Learn more about Observers.
|
||||
---
|
||||
|
||||
An **Observer** watches one [Subject](/main-modules/data_binding/subjects) and runs a
|
||||
callback whenever that Subject's value changes. When you subscribe, the callback is
|
||||
also called once immediately, so the Observer can pick up the current value right
|
||||
away.
|
||||
|
||||
A callback always has the same signature:
|
||||
|
||||
```c
|
||||
static void temperature_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
int32_t t = lv_subject_get_int(subject);
|
||||
LV_LOG_USER("Temperature is now %d", t);
|
||||
}
|
||||
```
|
||||
|
||||
## Subscribing
|
||||
|
||||
The simplest way to subscribe is <ApiLink name="lv_subject_add_observer" display="lv_subject_add_observer()" />.
|
||||
The third argument is free-form user data passed to the callback:
|
||||
|
||||
```c
|
||||
lv_subject_add_observer(&temperature, temperature_cb, NULL);
|
||||
```
|
||||
|
||||
Often the Observer needs to update a specific object. Pass a Widget with
|
||||
<ApiLink name="lv_subject_add_observer_obj" display="lv_subject_add_observer_obj()" />,
|
||||
then read it back in the callback with <ApiLink name="lv_observer_get_target_obj" display="lv_observer_get_target_obj()" />.
|
||||
|
||||
```c
|
||||
lv_subject_add_observer_obj(&temperature, label_cb, my_label, NULL);
|
||||
|
||||
static void label_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
lv_obj_t * label = lv_observer_get_target_obj(observer);
|
||||
lv_label_set_text_fmt(label, "%d °C", lv_subject_get_int(subject));
|
||||
}
|
||||
```
|
||||
|
||||
In a generic case any other pointer can be passed as target with
|
||||
<ApiLink name="lv_subject_add_observer_with_target" display="lv_subject_add_observer_with_target()" /> and the
|
||||
target can be read back with <ApiLink name="lv_observer_get_target" display="lv_observer_get_target()" />.
|
||||
|
||||
## Unsubscribing
|
||||
|
||||
An Observer bound to a Widget using <ApiLink name="lv_subject_add_observer_obj" display="lv_subject_add_observer_obj()" /> is removed automatically when that Widget is deleted, so in this case you don't need to do anything. Otherwise:
|
||||
|
||||
- <ApiLink name="lv_observer_delete" display="lv_observer_delete(observer)" /> removes a
|
||||
single Observer (use the pointer returned when subscribing).
|
||||
- <ApiLink name="lv_obj_remove_from_subject" display="lv_obj_remove_from_subject(widget, subject)" />
|
||||
removes a Widget's Observers (`subject` may be `NULL` to detach from all).
|
||||
- <ApiLink name="lv_subject_deinit" display="lv_subject_deinit(subject)" /> disconnects a
|
||||
Subject from all of its Observers.
|
||||
|
||||
## Binding a Widget to a Subject
|
||||
|
||||
Besides subscribing by hand, a Widget's property can be bound to a Subject directly.
|
||||
Each binding is just an Observer, so it too is removed automatically when the Widget
|
||||
is deleted.
|
||||
|
||||
### Flags
|
||||
|
||||
To drive a flag from a `0`/non-zero Subject, use
|
||||
<ApiLink name="lv_obj_bind_bool" display="lv_obj_bind_bool()" />. Because the callback
|
||||
takes a `bool`, a dedicated per-flag setter can be passed straight in:
|
||||
|
||||
```c
|
||||
lv_obj_bind_bool(widget, &subject, lv_obj_set_hidden); /* hidden while non-zero */
|
||||
```
|
||||
|
||||
### States
|
||||
|
||||
There's no dedicated setter for a state, and states often depend on a comparison, so
|
||||
bind them with a small Observer of your own:
|
||||
|
||||
```c
|
||||
static void disabled_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
lv_obj_t * obj = lv_observer_get_target_obj(observer);
|
||||
lv_obj_set_state(obj, LV_STATE_DISABLED, lv_subject_get_int(subject) > 80);
|
||||
}
|
||||
|
||||
lv_subject_add_observer_obj(&subject, disabled_cb, widget, NULL);
|
||||
```
|
||||
|
||||
<Callout type="warning">
|
||||
The older `lv_obj_bind_flag_if_*` and `lv_obj_bind_state_if_*` functions are
|
||||
**deprecated** in favor of the two approaches above.
|
||||
</Callout>
|
||||
|
||||
### Checked state
|
||||
|
||||
<ApiLink name="lv_obj_bind_checked" display="lv_obj_bind_checked()" /> creates a
|
||||
**two-way** binding: the Widget's checked state follows the Subject (checked while
|
||||
non-zero), and clicking the Widget writes `1` or `0` back to the Subject. The Widget
|
||||
should have <ApiLink name="lv_obj_set_checkable" display="lv_obj_set_checkable(obj, true)"/>
|
||||
to make it checkable.
|
||||
|
||||
```c
|
||||
lv_obj_bind_checked(checkbox, &subject);
|
||||
```
|
||||
|
||||
<LvglExample name="lv_example_checkbox_bind_checked" path="widgets/checkbox/lv_example_checkbox_bind_checked" />
|
||||
|
||||
### Widget values
|
||||
|
||||
A Widget's main value or text is bound with the widget's own `lv_<widget>_bind_...()`
|
||||
function, described under "Data binding" on each widget's page. A few examples:
|
||||
|
||||
#### Label text
|
||||
|
||||
<ApiLink name="lv_label_bind_text" display="lv_label_bind_text()" /> keeps a Label's
|
||||
text in sync with a Subject, optionally through a `printf`-style format string.
|
||||
|
||||
<LvglExample name="lv_example_label_bind_text" path="widgets/label/lv_example_label_bind_text" />
|
||||
|
||||
#### Slider value
|
||||
|
||||
<ApiLink name="lv_slider_bind_value" display="lv_slider_bind_value()" /> ties a Slider's
|
||||
position to a Subject; dragging the Slider updates the Subject and vice versa.
|
||||
|
||||
<LvglExample name="lv_example_slider_bind_value" path="widgets/slider/lv_example_slider_bind_value" />
|
||||
|
||||
#### Arc value
|
||||
|
||||
<ApiLink name="lv_arc_bind_value" display="lv_arc_bind_value()" /> keeps an Arc's value
|
||||
in sync with a Subject.
|
||||
|
||||
<LvglExample name="lv_example_arc_bind_value" path="widgets/arc/lv_example_arc_bind_value" />
|
||||
|
||||
#### Drop-down value
|
||||
|
||||
<ApiLink name="lv_dropdown_bind_value" display="lv_dropdown_bind_value()" /> binds the
|
||||
selected option's index to a Subject.
|
||||
|
||||
<LvglExample name="lv_example_dropdown_bind_value" path="widgets/dropdown/lv_example_dropdown_bind_value" />
|
||||
|
||||
#### Roller value
|
||||
|
||||
<ApiLink name="lv_roller_bind_value" display="lv_roller_bind_value()" /> binds the
|
||||
selected option's index to a Subject.
|
||||
|
||||
<LvglExample name="lv_example_roller_bind_value" path="widgets/roller/lv_example_roller_bind_value" />
|
||||
@@ -0,0 +1,92 @@
|
||||
---
|
||||
title: Overview
|
||||
description: Get familiar with the data binding concept of LVGL.
|
||||
---
|
||||
|
||||
## Concept
|
||||
|
||||
The `lv_observer` module is an implementation of the Subject-Observer pattern used to
|
||||
realize data binding in LVGL.
|
||||
|
||||
A **Subject** is a value being "observed", usually a global variable. It can store an
|
||||
integer, string, color, or other basic type.
|
||||
|
||||
An **Observer** is attached (subscribed) to a Subject and gets notified whenever the
|
||||
Subject changes.
|
||||
|
||||
This pattern separates the data from the places that consume it. Imagine a Subject
|
||||
that stores the temperature of a room: the sensor sets the value and doesn't need to
|
||||
know who uses it. Maybe a Label Widget is subscribed to update its text, maybe a popup
|
||||
is shown when the temperature is too high, maybe an I/O pin is set to `1` to start
|
||||
heating when it drops too low. Each subscriber only has to know that the Subject
|
||||
exists; it can subscribe to it and unsubscribe when it is deleted or no longer
|
||||
interested.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
This section shows the basics briefly. Read the other pages in this section to learn
|
||||
all the details.
|
||||
|
||||
The following Subject types are supported:
|
||||
|
||||
- int
|
||||
- float (requires `LV_USE_FLOAT`)
|
||||
- string
|
||||
- color
|
||||
- pointer
|
||||
- group
|
||||
|
||||
You can create a Subject by using a static/global variable:
|
||||
|
||||
```c
|
||||
static lv_subject_t subject_2;
|
||||
lv_subject_init_int(&subject_2, 10); /*10 is the initial value*/
|
||||
```
|
||||
|
||||
Set a Subject's value like below. This also notifies all subscribed Observers when the value changes.
|
||||
|
||||
```c
|
||||
lv_subject_set_int(subject_1, 20);
|
||||
```
|
||||
|
||||
You can subscribe with an Observer callback:
|
||||
|
||||
```c
|
||||
void my_observer(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
printf("New value: %d\n", lv_subject_get_int(subject));
|
||||
}
|
||||
|
||||
lv_subject_add_observer(subject_1, my_observer, NULL); /*3rd arg is optional user data*/
|
||||
```
|
||||
|
||||
When you subscribe, the callback is called immediately to pick up the current value.
|
||||
|
||||
You can also subscribe together with a Widget. This way the Observer is removed
|
||||
automatically when the Widget is deleted. This is convenient, because you don't need
|
||||
to check whether the Widget still exists. While it exists it gets updated; once deleted
|
||||
it unsubscribes itself.
|
||||
|
||||
```c
|
||||
lv_subject_add_observer_obj(subject_1, my_observer, label_1, NULL);
|
||||
```
|
||||
|
||||
Some Widgets also offer an API to easily create one- or two-way bindings. For example:
|
||||
|
||||
```c
|
||||
/*Update the label when the Subject changes, using a format string*/
|
||||
lv_label_bind_text(label_1, subject_1, "%d °C");
|
||||
|
||||
/*Keep the Subject and the selected drop-down option in sync (two-way)*/
|
||||
lv_dropdown_bind_value(dropdown_1, &subject_2);
|
||||
```
|
||||
|
||||
When the subjects are no longer needed they can be deinitialized:
|
||||
|
||||
```c
|
||||
lv_subject_deinit(&subject_2);
|
||||
```
|
||||
|
||||
That's it in short. Continue reading to learn more about
|
||||
[Subjects](/main-modules/data_binding/subjects) and
|
||||
[Observers](/main-modules/data_binding/observers).
|
||||
@@ -0,0 +1,124 @@
|
||||
---
|
||||
title: Subjects
|
||||
description: Learn more about Subjects.
|
||||
---
|
||||
|
||||
A **Subject** is a value you can observe. When you change it, every
|
||||
[Observer](/main-modules/data_binding/observers) attached to it is notified
|
||||
automatically. A Subject can hold an integer, a float, a string, a pointer, a
|
||||
color, or a group of other Subjects.
|
||||
|
||||
Keep a Subject in a static or global variable; it has to stay alive as long as
|
||||
anything observes it.
|
||||
|
||||
## Creating a Subject
|
||||
|
||||
Initialize a Subject with the function for its type. The last argument is the
|
||||
starting value:
|
||||
|
||||
```c
|
||||
static lv_subject_t temperature;
|
||||
lv_subject_init_int(&temperature, 20); /* integer, starts at 20 */
|
||||
```
|
||||
|
||||
Strings need a buffer to live in (and an optional second buffer to remember the
|
||||
previous value):
|
||||
|
||||
```c
|
||||
static lv_subject_t title;
|
||||
static char buf[32];
|
||||
static char prev_buf[32];
|
||||
lv_subject_init_string(&title, buf, prev_buf, sizeof(buf), "Hello");
|
||||
```
|
||||
|
||||
The remaining types work the same way with <ApiLink name="lv_subject_init_float" display="lv_subject_init_float()" /> (requires <ApiLink name="LV_USE_FLOAT"/>),
|
||||
<ApiLink name="lv_subject_init_pointer" display="lv_subject_init_pointer()" /> and
|
||||
<ApiLink name="lv_subject_init_color" display="lv_subject_init_color()" />.
|
||||
|
||||
## Reading and Writing the Value
|
||||
|
||||
Write a value with `lv_subject_set_...()` (e.g. <ApiLink name="lv_subject_set_int"/>).
|
||||
|
||||
Most value types notify Observers only when the value changes, however
|
||||
pointer Subjects notify on every set, group Subjects notify when their members or the group list changes, and strings
|
||||
when the previous buffer is also set.
|
||||
|
||||
Read the current value back with `lv_subject_get_...()` (e.g. <ApiLink name="lv_subject_get_int"/>):
|
||||
|
||||
```c
|
||||
lv_subject_set_int(&temperature, 25); /* notifies observers */
|
||||
int32_t t = lv_subject_get_int(&temperature); /* -> 25 */
|
||||
```
|
||||
|
||||
Strings are copied into the Subject's own buffer, so update them with
|
||||
<ApiLink name="lv_subject_copy_string" display="lv_subject_copy_string()" />:
|
||||
|
||||
```c
|
||||
lv_subject_copy_string(&title, "Settings");
|
||||
```
|
||||
|
||||
Every type (except groups) also has a `lv_subject_get_previous_...()` function (e.g.
|
||||
<ApiLink name="lv_subject_get_previous_int" />) that returns the value from before the
|
||||
last change, which is handy when a callback needs to compare old and new.
|
||||
|
||||
## Limiting the Range
|
||||
|
||||
Integer and float Subjects can be clamped. Out-of-range values are pulled back in
|
||||
before the Observers are notified:
|
||||
|
||||
```c
|
||||
lv_subject_set_min_value_int(&temperature, 0);
|
||||
lv_subject_set_max_value_int(&temperature, 100);
|
||||
```
|
||||
|
||||
## Grouping Several Subjects
|
||||
|
||||
Sometimes one thing depends on several values at once. A **group** Subject ties them
|
||||
together, so an Observer fires whenever *any* member changes.
|
||||
|
||||
```c
|
||||
static lv_subject_t * list[3] = {&mode, &value, &unit};
|
||||
static lv_subject_t measurement;
|
||||
lv_subject_init_group(&measurement, list, 3);
|
||||
```
|
||||
|
||||
In the callback, reach the members with
|
||||
<ApiLink name="lv_subject_get_group_element" display="lv_subject_get_group_element()" />:
|
||||
|
||||
```c
|
||||
static void measurement_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
lv_obj_t * label = lv_observer_get_target(observer);
|
||||
int32_t mode = lv_subject_get_int(lv_subject_get_group_element(subject, 0));
|
||||
int32_t value = lv_subject_get_int(lv_subject_get_group_element(subject, 1));
|
||||
const char * unit = lv_subject_get_pointer(lv_subject_get_group_element(subject, 2));
|
||||
lv_label_set_text_fmt(label, "%s: %d %s", mode ? "Current" : "Voltage", value, unit);
|
||||
}
|
||||
```
|
||||
|
||||
## Changing a Subject from the UI
|
||||
|
||||
Instead of writing an event handler, you can let a Widget update a Subject directly.
|
||||
For example, toggle an integer between `0` and `1` when a button is clicked:
|
||||
|
||||
```c
|
||||
lv_obj_add_subject_toggle_event(button, &enabled, LV_EVENT_CLICKED);
|
||||
```
|
||||
|
||||
Other ready-made actions:
|
||||
|
||||
- <ApiLink name="lv_obj_add_subject_increment_event" display="lv_obj_add_subject_increment_event(obj, &subject, trigger, step)" />
|
||||
adds `step` to the value (use a negative `step` to subtract). It returns a
|
||||
descriptor you can adjust further with
|
||||
<ApiLink name="lv_obj_set_subject_increment_event_min_value" display="lv_obj_set_subject_increment_event_min_value()" />,
|
||||
<ApiLink name="lv_obj_set_subject_increment_event_max_value" display="lv_obj_set_subject_increment_event_max_value()" /> and
|
||||
<ApiLink name="lv_obj_set_subject_increment_event_rollover" display="lv_obj_set_subject_increment_event_rollover()" />.
|
||||
- <ApiLink name="lv_obj_add_subject_set_int_event" display="lv_obj_add_subject_set_int_event()" />,
|
||||
<ApiLink name="lv_obj_add_subject_set_float_event" display="lv_obj_add_subject_set_float_event()" /> (requires <ApiLink name="LV_USE_FLOAT"/>)
|
||||
and <ApiLink name="lv_obj_add_subject_set_string_event" display="lv_obj_add_subject_set_string_event()" />
|
||||
write a fixed value on the trigger.
|
||||
|
||||
<Callout type="warning">
|
||||
Do not de-initialize or delete a Subject while these events remain attached as their callbacks saved a pointer to the Subject.
|
||||
</Callout>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"timer",
|
||||
"animation",
|
||||
"fs",
|
||||
"observer",
|
||||
"data_binding",
|
||||
"draw",
|
||||
"translation"
|
||||
]
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"title": "Observer",
|
||||
"pages": [
|
||||
"observer",
|
||||
"observer_examples"
|
||||
]
|
||||
}
|
||||
@@ -174,7 +174,7 @@ used. This is covered below.
|
||||
|
||||
Alternately, if you want to bind a Subject's value to a Widget's property, one
|
||||
of the `lv_<widget_type>_bind_...()` functions can be used. See
|
||||
[Widget Binding](/main-modules/observer/observer) for more details.
|
||||
[Widget Binding](/main-modules/data_binding) for more details.
|
||||
|
||||
By using `lv_obj_add_subject_...()` it's also possible to change a subject's value
|
||||
on a trigger. It's covered in [change subject on event](/widgets/scale)
|
||||
@@ -220,7 +220,7 @@ Subject. A copy of the pointer to that Widget is saved in the Observer's `targe
|
||||
field. This works exactly like the above method except that when the Widget is
|
||||
deleted, the Observer thus created will be automatically unsubscribed from the
|
||||
Subject and deleted from the LVGL heap. Note this is different from
|
||||
[Widget Binding](/main-modules/observer/observer).
|
||||
[Widget Binding](/main-modules/data_binding).
|
||||
|
||||
lv_observer_t * observer = <ApiLink name="lv_subject_add_observer_obj" display="lv_subject_add_observer_obj(&some_subject, some_observer_cb, widget, user_data)" />
|
||||
|
||||
@@ -228,11 +228,9 @@ Any number of Observers can be created and be associated with a Widget this way.
|
||||
|
||||
A copy of the pointer to the Widget can be retrieved by calling
|
||||
<ApiLink name="lv_observer_get_target_obj" display="lv_observer_get_target_obj(observer)" />, e.g. inside the callback function.
|
||||
Note that this function returns the stored pointer as a `lv_obj_t *` type, as
|
||||
opposed to the `void *` type returned by
|
||||
<ApiLink name="lv_observer_get_target_obj" display="lv_observer_get_target_obj(observer)" />.
|
||||
(<ApiLink name="lv_observer_get_target" display="lv_observer_get_target(observer)" /> can still be used if you need that
|
||||
pointer as a `void *` type for any reason, but in practice, this would be rare.)
|
||||
This returns the stored pointer as an `lv_obj_t *`, as opposed to the `void *`
|
||||
returned by <ApiLink name="lv_observer_get_target" display="lv_observer_get_target(observer)" /> (which can still be used if
|
||||
you need a `void *` for any reason, but in practice this is rare).
|
||||
|
||||
##### Unsubscribing from a Subject
|
||||
|
||||
@@ -249,7 +247,7 @@ from memory using any of the methods below, depending on the effect you want to
|
||||
from all associated Subjects.
|
||||
- <ApiLink name="lv_subject_deinit" display="lv_subject_deinit(subject)" /> gracefully disconnects `subject` from
|
||||
all associated Observers and Widget events. This includes subscriptions made
|
||||
using any of the [Widget Binding](/main-modules/observer/observer) functions covered below.
|
||||
using any of the [Widget Binding](/main-modules/data_binding) functions covered below.
|
||||
- <ApiLink name="lv_observer_delete" display="lv_observer_delete(observer)" /> deletes that specific Observer and
|
||||
gracefully disconnects it from its `subject` and any associated Widgets,
|
||||
where `observer` is the return value any of the above
|
||||
@@ -321,87 +319,45 @@ static void all_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
|
||||
### Widget Binding
|
||||
|
||||
The following methods of subscribing to an integer-type Subject associate the
|
||||
Observer with ONE of a Widget's properties as thought that property itself were the
|
||||
Observer. Any of the following Widget properties can be thus bound to an Subject's
|
||||
integer value:
|
||||
Besides subscribing manually, a Widget's property can be bound directly to a
|
||||
Subject. Any number of bindings can be created for one Widget, and — like
|
||||
<ApiLink name="lv_subject_add_observer_obj" /> — each one is removed automatically
|
||||
when the Widget is deleted.
|
||||
|
||||
- flag (or OR-ed combination of flags) from from the `LV_OBJ_FLAG_...` enumeration values;
|
||||
- state (or OR-ed combination of states) from the `LV_STATE_...` enumeration values;
|
||||
- text and/or integer values for
|
||||
- Label
|
||||
- Span;
|
||||
- Arc
|
||||
- Drop-Down
|
||||
- Roller
|
||||
- Slider
|
||||
- Scale Section Min/Max values
|
||||
Flags, states, and the checked state are covered below. Values (a Label's text, an
|
||||
Arc / Slider / Roller / Drop-Down value, a Scale Section's min/max) are covered in
|
||||
the "Data binding" section of each widget's documentation, e.g.
|
||||
[Data binding for lv_label](/widgets/label).
|
||||
|
||||
Any number of Observers can be created for a single Widget, each bound to ONE of
|
||||
the above properties.
|
||||
#### Flags and States
|
||||
|
||||
For all of the `lv_..._bind_...()` functions covered below, they are similar to
|
||||
<ApiLink name="lv_subject_add_observer_obj" display="lv_subject_add_observer_obj(&some_subject, some_observer_cb, widget, user_data)" />
|
||||
in that they create an Observer and associates the Widget with it. What is different
|
||||
is that updates to the Widget's property thus bound are handled internally -- the
|
||||
user *does not supply callback functions* for any of these subscribing methods -- the
|
||||
callback methods are supplied by the Observer subsystem.
|
||||
To drive a Widget's flag from a boolean Subject (a value that is only ever `0` or
|
||||
non-zero), use <ApiLink name="lv_obj_bind_bool" />. The callback receives the value
|
||||
as a `bool`, so a dedicated per-flag setter can be passed directly:
|
||||
|
||||
<Callout type="info">
|
||||
While the examples below show saving a reference to the created Observer objects
|
||||
for the various `lv_..._bind_...()` functions, it is not necessary to do so
|
||||
unless you need them for some purpose, because the created Observer objects will
|
||||
be automatically deleted when the Widget is deleted.
|
||||
```c
|
||||
lv_obj_bind_bool(widget, &subject, lv_obj_set_hidden); /* hidden while non-zero */
|
||||
```
|
||||
|
||||
For a state, or when the Subject must be compared to a reference value, add a custom
|
||||
Observer with <ApiLink name="lv_subject_add_observer_obj" /> and set the flag/state
|
||||
yourself:
|
||||
|
||||
```c
|
||||
static void disabled_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
lv_obj_t * obj = lv_observer_get_target_obj(observer);
|
||||
lv_obj_set_state(obj, LV_STATE_DISABLED, lv_subject_get_int(subject) > 80);
|
||||
}
|
||||
|
||||
lv_subject_add_observer_obj(&subject, disabled_observer_cb, widget, NULL);
|
||||
```
|
||||
|
||||
<Callout type="warning">
|
||||
The `lv_obj_bind_flag_if_*` and `lv_obj_bind_state_if_*` functions are
|
||||
**deprecated** in favor of the two approaches above.
|
||||
</Callout>
|
||||
|
||||
##### Any Widget Type
|
||||
|
||||
#### Flags
|
||||
|
||||
The following methods of subscribing to an integer Subject affect a Widget's flag (or
|
||||
OR-ed combination of flags). When the subscribing occurs, and each time the
|
||||
Subject's value is changed thereafter, the Subject's value is compared with the
|
||||
specified reference value, and the specified flag(s) is (are):
|
||||
|
||||
- SET when the Subject's integer value fulfills the indicated condition, and
|
||||
- CLEARED otherwise.
|
||||
|
||||
Here are the functions that carry out this method of subscribing to a Subject. The
|
||||
`flags` argument can contain a single, or bit-wise OR-ed combination of any of the
|
||||
`LV_OBJ_FLAG_...` enumeration values.
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `equal` | <ApiLink name="lv_obj_bind_flag_if_eq" display="lv_obj_bind_flag_if_eq(widget, &subject, flags, ref_value)" /> |
|
||||
| `not equal` | <ApiLink name="lv_obj_bind_flag_if_not_eq" display="lv_obj_bind_flag_if_not_eq(widget, &subject, flags, ref_value)" /> |
|
||||
| `greater than` | <ApiLink name="lv_obj_bind_flag_if_gt" display="lv_obj_bind_flag_if_gt(widget, &subject, flags, ref_value)" /> |
|
||||
| `greater than or equal` | <ApiLink name="lv_obj_bind_flag_if_ge" display="lv_obj_bind_flag_if_ge(widget, &subject, flags, ref_value)" /> |
|
||||
| `less than` | <ApiLink name="lv_obj_bind_flag_if_lt" display="lv_obj_bind_flag_if_lt(widget, &subject, flags, ref_value)" /> |
|
||||
| `less than or equal` | <ApiLink name="lv_obj_bind_flag_if_le" display="lv_obj_bind_flag_if_le(widget, &subject, flags, ref_value)" /> |
|
||||
|
||||
#### States
|
||||
|
||||
The following methods of subscribing to an integer Subject affect a Widget's states
|
||||
(or OR-ed combination of states). When the subscribing occurs, and each time the
|
||||
Subject's value is changed thereafter, the Subject's value is compared with the
|
||||
specified reference value, and the specified state(s) is (are):
|
||||
|
||||
- SET when the Subject's integer value fulfills the indicated condition, and
|
||||
- CLEARED otherwise.
|
||||
|
||||
Here are the functions that carry out this method of subscribing to a Subject. The
|
||||
`states` argument can contain a single, or bit-wise OR-ed combination of any of the
|
||||
`LV_STATE_...` enumeration values.
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `equal` | <ApiLink name="lv_obj_bind_state_if_eq" display="lv_obj_bind_state_if_eq(widget, &subject, states, ref_value)" /> |
|
||||
| `not equal` | <ApiLink name="lv_obj_bind_state_if_not_eq" display="lv_obj_bind_state_if_not_eq(widget, &subject, states, ref_value)" /> |
|
||||
| `greater than` | <ApiLink name="lv_obj_bind_state_if_gt" display="lv_obj_bind_state_if_gt(widget, &subject, states, ref_value)" /> |
|
||||
| `greater than or equal` | <ApiLink name="lv_obj_bind_state_if_ge" display="lv_obj_bind_state_if_ge(widget, &subject, states, ref_value)" /> |
|
||||
| `less than` | <ApiLink name="lv_obj_bind_state_if_lt" display="lv_obj_bind_state_if_lt(widget, &subject, states, ref_value)" /> |
|
||||
| `less than or equal` | <ApiLink name="lv_obj_bind_state_if_le" display="lv_obj_bind_state_if_le(widget, &subject, states, ref_value)" /> |
|
||||
|
||||
#### Checked State
|
||||
|
||||
The following method of subscribing to an integer Subject affects a Widget's
|
||||
@@ -419,11 +375,6 @@ Subject's value to be set to `1` or `0` respectively.
|
||||
|
||||
- <ApiLink name="lv_obj_bind_checked" display="lv_obj_bind_checked(widget, &subject)" />
|
||||
|
||||
##### Specific Widget Types
|
||||
|
||||
To learn how to bind subjects to Arcs, Labels, Sliders, etc. visit the "Data binding"
|
||||
section of the given widget's documentation. For example: [Data binding for lv_label](/widgets/label).
|
||||
|
||||
### Change Subject on Event
|
||||
|
||||
It's a common requirement to update a Subject based on a user action (trigger).
|
||||
|
||||
@@ -171,7 +171,7 @@ only declares its own angles and colour.
|
||||
## Data binding
|
||||
|
||||
Data bindings connect a widget property to a piece of global data — a
|
||||
[Subject](/main-modules/observer/observer). When the subject's value
|
||||
[Subject](/main-modules/data_binding/subjects). When the subject's value
|
||||
changes, every widget bound to it updates automatically; and an
|
||||
interactive widget can write back into the subject so other subscribers
|
||||
see the new value.
|
||||
|
||||
@@ -78,7 +78,7 @@ both the track and the active fill independently.
|
||||
## Data binding
|
||||
|
||||
Data bindings connect a widget property to a piece of global data — a
|
||||
[Subject](/main-modules/observer/observer). When the subject's value
|
||||
[Subject](/main-modules/data_binding/subjects). When the subject's value
|
||||
changes, every widget bound to it updates automatically; and an
|
||||
interactive widget can write back into the subject so other subscribers
|
||||
see the new value.
|
||||
|
||||
@@ -21,7 +21,7 @@ The Base Widget can have:
|
||||
- [Layouts](/common-widget-features/layouts)
|
||||
- [Styles](/common-widget-features/styles)
|
||||
- [Events](/common-widget-features/events)
|
||||
- [Data bindings](/main-modules/observer)
|
||||
- [Data bindings](/main-modules/data_binding)
|
||||
- [Flags](/common-widget-features/flags)
|
||||
- [Parts and States](/common-widget-features/parts_and_states)
|
||||
- And many more
|
||||
@@ -42,12 +42,19 @@ Every widget inherits the Base Widget's binding helpers, which let a Subject
|
||||
drive styles, flags, or states declaratively. The most common one is
|
||||
`bind_style`: attach a named style to a widget only while a Subject equals a
|
||||
reference value — perfect for theme switches and "mode" toggles. See the
|
||||
[Observer](/main-modules/observer/observer) page for an introduction to the
|
||||
[Observer](/main-modules/data_binding/observers) page for an introduction to the
|
||||
underlying mechanism.
|
||||
|
||||
- <ApiLink name="lv_obj_bind_style" display="lv_obj_bind_style(obj, &style, selector, &subject, ref_value)" />
|
||||
- <ApiLink name="lv_obj_bind_flag_if_eq" display="lv_obj_bind_flag_if_eq(obj, &subject, flag, ref_value)" /> and the matching `_not_eq`/`_gt`/`_ge`/`_lt`/`_le` variants
|
||||
- <ApiLink name="lv_obj_bind_state_if_eq" display="lv_obj_bind_state_if_eq(obj, &subject, state, ref_value)" /> and the matching `_not_eq`/`_gt`/`_ge`/`_lt`/`_le` variants
|
||||
- <ApiLink name="lv_obj_bind_bool" display="lv_obj_bind_bool(obj, &subject, set_flag_cb)" /> — drive a flag from a boolean Subject; a per-flag setter such as `lv_obj_set_hidden` can be passed directly
|
||||
- <ApiLink name="lv_subject_add_observer_obj" display="lv_subject_add_observer_obj(&subject, observer_cb, obj, user_data)" /> — for a state, or when the Subject must be compared against a reference value
|
||||
|
||||
<Callout type="warning">
|
||||
The `lv_obj_bind_flag_if_*` and `lv_obj_bind_state_if_*` families are
|
||||
**deprecated**. Use <ApiLink name="lv_obj_bind_bool" /> for a boolean Subject, or
|
||||
<ApiLink name="lv_subject_add_observer_obj" /> with a custom Observer for a
|
||||
comparison. See the [Observer](/main-modules/data_binding/observers) page for details.
|
||||
</Callout>
|
||||
|
||||
<LvglExample name="lv_example_obj_bind_style" path="widgets/obj/lv_example_obj_bind_style" />
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ ticked.
|
||||
## Data binding
|
||||
|
||||
Data bindings connect a widget property to a piece of global data — a
|
||||
[Subject](/main-modules/observer/observer). When the subject's value
|
||||
[Subject](/main-modules/data_binding/subjects). When the subject's value
|
||||
changes, every widget bound to it updates automatically; and an
|
||||
interactive widget can write back into the subject so other subscribers
|
||||
see the new value.
|
||||
|
||||
@@ -91,7 +91,7 @@ lv_obj_add_style(list, &my_style, LV_PART_SELECTED);
|
||||
## Data binding
|
||||
|
||||
Data bindings connect a widget property to a piece of global data — a
|
||||
[Subject](/main-modules/observer/observer). When the subject's value
|
||||
[Subject](/main-modules/data_binding/subjects). When the subject's value
|
||||
changes, every widget bound to it updates automatically; and an
|
||||
interactive widget can write back into the subject so other subscribers
|
||||
see the new value.
|
||||
|
||||
@@ -145,7 +145,7 @@ When the widget is larger than its image source, `inner_align`
|
||||
## Data binding
|
||||
|
||||
Data bindings connect a widget property to a piece of global data — a
|
||||
[Subject](/main-modules/observer/observer). When the subject's value
|
||||
[Subject](/main-modules/data_binding/subjects). When the subject's value
|
||||
changes, every widget bound to it updates automatically; and an
|
||||
interactive widget can write back into the subject so other subscribers
|
||||
see the new value.
|
||||
|
||||
@@ -133,7 +133,7 @@ directly on the `<lv_label>` tag for one-off variations.
|
||||
## Data binding
|
||||
|
||||
Data bindings connect a widget property to a piece of global data — a
|
||||
[Subject](/main-modules/observer/observer). When the subject's value
|
||||
[Subject](/main-modules/data_binding/subjects). When the subject's value
|
||||
changes, every widget bound to it updates automatically; and an
|
||||
interactive widget can write back into the subject so other subscribers
|
||||
see the new value.
|
||||
|
||||
@@ -66,7 +66,7 @@ a chunkier highlighted row without computing a separate selected-area height.
|
||||
## Data binding
|
||||
|
||||
Data bindings connect a widget property to a piece of global data — a
|
||||
[Subject](/main-modules/observer/observer). When the subject's value
|
||||
[Subject](/main-modules/data_binding/subjects). When the subject's value
|
||||
changes, every widget bound to it updates automatically; and an
|
||||
interactive widget can write back into the subject so other subscribers
|
||||
see the new value.
|
||||
|
||||
@@ -139,7 +139,7 @@ hides the section — a quick way to toggle it off
|
||||
### Data binding
|
||||
|
||||
Data bindings connect a widget property to a piece of global data — a
|
||||
[Subject](/main-modules/observer/observer). When the subject's value
|
||||
[Subject](/main-modules/data_binding/subjects). When the subject's value
|
||||
changes, every widget bound to it updates automatically; and an
|
||||
interactive widget can write back into the subject so other subscribers
|
||||
see the new value.
|
||||
@@ -197,7 +197,7 @@ Needles aren't yet exposed through XML, so this example is C-only:
|
||||
### Data binding
|
||||
|
||||
Data bindings connect a widget property to a piece of global data — a
|
||||
[Subject](/main-modules/observer/observer). When the subject's value
|
||||
[Subject](/main-modules/data_binding/subjects). When the subject's value
|
||||
changes, every widget bound to it updates automatically; and an
|
||||
interactive widget can write back into the subject so other subscribers
|
||||
see the new value.
|
||||
|
||||
@@ -99,7 +99,7 @@ increases the knob's click area.
|
||||
## Data binding
|
||||
|
||||
Data bindings connect a widget property to a piece of global data — a
|
||||
[Subject](/main-modules/observer/observer). When the subject's value
|
||||
[Subject](/main-modules/data_binding/subjects). When the subject's value
|
||||
changes, every widget bound to it updates automatically; and an
|
||||
interactive widget can write back into the subject so other subscribers
|
||||
see the new value.
|
||||
|
||||
@@ -86,7 +86,7 @@ A few practical notes for the cursor look:
|
||||
## Data binding
|
||||
|
||||
Data bindings connect a widget property to a piece of global data — a
|
||||
[Subject](/main-modules/observer/observer). When the subject's value
|
||||
[Subject](/main-modules/data_binding/subjects). When the subject's value
|
||||
changes, every widget bound to it updates automatically; and an
|
||||
interactive widget can write back into the subject so other subscribers
|
||||
see the new value.
|
||||
|
||||
@@ -8,17 +8,16 @@ static void app_init(void);
|
||||
static void ui_init(void);
|
||||
|
||||
/**
|
||||
* @title PIN login via state bindings
|
||||
* @brief Decouple a login UI from an engine subject using `lv_obj_bind_state_if_*`.
|
||||
* @title PIN login via state observers
|
||||
* @brief Decouple a login UI from an engine subject using observers on an auth subject.
|
||||
*
|
||||
* Two int subjects are initialised: `engine_subject` for the engine state and
|
||||
* `auth_state_subject` for `LOGGED_OUT`, `LOGGED_IN`, and `AUTH_FAILED`. A
|
||||
* password textarea fires `LV_EVENT_READY` to set the auth subject, a log-out
|
||||
* button resets it, and an observer on `auth_state_subject` writes status text
|
||||
* into an info label. `lv_obj_bind_state_if_eq` and
|
||||
* `lv_obj_bind_state_if_not_eq` toggle `LV_STATE_DISABLED` on the textarea, log
|
||||
* out button, and a start-engine button, which itself uses `lv_obj_bind_checked`
|
||||
* to drive `engine_subject`.
|
||||
* into an info label. Observers added with `lv_subject_add_observer_obj` toggle
|
||||
* `LV_STATE_DISABLED` on the textarea, log out button, and a start-engine
|
||||
* button, which itself uses `lv_obj_bind_checked` to drive `engine_subject`.
|
||||
*/
|
||||
void lv_example_observer_2(void)
|
||||
{
|
||||
@@ -92,6 +91,20 @@ static void info_label_observer_cb(lv_observer_t * observer, lv_subject_t * subj
|
||||
}
|
||||
}
|
||||
|
||||
/*Disable the target while logged in (e.g. the password field and keyboard)*/
|
||||
static void disable_while_logged_in_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
lv_obj_t * obj = lv_observer_get_target_obj(observer);
|
||||
lv_obj_set_state(obj, LV_STATE_DISABLED, lv_subject_get_int(subject) == LOGGED_IN);
|
||||
}
|
||||
|
||||
/*Enable the target only while logged in, disable it otherwise*/
|
||||
static void enable_while_logged_in_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
lv_obj_t * obj = lv_observer_get_target_obj(observer);
|
||||
lv_obj_set_state(obj, LV_STATE_DISABLED, lv_subject_get_int(subject) != LOGGED_IN);
|
||||
}
|
||||
|
||||
static void log_out_click_event_cb(lv_event_t * e)
|
||||
{
|
||||
LV_UNUSED(e);
|
||||
@@ -110,7 +123,7 @@ static void ui_init(void)
|
||||
lv_textarea_set_password_mode(ta, true);
|
||||
lv_textarea_set_placeholder_text(ta, "The password is: hello");
|
||||
lv_obj_add_event_cb(ta, textarea_event_cb, LV_EVENT_READY, NULL);
|
||||
lv_obj_bind_state_if_eq(ta, &auth_state_subject, LV_STATE_DISABLED, LOGGED_IN);
|
||||
lv_subject_add_observer_obj(&auth_state_subject, disable_while_logged_in_observer_cb, ta, NULL);
|
||||
|
||||
lv_obj_t * kb = lv_keyboard_create(lv_screen_active());
|
||||
lv_keyboard_set_textarea(kb, ta);
|
||||
@@ -122,7 +135,7 @@ static void ui_init(void)
|
||||
btn = lv_button_create(lv_screen_active());
|
||||
lv_obj_set_pos(btn, 220, 10);
|
||||
lv_obj_add_event_cb(btn, log_out_click_event_cb, LV_EVENT_CLICKED, NULL);
|
||||
lv_obj_bind_state_if_not_eq(btn, &auth_state_subject, LV_STATE_DISABLED, LOGGED_IN);
|
||||
lv_subject_add_observer_obj(&auth_state_subject, enable_while_logged_in_observer_cb, btn, NULL);
|
||||
|
||||
label = lv_label_create(btn);
|
||||
lv_label_set_text(label, "LOG OUT");
|
||||
@@ -136,7 +149,7 @@ static void ui_init(void)
|
||||
btn = lv_button_create(lv_screen_active());
|
||||
lv_obj_set_pos(btn, 10, 80);
|
||||
lv_obj_set_checkable(btn, true);
|
||||
lv_obj_bind_state_if_not_eq(btn, &auth_state_subject, LV_STATE_DISABLED, LOGGED_IN);
|
||||
lv_subject_add_observer_obj(&auth_state_subject, enable_while_logged_in_observer_cb, btn, NULL);
|
||||
lv_obj_bind_checked(btn, &engine_subject);
|
||||
label = lv_label_create(btn);
|
||||
lv_label_set_text(label, "START ENGINE");
|
||||
|
||||
@@ -36,10 +36,10 @@ typedef enum {
|
||||
* into `time_subject` via `lv_subject_init_group` so a single observer can
|
||||
* re-render the time label whenever any element changes. A "Set" button creates
|
||||
* a bottom container with two rollers and two dropdowns bound through
|
||||
* `lv_roller_bind_value` and `lv_dropdown_bind_value`; the AM/PM dropdown uses
|
||||
* `lv_obj_bind_state_if_eq` to disable itself in `TIME_FORMAT_24`. A second
|
||||
* observer on the format subject swaps the hour roller options between the 12
|
||||
* and 24 lists.
|
||||
* `lv_roller_bind_value` and `lv_dropdown_bind_value`; the AM/PM dropdown adds an
|
||||
* observer with `lv_subject_add_observer_obj` to disable itself in
|
||||
* `TIME_FORMAT_24`. A second observer on the format subject swaps the hour roller
|
||||
* options between the 12 and 24 lists.
|
||||
*/
|
||||
void lv_example_observer_3(void)
|
||||
{
|
||||
@@ -72,6 +72,13 @@ void lv_example_observer_3(void)
|
||||
lv_subject_set_int(&am_pm_subject, TIME_PM);
|
||||
}
|
||||
|
||||
/*Disable the AM/PM dropdown while the 24-hour format is selected*/
|
||||
static void am_pm_disabled_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
lv_obj_t * obj = lv_observer_get_target_obj(observer);
|
||||
lv_obj_set_state(obj, LV_STATE_DISABLED, lv_subject_get_int(subject) == TIME_FORMAT_24);
|
||||
}
|
||||
|
||||
static void set_btn_clicked_event_cb(lv_event_t * e)
|
||||
{
|
||||
lv_obj_t * set_btn = lv_event_get_target_obj(e);
|
||||
@@ -101,7 +108,7 @@ static void set_btn_clicked_event_cb(lv_event_t * e)
|
||||
lv_obj_t * am_pm_dropdown = lv_dropdown_create(cont);
|
||||
lv_dropdown_set_options(am_pm_dropdown, "am\npm");
|
||||
lv_dropdown_bind_value(am_pm_dropdown, &am_pm_subject);
|
||||
lv_obj_bind_state_if_eq(am_pm_dropdown, &format_subject, LV_STATE_DISABLED, TIME_FORMAT_24);
|
||||
lv_subject_add_observer_obj(&format_subject, am_pm_disabled_observer_cb, am_pm_dropdown, NULL);
|
||||
lv_obj_set_pos(am_pm_dropdown, 128, 48);
|
||||
lv_obj_set_width(am_pm_dropdown, 80);
|
||||
|
||||
|
||||
@@ -10,9 +10,15 @@
|
||||
* @brief A checkbox drives an int subject; a sibling label hides when the subject is zero.
|
||||
*
|
||||
* `bind_checked` connects the checkbox's checked state to `subject_flag` (0 or 1).
|
||||
* The note label uses `bind_flag_if_eq` to enable the `hidden` flag while
|
||||
* `subject_flag` equals 0 — so the label only shows once the box is ticked.
|
||||
* The note label uses a subject to enable the `hidden` flag when `subject_flag`
|
||||
* equals 0 — so the label only shows once the box is ticked.
|
||||
*/
|
||||
|
||||
static void show_label(lv_obj_t * obj, bool value)
|
||||
{
|
||||
lv_obj_set_hidden(obj, !value);
|
||||
}
|
||||
|
||||
void lv_example_checkbox_bind_checked(void)
|
||||
{
|
||||
static lv_subject_t subject_flag;
|
||||
@@ -36,6 +42,6 @@ void lv_example_checkbox_bind_checked(void)
|
||||
lv_label_set_text(label, "Extra details only visible while the box is ticked.");
|
||||
lv_obj_set_align(label, LV_ALIGN_CENTER);
|
||||
lv_obj_set_y(label, 30);
|
||||
lv_obj_bind_flag_if_eq(label, &subject_flag, LV_OBJ_FLAG_HIDDEN, 0);
|
||||
lv_obj_bind_bool(label, &subject_flag, show_label);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
* a guard like "block submit when the value is out of range". Slide past 80 to
|
||||
* watch the button grey out.
|
||||
*/
|
||||
static void disable_if_gt_80(lv_obj_t * obj, int32_t value)
|
||||
{
|
||||
lv_obj_set_disabled(obj, value > 80);
|
||||
}
|
||||
void lv_example_slider_bind_state(void)
|
||||
{
|
||||
static lv_subject_t subject_value;
|
||||
@@ -46,7 +50,6 @@ void lv_example_slider_bind_state(void)
|
||||
lv_obj_t * label_2 = lv_label_create(button);
|
||||
lv_obj_set_align(label_2, LV_ALIGN_CENTER);
|
||||
lv_label_set_text(label_2, "Submit");
|
||||
|
||||
lv_obj_bind_state_if_gt(button, &subject_value, LV_STATE_DISABLED, 80);
|
||||
lv_obj_bind_int(button, &subject_value, disable_if_gt_80);
|
||||
}
|
||||
#endif
|
||||
|
||||
+155
-24
File diff suppressed because it is too large
Load Diff
@@ -1540,7 +1540,7 @@ static void remove_style_core(lv_obj_t * obj, const lv_style_t * style, lv_style
|
||||
|
||||
static void bind_style_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
bind_style_t * p = observer->user_data;
|
||||
bind_style_t * p = lv_observer_get_user_data(observer);
|
||||
|
||||
int32_t v = lv_subject_get_int(subject);
|
||||
bool dis = (v != p->value);
|
||||
@@ -1549,7 +1549,7 @@ static void bind_style_observer_cb(lv_observer_t * observer, lv_subject_t * subj
|
||||
|
||||
static void bind_style_prop_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
bind_style_prop_t * p = observer->user_data;
|
||||
bind_style_prop_t * p = lv_observer_get_user_data(observer);
|
||||
|
||||
lv_style_value_t style_v;
|
||||
if(subject->type == LV_SUBJECT_TYPE_INT) style_v.num = lv_subject_get_int(subject);
|
||||
|
||||
+173
-6
File diff suppressed because it is too large
Load Diff
@@ -33,7 +33,8 @@ struct _lv_observer_t {
|
||||
lv_subject_t * subject; /**< Observed subject */
|
||||
lv_observer_cb_t cb; /**< Callback that notifies when value changes */
|
||||
void * target; /**< A target for the observer, e.g. a widget or any pointer */
|
||||
void * user_data; /**< Additional parameter supplied when subscribing */
|
||||
void * user_data; /**< Additional parameter, can be used freely by user */
|
||||
void (*user_cb)(void); /**< Additional function pointer, can be used freely by user */
|
||||
uint32_t auto_free_user_data : 1; /**< Automatically free user data when observer is removed */
|
||||
uint32_t notified : 1; /**< Was observer already notified? */
|
||||
uint32_t for_obj : 1; /**< Is `target` a pointer to a Widget (`lv_obj_t *`)? */
|
||||
|
||||
@@ -1559,7 +1559,7 @@ static void label_text_observer_cb(lv_observer_t * observer, lv_subject_t * subj
|
||||
LV_ASSERT(observer != NULL);
|
||||
LV_ASSERT(observer->target != NULL);
|
||||
LV_ASSERT(subject != NULL);
|
||||
const char * fmt = observer->user_data;
|
||||
const char * fmt = lv_observer_get_user_data(observer);
|
||||
|
||||
if(fmt == NULL) {
|
||||
lv_label_set_text(observer->target, subject->value.pointer);
|
||||
|
||||
@@ -2017,35 +2017,34 @@ static void needle_deleted_cb(lv_event_t * e)
|
||||
static void scale_section_min_value_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
LV_ASSERT(observer != NULL);
|
||||
LV_ASSERT(observer->user_data != NULL);
|
||||
LV_ASSERT(observer->target != NULL);
|
||||
LV_ASSERT(subject != NULL);
|
||||
LV_ASSERT(subject->type == LV_SUBJECT_TYPE_INT);
|
||||
lv_scale_section_t * section = lv_observer_get_user_data(observer);
|
||||
LV_ASSERT(section != NULL);
|
||||
|
||||
lv_scale_section_t * section = observer->user_data;
|
||||
lv_scale_set_section_min_value(observer->target, section, subject->value.num);
|
||||
}
|
||||
|
||||
static void scale_section_max_value_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
LV_ASSERT(observer != NULL);
|
||||
LV_ASSERT(observer->user_data != NULL);
|
||||
LV_ASSERT(observer->target != NULL);
|
||||
LV_ASSERT(subject != NULL);
|
||||
LV_ASSERT(subject->type == LV_SUBJECT_TYPE_INT);
|
||||
|
||||
lv_scale_section_t * section = observer->user_data;
|
||||
lv_scale_section_t * section = lv_observer_get_user_data(observer);
|
||||
LV_ASSERT(section != NULL);
|
||||
lv_scale_set_section_max_value(observer->target, section, subject->value.num);
|
||||
}
|
||||
|
||||
static void scale_line_needle_value_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
LV_ASSERT(observer != NULL);
|
||||
LV_ASSERT(observer->user_data != NULL);
|
||||
LV_ASSERT(observer->target != NULL);
|
||||
LV_ASSERT(subject != NULL);
|
||||
LV_ASSERT(subject->type == LV_SUBJECT_TYPE_INT);
|
||||
bind_element_needle_t * bind_element = observer->user_data;
|
||||
bind_element_needle_t * bind_element = lv_observer_get_user_data(observer);
|
||||
LV_ASSERT(bind_element != NULL);
|
||||
lv_scale_set_line_needle_value(observer->target, bind_element->needle_line, bind_element->needle_length,
|
||||
subject->value.num);
|
||||
}
|
||||
@@ -2053,11 +2052,11 @@ static void scale_line_needle_value_observer_cb(lv_observer_t * observer, lv_sub
|
||||
static void scale_image_needle_value_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
|
||||
{
|
||||
LV_ASSERT(observer != NULL);
|
||||
LV_ASSERT(observer->user_data != NULL);
|
||||
LV_ASSERT(observer->target != NULL);
|
||||
LV_ASSERT(subject != NULL);
|
||||
LV_ASSERT(subject->type == LV_SUBJECT_TYPE_INT);
|
||||
lv_obj_t * needle_img = observer->user_data;
|
||||
lv_obj_t * needle_img = lv_observer_get_user_data(observer);
|
||||
LV_ASSERT(needle_img != NULL);
|
||||
lv_scale_set_image_needle_value(observer->target, needle_img, subject->value.num);
|
||||
}
|
||||
|
||||
|
||||
@@ -1531,7 +1531,7 @@ static void span_text_observer_cb(lv_observer_t * observer, lv_subject_t * subje
|
||||
LV_ASSERT(subject != NULL);
|
||||
LV_ASSERT(subject->type == LV_SUBJECT_TYPE_INT || subject->type == LV_SUBJECT_TYPE_FLOAT ||
|
||||
subject->type == LV_SUBJECT_TYPE_STRING || subject->type == LV_SUBJECT_TYPE_POINTER);
|
||||
bind_element_string_t * user_data = observer->user_data;
|
||||
bind_element_string_t * user_data = lv_observer_get_user_data(observer);
|
||||
LV_ASSERT(user_data != NULL);
|
||||
|
||||
if(user_data->fmt == NULL) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user