From e21695f6cb6f91f120da7ead63e15fb25998b990 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Costa?= Date: Wed, 2 Sep 2026 09:41:47 +0200 Subject: [PATCH] feat(subject): deprecate lv_obj_bind_flag/state and add lv_obj_bind_ (#10541) Co-authored-by: Gabor Kiss-Vamosi --- README.md | 2 +- demos/gltf/lv_demo_gltf.c | 13 +- docs/README_he.md | 2 +- docs/README_ja.md | 2 +- docs/README_ko.md | 2 +- docs/README_pt_BR.md | 2 +- docs/README_zh.md | 2 +- docs/src/changelog/migration-v10.mdx | 27 ++ .../styles/local_styles.mdx | 2 +- .../styles/style_sheets.mdx | 2 +- docs/src/getting_started/learn_the_basics.mdx | 2 +- .../examples.mdx} | 0 .../{observer => data_binding}/index.mdx | 2 +- docs/src/main-modules/data_binding/meta.json | 9 + .../main-modules/data_binding/observers.mdx | 147 +++++++++++ .../main-modules/data_binding/overview.mdx | 92 +++++++ .../main-modules/data_binding/subjects.mdx | 124 ++++++++++ docs/src/main-modules/meta.json | 2 +- docs/src/main-modules/observer/meta.json | 7 - docs/src/main-modules/observer/observer.mdx | 127 +++------- docs/src/widgets/arc.mdx | 2 +- docs/src/widgets/bar.mdx | 2 +- docs/src/widgets/base_widget.mdx | 15 +- docs/src/widgets/checkbox.mdx | 2 +- docs/src/widgets/dropdown.mdx | 2 +- docs/src/widgets/image.mdx | 2 +- docs/src/widgets/label.mdx | 2 +- docs/src/widgets/roller.mdx | 2 +- docs/src/widgets/scale.mdx | 4 +- docs/src/widgets/slider.mdx | 2 +- docs/src/widgets/spinbox.mdx | 2 +- .../others/observer/lv_example_observer_2.c | 31 ++- .../others/observer/lv_example_observer_3.c | 17 +- .../lv_example_checkbox_bind_checked.c | 12 +- .../slider/lv_example_slider_bind_state.c | 7 +- include/lvgl/core/lv_observer.h | 179 ++++++++++++-- src/core/lv_obj_style.c | 4 +- src/core/lv_observer.c | 179 +++++++++++++- src/core/lv_observer_private.h | 3 +- src/widgets/label/lv_label.c | 2 +- src/widgets/scale/lv_scale.c | 17 +- src/widgets/span/lv_span.c | 2 +- tests/src/test_cases/test_observer.c | 231 +++++++++++++++++- 43 files changed, 1101 insertions(+), 188 deletions(-) rename docs/src/main-modules/{observer/observer_examples.mdx => data_binding/examples.mdx} (100%) rename docs/src/main-modules/{observer => data_binding}/index.mdx (59%) create mode 100644 docs/src/main-modules/data_binding/meta.json create mode 100644 docs/src/main-modules/data_binding/observers.mdx create mode 100644 docs/src/main-modules/data_binding/overview.mdx create mode 100644 docs/src/main-modules/data_binding/subjects.mdx delete mode 100644 docs/src/main-modules/observer/meta.json diff --git a/README.md b/README.md index fb92a1ae91..3c590ce62d 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/demos/gltf/lv_demo_gltf.c b/demos/gltf/lv_demo_gltf.c index fceebdc6ec..d95827f9da 100644 --- a/demos/gltf/lv_demo_gltf.c +++ b/demos/gltf/lv_demo_gltf.c @@ -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) diff --git a/docs/README_he.md b/docs/README_he.md index 30873801ef..c1f19551f3 100644 --- a/docs/README_he.md +++ b/docs/README_he.md @@ -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). diff --git a/docs/README_ja.md b/docs/README_ja.md index 16ea707b5e..cf46229925 100644 --- a/docs/README_ja.md +++ b/docs/README_ja.md @@ -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) 対応。 diff --git a/docs/README_ko.md b/docs/README_ko.md index bb1f3808a9..893630c706 100644 --- a/docs/README_ko.md +++ b/docs/README_ko.md @@ -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) 지원. diff --git a/docs/README_pt_BR.md b/docs/README_pt_BR.md index 69986536dd..99afaf7fb1 100644 --- a/docs/README_pt_BR.md +++ b/docs/README_pt_BR.md @@ -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). diff --git a/docs/README_zh.md b/docs/README_zh.md index da28f9c520..50053c93ef 100644 --- a/docs/README_zh.md +++ b/docs/README_zh.md @@ -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)。 diff --git a/docs/src/changelog/migration-v10.mdx b/docs/src/changelog/migration-v10.mdx index 4b4e0dbefc..a47dbc3969 100644 --- a/docs/src/changelog/migration-v10.mdx +++ b/docs/src/changelog/migration-v10.mdx @@ -570,6 +570,33 @@ Calling the following functions with `NULL` as the display parameter is deprecat - - +### 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 diff --git a/docs/src/common-widget-features/styles/local_styles.mdx b/docs/src/common-widget-features/styles/local_styles.mdx index 0ecdb638e7..86746a77d0 100644 --- a/docs/src/common-widget-features/styles/local_styles.mdx +++ b/docs/src/common-widget-features/styles/local_styles.mdx @@ -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 , 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. diff --git a/docs/src/common-widget-features/styles/style_sheets.mdx b/docs/src/common-widget-features/styles/style_sheets.mdx index 7924ebf7ae..d4492122b8 100644 --- a/docs/src/common-widget-features/styles/style_sheets.mdx +++ b/docs/src/common-widget-features/styles/style_sheets.mdx @@ -146,7 +146,7 @@ notified. There are 3 options to do this: ## Binding Styles By using , 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 diff --git a/docs/src/getting_started/learn_the_basics.mdx b/docs/src/getting_started/learn_the_basics.mdx index 3f13a8cfb0..cde458eaf9 100644 --- a/docs/src/getting_started/learn_the_basics.mdx +++ b/docs/src/getting_started/learn_the_basics.mdx @@ -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). diff --git a/docs/src/main-modules/observer/observer_examples.mdx b/docs/src/main-modules/data_binding/examples.mdx similarity index 100% rename from docs/src/main-modules/observer/observer_examples.mdx rename to docs/src/main-modules/data_binding/examples.mdx diff --git a/docs/src/main-modules/observer/index.mdx b/docs/src/main-modules/data_binding/index.mdx similarity index 59% rename from docs/src/main-modules/observer/index.mdx rename to docs/src/main-modules/data_binding/index.mdx index e62da580f6..ccb17c856b 100644 --- a/docs/src/main-modules/observer/index.mdx +++ b/docs/src/main-modules/data_binding/index.mdx @@ -1,5 +1,5 @@ --- -title: Observer +title: Data binding --- diff --git a/docs/src/main-modules/data_binding/meta.json b/docs/src/main-modules/data_binding/meta.json new file mode 100644 index 0000000000..6a76c68b2a --- /dev/null +++ b/docs/src/main-modules/data_binding/meta.json @@ -0,0 +1,9 @@ +{ + "title": "Data binding", + "pages": [ + "overview", + "subjects", + "observers", + "examples" + ] +} diff --git a/docs/src/main-modules/data_binding/observers.mdx b/docs/src/main-modules/data_binding/observers.mdx new file mode 100644 index 0000000000..2f5d07bd36 --- /dev/null +++ b/docs/src/main-modules/data_binding/observers.mdx @@ -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 . +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 +, +then read it back in the callback with . + +```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 + and the +target can be read back with . + +## Unsubscribing + +An Observer bound to a Widget using is removed automatically when that Widget is deleted, so in this case you don't need to do anything. Otherwise: + +- removes a + single Observer (use the pointer returned when subscribing). +- + removes a Widget's Observers (`subject` may be `NULL` to detach from all). +- 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 +. 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); +``` + + +The older `lv_obj_bind_flag_if_*` and `lv_obj_bind_state_if_*` functions are +**deprecated** in favor of the two approaches above. + + +### Checked state + + 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 +to make it checkable. + +```c +lv_obj_bind_checked(checkbox, &subject); +``` + + + +### Widget values + +A Widget's main value or text is bound with the widget's own `lv__bind_...()` +function, described under "Data binding" on each widget's page. A few examples: + +#### Label text + + keeps a Label's +text in sync with a Subject, optionally through a `printf`-style format string. + + + +#### Slider value + + ties a Slider's +position to a Subject; dragging the Slider updates the Subject and vice versa. + + + +#### Arc value + + keeps an Arc's value +in sync with a Subject. + + + +#### Drop-down value + + binds the +selected option's index to a Subject. + + + +#### Roller value + + binds the +selected option's index to a Subject. + + diff --git a/docs/src/main-modules/data_binding/overview.mdx b/docs/src/main-modules/data_binding/overview.mdx new file mode 100644 index 0000000000..9f3f02ea71 --- /dev/null +++ b/docs/src/main-modules/data_binding/overview.mdx @@ -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). diff --git a/docs/src/main-modules/data_binding/subjects.mdx b/docs/src/main-modules/data_binding/subjects.mdx new file mode 100644 index 0000000000..3abf488bb1 --- /dev/null +++ b/docs/src/main-modules/data_binding/subjects.mdx @@ -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 (requires ), + and +. + +## Reading and Writing the Value + +Write a value with `lv_subject_set_...()` (e.g. ). + +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. ): + +```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 +: + +```c +lv_subject_copy_string(&title, "Settings"); +``` + +Every type (except groups) also has a `lv_subject_get_previous_...()` function (e.g. +) 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 +: + +```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: + +- + adds `step` to the value (use a negative `step` to subtract). It returns a + descriptor you can adjust further with + , + and + . +- , + (requires ) + and + write a fixed value on the trigger. + + +Do not de-initialize or delete a Subject while these events remain attached as their callbacks saved a pointer to the Subject. + + diff --git a/docs/src/main-modules/meta.json b/docs/src/main-modules/meta.json index fe9ad6d366..28f4ec81bf 100644 --- a/docs/src/main-modules/meta.json +++ b/docs/src/main-modules/meta.json @@ -9,7 +9,7 @@ "timer", "animation", "fs", - "observer", + "data_binding", "draw", "translation" ] diff --git a/docs/src/main-modules/observer/meta.json b/docs/src/main-modules/observer/meta.json deleted file mode 100644 index acf3c18e3c..0000000000 --- a/docs/src/main-modules/observer/meta.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "title": "Observer", - "pages": [ - "observer", - "observer_examples" - ] -} diff --git a/docs/src/main-modules/observer/observer.mdx b/docs/src/main-modules/observer/observer.mdx index b49d1aa694..4fc2735e05 100644 --- a/docs/src/main-modules/observer/observer.mdx +++ b/docs/src/main-modules/observer/observer.mdx @@ -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__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 = @@ -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 , 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 -. -( 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 (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. - 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. - 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 + — 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 - -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 . The callback receives the value +as a `bool`, so a dedicated per-flag setter can be passed directly: - -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 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); +``` + + +The `lv_obj_bind_flag_if_*` and `lv_obj_bind_state_if_*` functions are +**deprecated** in favor of the two approaches above. -##### 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` | | -| `not equal` | | -| `greater than` | | -| `greater than or equal` | | -| `less than` | | -| `less than or equal` | | - -#### 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` | | -| `not equal` | | -| `greater than` | | -| `greater than or equal` | | -| `less than` | | -| `less than or equal` | | - #### 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. - -##### 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). diff --git a/docs/src/widgets/arc.mdx b/docs/src/widgets/arc.mdx index 675e93e8fa..376c806e75 100644 --- a/docs/src/widgets/arc.mdx +++ b/docs/src/widgets/arc.mdx @@ -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. diff --git a/docs/src/widgets/bar.mdx b/docs/src/widgets/bar.mdx index 73c58d8070..2f14682d5b 100644 --- a/docs/src/widgets/bar.mdx +++ b/docs/src/widgets/bar.mdx @@ -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. diff --git a/docs/src/widgets/base_widget.mdx b/docs/src/widgets/base_widget.mdx index e8ce34e3e0..6aaf7347e2 100644 --- a/docs/src/widgets/base_widget.mdx +++ b/docs/src/widgets/base_widget.mdx @@ -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. - -- and the matching `_not_eq`/`_gt`/`_ge`/`_lt`/`_le` variants -- and the matching `_not_eq`/`_gt`/`_ge`/`_lt`/`_le` variants +- — drive a flag from a boolean Subject; a per-flag setter such as `lv_obj_set_hidden` can be passed directly +- — for a state, or when the Subject must be compared against a reference value + + +The `lv_obj_bind_flag_if_*` and `lv_obj_bind_state_if_*` families are +**deprecated**. Use for a boolean Subject, or + with a custom Observer for a +comparison. See the [Observer](/main-modules/data_binding/observers) page for details. + diff --git a/docs/src/widgets/checkbox.mdx b/docs/src/widgets/checkbox.mdx index 39f5764e67..2f0e8d3e42 100644 --- a/docs/src/widgets/checkbox.mdx +++ b/docs/src/widgets/checkbox.mdx @@ -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. diff --git a/docs/src/widgets/dropdown.mdx b/docs/src/widgets/dropdown.mdx index 3c449b6e87..87c0375ec0 100644 --- a/docs/src/widgets/dropdown.mdx +++ b/docs/src/widgets/dropdown.mdx @@ -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. diff --git a/docs/src/widgets/image.mdx b/docs/src/widgets/image.mdx index 452e4a9ec9..b960c2656f 100644 --- a/docs/src/widgets/image.mdx +++ b/docs/src/widgets/image.mdx @@ -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. diff --git a/docs/src/widgets/label.mdx b/docs/src/widgets/label.mdx index d8e8187bd6..c00c79ea5c 100644 --- a/docs/src/widgets/label.mdx +++ b/docs/src/widgets/label.mdx @@ -133,7 +133,7 @@ directly on the `` 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. diff --git a/docs/src/widgets/roller.mdx b/docs/src/widgets/roller.mdx index 5f32f7514b..4d17a3aabb 100644 --- a/docs/src/widgets/roller.mdx +++ b/docs/src/widgets/roller.mdx @@ -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. diff --git a/docs/src/widgets/scale.mdx b/docs/src/widgets/scale.mdx index 5155aef60d..a565d9a8db 100644 --- a/docs/src/widgets/scale.mdx +++ b/docs/src/widgets/scale.mdx @@ -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. diff --git a/docs/src/widgets/slider.mdx b/docs/src/widgets/slider.mdx index d7c9e9334c..27956fcbba 100644 --- a/docs/src/widgets/slider.mdx +++ b/docs/src/widgets/slider.mdx @@ -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. diff --git a/docs/src/widgets/spinbox.mdx b/docs/src/widgets/spinbox.mdx index 7cd0d4a09f..cf3b5ab355 100644 --- a/docs/src/widgets/spinbox.mdx +++ b/docs/src/widgets/spinbox.mdx @@ -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. diff --git a/examples/others/observer/lv_example_observer_2.c b/examples/others/observer/lv_example_observer_2.c index ebca0f8ea8..30e7b98ddb 100644 --- a/examples/others/observer/lv_example_observer_2.c +++ b/examples/others/observer/lv_example_observer_2.c @@ -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"); diff --git a/examples/others/observer/lv_example_observer_3.c b/examples/others/observer/lv_example_observer_3.c index 377345fe44..88b568d852 100644 --- a/examples/others/observer/lv_example_observer_3.c +++ b/examples/others/observer/lv_example_observer_3.c @@ -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); diff --git a/examples/widgets/checkbox/lv_example_checkbox_bind_checked.c b/examples/widgets/checkbox/lv_example_checkbox_bind_checked.c index 3257a0adda..6b1af9db29 100644 --- a/examples/widgets/checkbox/lv_example_checkbox_bind_checked.c +++ b/examples/widgets/checkbox/lv_example_checkbox_bind_checked.c @@ -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 diff --git a/examples/widgets/slider/lv_example_slider_bind_state.c b/examples/widgets/slider/lv_example_slider_bind_state.c index feb62c683d..e0f98f91de 100644 --- a/examples/widgets/slider/lv_example_slider_bind_state.c +++ b/examples/widgets/slider/lv_example_slider_bind_state.c @@ -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 diff --git a/include/lvgl/core/lv_observer.h b/include/lvgl/core/lv_observer.h index d1b03590a3..1de0dbc318 100644 --- a/include/lvgl/core/lv_observer.h +++ b/include/lvgl/core/lv_observer.h @@ -67,6 +67,7 @@ struct _lv_subject_t { lv_subject_value_t min_value; /**< Minimum value for min. int or float*/ lv_subject_value_t max_value; /**< Maximum value for max. int or float*/ void * user_data; /**< Additional parameter, can be used freely by user */ + uint32_t type : 4; /**< One of the LV_SUBJECT_TYPE_... values */ uint32_t size : 24; /**< String buffer size or group length */ uint32_t notify_restart_query : 1; /**< If an Observer was deleted during notification, @@ -80,31 +81,54 @@ struct _lv_subject_t { */ typedef void (*lv_observer_cb_t)(lv_observer_t * observer, lv_subject_t * subject); + +/** + * Generic callback called to set a boolean value on a Widget. + * @param obj pointer to Widget + * @param value new value + */ +typedef void (*lv_obj_set_bool_t)(lv_obj_t * obj, bool value); + +/** + * Generic callback called to set an int value on a Widget. + * @param obj pointer to Widget + * @param value new value + */ +typedef void (*lv_obj_set_int_t)(lv_obj_t * obj, int32_t value); + +/** + * Generic callback called to set a float value on a Widget. + * @param obj pointer to Widget + * @param value new value + */ +typedef void (*lv_obj_set_float_t)(lv_obj_t * obj, float value); + +/** + * Generic callback called to set a string value on a Widget. + * @param obj pointer to Widget + * @param value new value + */ +typedef void (*lv_obj_set_string_t)(lv_obj_t * obj, const char * value); + +/** + * Generic callback called to set a color value on a Widget. + * @param obj pointer to Widget + * @param value new value + */ +typedef void (*lv_obj_set_color_t)(lv_obj_t * obj, lv_color_t value); + +/** + * Generic callback called to set a pointer value on a Widget. + * @param obj pointer to Widget + * @param value new value + */ +typedef void (*lv_obj_set_pointer_t)(lv_obj_t * obj, const void * value); + + /********************** * GLOBAL PROTOTYPES **********************/ -#if LV_USE_EXT_DATA -/** - * @brief Attaches external user data to an integer Subject with lifecycle management - * - * Associates arbitrary user-defined data with an LVGL observer and registers a destructor - * callback that will be automatically invoked when the observer is deleted. This enables: - * - Safe resource cleanup through the destructor mechanism - * - Contextual data storage for observer callbacks - * - Proper memory management for observer-related resources - * - * @param subject pointer to Subject - * @param data User-defined data pointer to associate @nullable - * @param free_cb Cleanup function called when: @nullable - * - Observer is explicitly deleted - * - Observed object is deleted - * - New data replaces current association - * NULL indicates no cleanup required - */ -void lv_subject_set_external_data(lv_subject_t * subject, void * data, void (* free_cb)(void * data)); -#endif - /** * Initialize an integer-type Subject. * @param subject pointer to Subject @@ -231,8 +255,6 @@ const char * lv_subject_get_string(lv_subject_t * subject); * Get previous value of a string Subject. * @param subject pointer to Subject * @return pointer to buffer containing previous value - * @note NULL will be returned if NULL was passed in `lv_subject_init_string()` - * as `prev_buf` or if `subject` is NULL or not of string type. */ const char * lv_subject_get_previous_string(lv_subject_t * subject); @@ -296,7 +318,7 @@ lv_color_t lv_subject_get_previous_color(lv_subject_t * subject); * Initialize a Group-type Subject. * @param group_subject pointer to Group-type Subject * @param list list of other Subject addresses; when any of these have values - updated, Observers of `group_subject` will be notified. + * updated, Observers of `group_subject` will be notified. * @param list_len number of elements in `list[]` */ void lv_subject_init_group(lv_subject_t * group_subject, lv_subject_t * list[], uint32_t list_len); @@ -397,6 +419,28 @@ lv_obj_t * lv_observer_get_target_obj(lv_observer_t * observer); */ void * lv_observer_get_user_data(const lv_observer_t * observer); +#if LV_USE_EXT_DATA + +/** + * @brief Attaches external user data to an integer Subject with lifecycle management + * + * Associates arbitrary user-defined data with an LVGL observer and registers a destructor + * callback that will be automatically invoked when the observer is deleted. This enables: + * - Safe resource cleanup through the destructor mechanism + * - Contextual data storage for observer callbacks + * - Proper memory management for observer-related resources + * + * @param subject pointer to Subject + * @param data User-defined data pointer to associate @nullable + * @param free_cb Cleanup function called when: @nullable + * - Observer is explicitly deleted + * - Observed object is deleted + * - New data replaces current association + * NULL indicates no cleanup required + */ +void lv_subject_set_external_data(lv_subject_t * subject, void * data, void (* free_cb)(void * data)); +#endif + /** * Set Observer's user data. * @param observer pointer to Observer @@ -486,6 +530,69 @@ void lv_obj_add_subject_set_float_event(lv_obj_t * obj, lv_subject_t * subject, void lv_obj_add_subject_set_string_event(lv_obj_t * obj, lv_subject_t * subject, lv_event_code_t trigger, const char * value); +/** + * Bind a boolean value to a Widget: `set_bool_cb` is called with the Subject's + * value (as a `bool`) on subscribing and whenever it changes. A dedicated per-flag + * setter such as `lv_obj_set_hidden` can be passed directly. + * @param obj pointer to Widget + * @param subject pointer to an integer Subject + * @param set_bool_cb callback that applies the boolean value to the Widget + * @return pointer to newly-created Observer + */ +lv_observer_t * lv_obj_bind_bool(lv_obj_t * obj, lv_subject_t * subject, lv_obj_set_bool_t set_bool_cb); + +/** + * Bind an integer value to a Widget: `set_int_cb` is called with the Subject's + * value on subscribing and whenever it changes. + * @param obj pointer to Widget + * @param subject pointer to an integer Subject + * @param set_int_cb callback that applies the integer value to the Widget + * @return pointer to newly-created Observer + */ +lv_observer_t * lv_obj_bind_int(lv_obj_t * obj, lv_subject_t * subject, lv_obj_set_int_t set_int_cb); + +#if LV_USE_FLOAT +/** + * Bind a float value to a Widget: `set_float_cb` is called with the Subject's + * value on subscribing and whenever it changes. + * @param obj pointer to Widget + * @param subject pointer to a float Subject + * @param set_float_cb callback that applies the float value to the Widget + * @return pointer to newly-created Observer + */ +lv_observer_t * lv_obj_bind_float(lv_obj_t * obj, lv_subject_t * subject, lv_obj_set_float_t set_float_cb); +#endif + +/** + * Bind a string value to a Widget: `set_string_cb` is called with the Subject's + * value on subscribing and whenever it changes. + * @param obj pointer to Widget + * @param subject pointer to a string Subject + * @param set_string_cb callback that applies the string value to the Widget + * @return pointer to newly-created Observer + */ +lv_observer_t * lv_obj_bind_string(lv_obj_t * obj, lv_subject_t * subject, lv_obj_set_string_t set_string_cb); + +/** + * Bind a color value to a Widget: `set_color_cb` is called with the Subject's + * value on subscribing and whenever it changes. + * @param obj pointer to Widget + * @param subject pointer to a color Subject + * @param set_color_cb callback that applies the color value to the Widget + * @return pointer to newly-created Observer + */ +lv_observer_t * lv_obj_bind_color(lv_obj_t * obj, lv_subject_t * subject, lv_obj_set_color_t set_color_cb); + +/** + * Bind a pointer value to a Widget: `set_pointer_cb` is called with the Subject's + * value on subscribing and whenever it changes. + * @param obj pointer to Widget + * @param subject pointer to a pointer Subject + * @param set_pointer_cb callback that applies the pointer value to the Widget + * @return pointer to newly-created Observer + */ +lv_observer_t * lv_obj_bind_pointer(lv_obj_t * obj, lv_subject_t * subject, lv_obj_set_pointer_t set_pointer_cb); + /** * Set Widget's flag(s) if an integer Subject's value is equal to a reference value, clear flag otherwise. * @param obj pointer to Widget @@ -493,7 +600,9 @@ void lv_obj_add_subject_set_string_event(lv_obj_t * obj, lv_subject_t * subject, * @param flag flag(s) (can be bit-wise OR-ed) to set or clear (e.g. `LV_OBJ_FLAG_HIDDEN`) * @param ref_value reference value to compare Subject's value with * @return pointer to newly-created Observer + * @deprecated Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead. */ +LV_DEPRECATED("Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead.") lv_observer_t * lv_obj_bind_flag_if_eq(lv_obj_t * obj, lv_subject_t * subject, lv_obj_flag_t flag, int32_t ref_value); /** @@ -503,7 +612,9 @@ lv_observer_t * lv_obj_bind_flag_if_eq(lv_obj_t * obj, lv_subject_t * subject, l * @param flag flag(s) (can be bit-wise OR-ed) to set or clear (e.g. `LV_OBJ_FLAG_HIDDEN`) * @param ref_value reference value to compare Subject's value with * @return pointer to newly-created Observer + * @deprecated Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead. */ +LV_DEPRECATED("Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead.") lv_observer_t * lv_obj_bind_flag_if_not_eq(lv_obj_t * obj, lv_subject_t * subject, lv_obj_flag_t flag, int32_t ref_value); @@ -514,7 +625,9 @@ lv_observer_t * lv_obj_bind_flag_if_not_eq(lv_obj_t * obj, lv_subject_t * subjec * @param flag flag(s) (can be bit-wise OR-ed) to set or clear (e.g. `LV_OBJ_FLAG_HIDDEN`) * @param ref_value reference value to compare Subject's value with * @return pointer to newly-created Observer + * @deprecated Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead. */ +LV_DEPRECATED("Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead.") lv_observer_t * lv_obj_bind_flag_if_gt(lv_obj_t * obj, lv_subject_t * subject, lv_obj_flag_t flag, int32_t ref_value); /** @@ -524,7 +637,9 @@ lv_observer_t * lv_obj_bind_flag_if_gt(lv_obj_t * obj, lv_subject_t * subject, l * @param flag flag(s) (can be bit-wise OR-ed) to set or clear (e.g. `LV_OBJ_FLAG_HIDDEN`) * @param ref_value reference value to compare Subject's value with * @return pointer to newly-created Observer + * @deprecated Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead. */ +LV_DEPRECATED("Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead.") lv_observer_t * lv_obj_bind_flag_if_ge(lv_obj_t * obj, lv_subject_t * subject, lv_obj_flag_t flag, int32_t ref_value); /** @@ -534,7 +649,9 @@ lv_observer_t * lv_obj_bind_flag_if_ge(lv_obj_t * obj, lv_subject_t * subject, l * @param flag flag(s) (can be bit-wise OR-ed) to set or clear (e.g. `LV_OBJ_FLAG_HIDDEN`) * @param ref_value reference value to compare Subject's value with * @return pointer to newly-created Observer + * @deprecated Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead. */ +LV_DEPRECATED("Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead.") lv_observer_t * lv_obj_bind_flag_if_lt(lv_obj_t * obj, lv_subject_t * subject, lv_obj_flag_t flag, int32_t ref_value); /** @@ -544,7 +661,9 @@ lv_observer_t * lv_obj_bind_flag_if_lt(lv_obj_t * obj, lv_subject_t * subject, l * @param flag flag(s) (can be bit-wise OR-ed) to set or clear (e.g. `LV_OBJ_FLAG_HIDDEN`) * @param ref_value reference value to compare Subject's value with * @return pointer to newly-created Observer + * @deprecated Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead. */ +LV_DEPRECATED("Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead.") lv_observer_t * lv_obj_bind_flag_if_le(lv_obj_t * obj, lv_subject_t * subject, lv_obj_flag_t flag, int32_t ref_value); @@ -555,7 +674,9 @@ lv_observer_t * lv_obj_bind_flag_if_le(lv_obj_t * obj, lv_subject_t * subject, l * @param state state(s) (can be bit-wise OR-ed) to set or clear (e.g. `LV_STATE_CHECKED`) * @param ref_value reference value to compare Subject's value with * @return pointer to newly-created Observer + * @deprecated Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead. */ +LV_DEPRECATED("Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead.") lv_observer_t * lv_obj_bind_state_if_eq(lv_obj_t * obj, lv_subject_t * subject, lv_state_t state, int32_t ref_value); /** @@ -565,7 +686,9 @@ lv_observer_t * lv_obj_bind_state_if_eq(lv_obj_t * obj, lv_subject_t * subject, * @param state state(s) (can be bit-wise OR-ed) to set or clear (e.g. `LV_STATE_CHECKED`) * @param ref_value reference value to compare Subject's value with * @return pointer to newly-created Observer + * @deprecated Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead. */ +LV_DEPRECATED("Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead.") lv_observer_t * lv_obj_bind_state_if_not_eq(lv_obj_t * obj, lv_subject_t * subject, lv_state_t state, int32_t ref_value); @@ -576,7 +699,9 @@ lv_observer_t * lv_obj_bind_state_if_not_eq(lv_obj_t * obj, lv_subject_t * subje * @param state state(s) (can be bit-wise OR-ed) to set or clear (e.g. `LV_STATE_CHECKED`) * @param ref_value reference value to compare Subject's value with * @return pointer to newly-created Observer + * @deprecated Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead. */ +LV_DEPRECATED("Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead.") lv_observer_t * lv_obj_bind_state_if_gt(lv_obj_t * obj, lv_subject_t * subject, lv_state_t state, int32_t ref_value); /** @@ -586,7 +711,9 @@ lv_observer_t * lv_obj_bind_state_if_gt(lv_obj_t * obj, lv_subject_t * subject, * @param state state(s) (can be bit-wise OR-ed) to set or clear (e.g. `LV_STATE_CHECKED`) * @param ref_value reference value to compare Subject's value with * @return pointer to newly-created Observer + * @deprecated Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead. */ +LV_DEPRECATED("Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead.") lv_observer_t * lv_obj_bind_state_if_ge(lv_obj_t * obj, lv_subject_t * subject, lv_state_t state, int32_t ref_value); /** @@ -596,7 +723,9 @@ lv_observer_t * lv_obj_bind_state_if_ge(lv_obj_t * obj, lv_subject_t * subject, * @param state state(s) (can be bit-wise OR-ed) to set or clear (e.g. `LV_STATE_CHECKED`) * @param ref_value reference value to compare Subject's value with * @return pointer to newly-created Observer + * @deprecated Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead. */ +LV_DEPRECATED("Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead.") lv_observer_t * lv_obj_bind_state_if_lt(lv_obj_t * obj, lv_subject_t * subject, lv_state_t state, int32_t ref_value); /** @@ -606,7 +735,9 @@ lv_observer_t * lv_obj_bind_state_if_lt(lv_obj_t * obj, lv_subject_t * subject, * @param state state(s) (can be bit-wise OR-ed) to set or clear (e.g. `LV_STATE_CHECKED`) * @param ref_value reference value to compare Subject's value with * @return pointer to newly-created Observer + * @deprecated Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead. */ +LV_DEPRECATED("Use `lv_obj_bind_bool()` or `lv_subject_add_observer_obj()` instead.") lv_observer_t * lv_obj_bind_state_if_le(lv_obj_t * obj, lv_subject_t * subject, lv_state_t state, int32_t ref_value); /** diff --git a/src/core/lv_obj_style.c b/src/core/lv_obj_style.c index f12528e96e..25e5f383c8 100644 --- a/src/core/lv_obj_style.c +++ b/src/core/lv_obj_style.c @@ -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); diff --git a/src/core/lv_observer.c b/src/core/lv_observer.c index 3fb5186508..b2c6b520a6 100644 --- a/src/core/lv_observer.c +++ b/src/core/lv_observer.c @@ -78,6 +78,16 @@ static void lv_subject_notify_if_changed(lv_subject_t * subject); static void subject_set_string_free_user_data_event_cb(lv_event_t * e); +static void set_bool_observer(lv_observer_t * observer, lv_subject_t * subject); +static void set_int_observer(lv_observer_t * observer, lv_subject_t * subject); +#if LV_USE_FLOAT + static void set_float_observer(lv_observer_t * observer, lv_subject_t * subject); +#endif /*LV_USE_FLOAT*/ + +static void set_string_observer(lv_observer_t * observer, lv_subject_t * subject); +static void set_color_observer(lv_observer_t * observer, lv_subject_t * subject); +static void set_pointer_observer(lv_observer_t * observer, lv_subject_t * subject); + /********************** * STATIC VARIABLES **********************/ @@ -371,15 +381,14 @@ void lv_subject_init_group(lv_subject_t * group_subject, lv_subject_t * list[], LV_CHECK_ARG(group_subject != NULL, return); LV_CHECK_ARG(list != NULL, return); + lv_memzero(group_subject, sizeof(lv_subject_t)); + lv_ll_init(&(group_subject->subs_ll), sizeof(lv_observer_t)); group_subject->type = LV_SUBJECT_TYPE_GROUP; group_subject->size = list_len; - lv_ll_init(&(group_subject->subs_ll), sizeof(lv_observer_t)); group_subject->value.pointer = list; - /* Bind all list[] subjects to `group_subject`. */ - uint32_t i; - for(i = 0; i < list_len; i++) { - /* If a subject in `list[]` changes, notify `group_subject`. */ + /* Bind all new subjects in `list[]` to `group_subject`. */ + for(uint32_t i = 0; i < list_len; i++) { lv_subject_add_observer(list[i], group_notify_cb, group_subject); } } @@ -468,7 +477,7 @@ lv_observer_t * lv_subject_add_observer_with_target(lv_subject_t * subject, lv_o observer->target = target; /* Update Observer immediately. */ - if(observer->cb) observer->cb(observer, subject); + observer->cb(observer, subject); return observer; } @@ -714,6 +723,120 @@ void lv_obj_add_subject_set_string_event(lv_obj_t * obj, lv_subject_t * subject, lv_obj_add_event_cb(obj, subject_set_string_free_user_data_event_cb, LV_EVENT_DELETE, user_data); } +lv_observer_t * lv_obj_bind_bool(lv_obj_t * obj, lv_subject_t * subject, lv_obj_set_bool_t set_bool_cb) +{ + LV_CHECK_ARG(obj != NULL, return NULL); + LV_CHECK_ARG(subject != NULL, return NULL); + LV_CHECK_ARG(set_bool_cb != NULL, return NULL); + LV_CHECK_ARG(subject->type == LV_SUBJECT_TYPE_INT, return NULL); + + lv_observer_t * observable = lv_subject_add_observer_obj(subject, set_bool_observer, obj, NULL); + if(observable == NULL) { + LV_LOG_WARN("Couldn't add observer to subject"); + return NULL; + } + + /* Passing a function pointer as void * user_data generates warning so set it here, and call the callback manually */ + observable->user_cb = (void (*)(void))set_bool_cb; + set_bool_observer(observable, subject); + return observable; +} + +lv_observer_t * lv_obj_bind_int(lv_obj_t * obj, lv_subject_t * subject, lv_obj_set_int_t set_int_cb) +{ + LV_CHECK_ARG(obj != NULL, return NULL); + LV_CHECK_ARG(subject != NULL, return NULL); + LV_CHECK_ARG(set_int_cb != NULL, return NULL); + LV_CHECK_ARG(subject->type == LV_SUBJECT_TYPE_INT, return NULL); + lv_observer_t * observable = lv_subject_add_observer_obj(subject, set_int_observer, obj, NULL); + if(observable == NULL) { + LV_LOG_WARN("Couldn't add observer to subject"); + return NULL; + } + + /* Passing a function pointer as void * user_data generates warning so set it here, and call the callback manually */ + observable->user_cb = (void (*)(void))set_int_cb; + set_int_observer(observable, subject); + return observable; +} + +#if LV_USE_FLOAT +lv_observer_t * lv_obj_bind_float(lv_obj_t * obj, lv_subject_t * subject, lv_obj_set_float_t set_float_cb) +{ + LV_CHECK_ARG(obj != NULL, return NULL); + LV_CHECK_ARG(subject != NULL, return NULL); + LV_CHECK_ARG(set_float_cb != NULL, return NULL); + LV_CHECK_ARG(subject->type == LV_SUBJECT_TYPE_FLOAT, return NULL); + + lv_observer_t * observable = lv_subject_add_observer_obj(subject, set_float_observer, obj, NULL); + if(observable == NULL) { + LV_LOG_WARN("Couldn't add observer to subject"); + return NULL; + } + + /* Passing a function pointer as void * user_data generates warning so set it here, and call the callback manually */ + observable->user_cb = (void (*)(void))set_float_cb; + set_float_observer(observable, subject); + return observable; +} +#endif + +lv_observer_t * lv_obj_bind_string(lv_obj_t * obj, lv_subject_t * subject, lv_obj_set_string_t set_string_cb) +{ + LV_CHECK_ARG(obj != NULL, return NULL); + LV_CHECK_ARG(subject != NULL, return NULL); + LV_CHECK_ARG(set_string_cb != NULL, return NULL); + LV_CHECK_ARG(subject->type == LV_SUBJECT_TYPE_STRING, return NULL); + + lv_observer_t * observable = lv_subject_add_observer_obj(subject, set_string_observer, obj, NULL); + if(observable == NULL) { + LV_LOG_WARN("Couldn't add observer to subject"); + return NULL; + } + + /* Passing a function pointer as void * user_data generates warning so set it here, and call the callback manually */ + observable->user_cb = (void (*)(void))set_string_cb; + set_string_observer(observable, subject); + return observable; +} + +lv_observer_t * lv_obj_bind_color(lv_obj_t * obj, lv_subject_t * subject, lv_obj_set_color_t set_color_cb) +{ + LV_CHECK_ARG(obj != NULL, return NULL); + LV_CHECK_ARG(subject != NULL, return NULL); + LV_CHECK_ARG(set_color_cb != NULL, return NULL); + LV_CHECK_ARG(subject->type == LV_SUBJECT_TYPE_COLOR, return NULL); + + lv_observer_t * observable = lv_subject_add_observer_obj(subject, set_color_observer, obj, NULL); + if(observable == NULL) { + LV_LOG_WARN("Couldn't add observer to subject"); + return NULL; + } + + /* Passing a function pointer as void * user_data generates warning so set it here, and call the callback manually */ + observable->user_cb = (void (*)(void))set_color_cb; + set_color_observer(observable, subject); + return observable; +} + +lv_observer_t * lv_obj_bind_pointer(lv_obj_t * obj, lv_subject_t * subject, lv_obj_set_pointer_t set_pointer_cb) +{ + LV_CHECK_ARG(obj != NULL, return NULL); + LV_CHECK_ARG(subject != NULL, return NULL); + LV_CHECK_ARG(set_pointer_cb != NULL, return NULL); + LV_CHECK_ARG(subject->type == LV_SUBJECT_TYPE_POINTER, return NULL); + + lv_observer_t * observable = lv_subject_add_observer_obj(subject, set_pointer_observer, obj, NULL); + if(observable == NULL) { + LV_LOG_WARN("Couldn't add observer to subject"); + return NULL; + } + + /* Passing a function pointer as void * user_data generates warning so set it here, and call the callback manually */ + observable->user_cb = (void (*)(void))set_pointer_cb; + set_pointer_observer(observable, subject); + return observable; +} lv_observer_t * lv_obj_bind_flag_if_eq(lv_obj_t * obj, lv_subject_t * subject, lv_obj_flag_t flag, int32_t ref_value) { @@ -1160,5 +1283,49 @@ static void subject_set_string_free_user_data_event_cb(lv_event_t * e) lv_free(user_data); } +static void set_bool_observer(lv_observer_t * observer, lv_subject_t * subject) +{ + lv_obj_t * obj = (lv_obj_t *)observer->target; + lv_obj_set_bool_t set_bool_cb = (lv_obj_set_bool_t)observer->user_cb; + if(set_bool_cb) set_bool_cb(obj, subject->value.num); +} + +static void set_int_observer(lv_observer_t * observer, lv_subject_t * subject) +{ + lv_obj_t * obj = (lv_obj_t *)observer->target; + lv_obj_set_int_t set_int_cb = (lv_obj_set_int_t)observer->user_cb; + if(set_int_cb) set_int_cb(obj, subject->value.num); +} + +#if LV_USE_FLOAT +static void set_float_observer(lv_observer_t * observer, lv_subject_t * subject) +{ + lv_obj_t * obj = (lv_obj_t *)observer->target; + lv_obj_set_float_t set_float_cb = (lv_obj_set_float_t)observer->user_cb; + if(set_float_cb) set_float_cb(obj, subject->value.float_v); +} +#endif /*LV_USE_FLOAT*/ + +static void set_string_observer(lv_observer_t * observer, lv_subject_t * subject) +{ + lv_obj_t * obj = (lv_obj_t *)observer->target; + lv_obj_set_string_t set_string_cb = (lv_obj_set_string_t)observer->user_cb; + if(set_string_cb) set_string_cb(obj, subject->value.pointer); +} + + +static void set_color_observer(lv_observer_t * observer, lv_subject_t * subject) +{ + lv_obj_t * obj = (lv_obj_t *)observer->target; + lv_obj_set_color_t set_color_cb = (lv_obj_set_color_t)observer->user_cb; + if(set_color_cb) set_color_cb(obj, subject->value.color); +} + +static void set_pointer_observer(lv_observer_t * observer, lv_subject_t * subject) +{ + lv_obj_t * obj = (lv_obj_t *)observer->target; + lv_obj_set_pointer_t set_pointer_cb = (lv_obj_set_pointer_t)observer->user_cb; + if(set_pointer_cb) set_pointer_cb(obj, subject->value.pointer); +} #endif /*LV_USE_OBSERVER*/ diff --git a/src/core/lv_observer_private.h b/src/core/lv_observer_private.h index 794e3b9816..425470fbcc 100644 --- a/src/core/lv_observer_private.h +++ b/src/core/lv_observer_private.h @@ -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 *`)? */ diff --git a/src/widgets/label/lv_label.c b/src/widgets/label/lv_label.c index fb55d62f08..76a085f47b 100644 --- a/src/widgets/label/lv_label.c +++ b/src/widgets/label/lv_label.c @@ -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); diff --git a/src/widgets/scale/lv_scale.c b/src/widgets/scale/lv_scale.c index c1a79a6ba8..631da7aa6a 100644 --- a/src/widgets/scale/lv_scale.c +++ b/src/widgets/scale/lv_scale.c @@ -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); } diff --git a/src/widgets/span/lv_span.c b/src/widgets/span/lv_span.c index 27f05d59bf..db7e532154 100644 --- a/src/widgets/span/lv_span.c +++ b/src/widgets/span/lv_span.c @@ -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) { diff --git a/tests/src/test_cases/test_observer.c b/tests/src/test_cases/test_observer.c index 0ee774feae..390728cc51 100644 --- a/tests/src/test_cases/test_observer.c +++ b/tests/src/test_cases/test_observer.c @@ -61,7 +61,7 @@ void test_observer_add_remove(void) void test_object_observer_add_remove(void) { - + LV_DEPRECATIONS_IGNORE_BEGIN lv_obj_t * obj = lv_obj_create(lv_screen_active()); static lv_subject_t subject; lv_subject_init_int(&subject, 1); @@ -78,6 +78,7 @@ void test_object_observer_add_remove(void) TEST_ASSERT_EQUAL(true, lv_obj_is_hidden(obj)); lv_obj_delete(obj); /* We shouldn't crash here */ + LV_DEPRECATIONS_IGNORE_END } static lv_event_dsc_t * get_event_delete_from_obj(lv_obj_t * obj) @@ -444,12 +445,20 @@ void test_observer_color(void) } static int32_t group_observer_called; +static int32_t group_value_sum; static void group_observer_cb(lv_observer_t * observer, lv_subject_t * subject) { LV_UNUSED(observer); LV_UNUSED(subject); group_observer_called++; + + group_value_sum = 0; + uint32_t i; + for(i = 0; i < subject->size; i++) { + lv_subject_t * sub = ((lv_subject_t **)(subject->value.pointer))[i]; + group_value_sum += lv_subject_get_int(sub); + } } void test_observer_group(void) @@ -457,10 +466,16 @@ void test_observer_group(void) static lv_subject_t subject_main; static lv_subject_t subject_sub1; static lv_subject_t subject_sub2; + static lv_subject_t subject_sub3; + static lv_subject_t subject_sub4; + static lv_subject_t subject_sub5; static lv_subject_t * subject_list[2] = { &subject_sub1, &subject_sub2 }; lv_subject_init_int(&subject_sub1, 1); lv_subject_init_int(&subject_sub2, 2); + lv_subject_init_int(&subject_sub3, 3); + lv_subject_init_int(&subject_sub4, 4); + lv_subject_init_int(&subject_sub5, 5); lv_subject_init_group(&subject_main, subject_list, 2); TEST_ASSERT_EQUAL_PTR(&subject_sub1, @@ -475,16 +490,20 @@ void test_observer_group(void) group_observer_called = 0; lv_subject_add_observer(&subject_main, group_observer_cb, NULL); TEST_ASSERT_EQUAL(1, group_observer_called); + TEST_ASSERT_EQUAL(1 + 2, group_value_sum); lv_subject_set_int(&subject_sub1, 10); TEST_ASSERT_EQUAL(2, group_observer_called); + TEST_ASSERT_EQUAL(10 + 2, group_value_sum); lv_subject_set_int(&subject_sub2, 20); TEST_ASSERT_EQUAL(3, group_observer_called); + TEST_ASSERT_EQUAL(10 + 20, group_value_sum); } void test_observer_obj_flag_invalid_subject(void) { + LV_DEPRECATIONS_IGNORE_BEGIN typedef lv_observer_t * (*lv_obj_bind_flag_fn)( lv_obj_t *, lv_subject_t *, lv_obj_flag_t, int32_t); static const lv_obj_bind_flag_fn fns[] = { @@ -492,6 +511,7 @@ void test_observer_obj_flag_invalid_subject(void) lv_obj_bind_flag_if_ge, lv_obj_bind_flag_if_gt, lv_obj_bind_flag_if_lt, lv_obj_bind_flag_if_le, }; + LV_DEPRECATIONS_IGNORE_END static lv_subject_t invalid_subjects[4]; static char buf1[30]; @@ -520,6 +540,7 @@ void test_observer_obj_flag_invalid_subject(void) } void test_observer_obj_flag_eq(void) { + LV_DEPRECATIONS_IGNORE_BEGIN lv_obj_t * obj = lv_obj_create(lv_screen_active()); static lv_subject_t subject; lv_subject_init_int(&subject, 1); @@ -540,10 +561,12 @@ void test_observer_obj_flag_eq(void) lv_subject_set_int(&subject, 10); TEST_ASSERT_EQUAL(false, lv_obj_is_hidden(obj)); TEST_ASSERT_EQUAL(false, lv_obj_is_checkable(obj)); + LV_DEPRECATIONS_IGNORE_END } void test_observer_obj_flag_ge(void) { + LV_DEPRECATIONS_IGNORE_BEGIN lv_obj_t * obj = lv_obj_create(lv_screen_active()); static lv_subject_t subject; lv_subject_init_int(&subject, 1); @@ -560,10 +583,12 @@ void test_observer_obj_flag_ge(void) lv_subject_set_int(&subject, 6); TEST_ASSERT_EQUAL(true, lv_obj_is_hidden(obj)); + LV_DEPRECATIONS_IGNORE_END } void test_observer_obj_flag_gt(void) { + LV_DEPRECATIONS_IGNORE_BEGIN lv_obj_t * obj = lv_obj_create(lv_screen_active()); static lv_subject_t subject; lv_subject_init_int(&subject, 1); @@ -580,10 +605,12 @@ void test_observer_obj_flag_gt(void) lv_subject_set_int(&subject, 4); TEST_ASSERT_EQUAL(false, lv_obj_is_hidden(obj)); + LV_DEPRECATIONS_IGNORE_END } void test_observer_obj_flag_le(void) { + LV_DEPRECATIONS_IGNORE_BEGIN lv_obj_t * obj = lv_obj_create(lv_screen_active()); static lv_subject_t subject; lv_subject_init_int(&subject, 7); @@ -600,10 +627,12 @@ void test_observer_obj_flag_le(void) lv_subject_set_int(&subject, 4); TEST_ASSERT_EQUAL(true, lv_obj_is_hidden(obj)); + LV_DEPRECATIONS_IGNORE_END } void test_observer_obj_flag_lt(void) { + LV_DEPRECATIONS_IGNORE_BEGIN lv_obj_t * obj = lv_obj_create(lv_screen_active()); static lv_subject_t subject; lv_subject_init_int(&subject, 7); @@ -620,10 +649,12 @@ void test_observer_obj_flag_lt(void) lv_subject_set_int(&subject, 3); TEST_ASSERT_EQUAL(true, lv_obj_is_hidden(obj)); + LV_DEPRECATIONS_IGNORE_END } void test_observer_obj_state_invalid_subject(void) { + LV_DEPRECATIONS_IGNORE_BEGIN typedef lv_observer_t * (*lv_obj_bind_state_fn)( lv_obj_t *, lv_subject_t *, lv_state_t, int32_t); @@ -657,10 +688,12 @@ void test_observer_obj_state_invalid_subject(void) NULL, fns[i](obj, &invalid_subjects[j], 0, 5)); } } + LV_DEPRECATIONS_IGNORE_END } void test_observer_obj_state_eq(void) { + LV_DEPRECATIONS_IGNORE_BEGIN lv_obj_t * obj = lv_obj_create(lv_screen_active()); static lv_subject_t subject; lv_subject_init_int(&subject, 1); @@ -681,10 +714,12 @@ void test_observer_obj_state_eq(void) lv_subject_set_int(&subject, 10); TEST_ASSERT_EQUAL(false, lv_obj_has_state(obj, LV_STATE_CHECKED)); TEST_ASSERT_EQUAL(false, lv_obj_has_state(obj, LV_STATE_DISABLED)); + LV_DEPRECATIONS_IGNORE_END } void test_observer_obj_state_gt(void) { + LV_DEPRECATIONS_IGNORE_BEGIN lv_obj_t * obj = lv_obj_create(lv_screen_active()); static lv_subject_t subject; lv_subject_init_int(&subject, 1); @@ -701,10 +736,12 @@ void test_observer_obj_state_gt(void) lv_subject_set_int(&subject, 7); TEST_ASSERT_EQUAL(true, lv_obj_has_state(obj, LV_STATE_CHECKED)); + LV_DEPRECATIONS_IGNORE_END } void test_observer_obj_state_ge(void) { + LV_DEPRECATIONS_IGNORE_BEGIN lv_obj_t * obj = lv_obj_create(lv_screen_active()); static lv_subject_t subject; lv_subject_init_int(&subject, 1); @@ -721,10 +758,12 @@ void test_observer_obj_state_ge(void) lv_subject_set_int(&subject, 5); TEST_ASSERT_EQUAL(true, lv_obj_has_state(obj, LV_STATE_CHECKED)); + LV_DEPRECATIONS_IGNORE_END } void test_observer_obj_state_le(void) { + LV_DEPRECATIONS_IGNORE_BEGIN lv_obj_t * obj = lv_obj_create(lv_screen_active()); static lv_subject_t subject; lv_subject_init_int(&subject, 1); @@ -738,10 +777,12 @@ void test_observer_obj_state_le(void) lv_subject_set_int(&subject, 4); TEST_ASSERT_EQUAL(true, lv_obj_has_state(obj, LV_STATE_CHECKED)); + LV_DEPRECATIONS_IGNORE_END } void test_observer_obj_state_lt(void) { + LV_DEPRECATIONS_IGNORE_BEGIN lv_obj_t * obj = lv_obj_create(lv_screen_active()); static lv_subject_t subject; lv_subject_init_int(&subject, 1); @@ -755,6 +796,194 @@ void test_observer_obj_state_lt(void) lv_subject_set_int(&subject, 4); TEST_ASSERT_EQUAL(true, lv_obj_has_state(obj, LV_STATE_CHECKED)); + LV_DEPRECATIONS_IGNORE_END +} + +/* Recommended replacement for lv_obj_bind_flag_if_*: bind a flag from a boolean + * subject by passing a dedicated per-flag setter directly to lv_obj_bind_bool. */ +void test_observer_obj_bind_bool_flag(void) +{ + lv_obj_t * obj = lv_obj_create(lv_screen_active()); + static lv_subject_t subject; + lv_subject_init_int(&subject, 0); + + lv_observer_t * observer = lv_obj_bind_bool(obj, &subject, lv_obj_set_hidden); + TEST_ASSERT_NOT_NULL(observer); + + /*Applied immediately: 0 -> not hidden*/ + TEST_ASSERT_EQUAL(false, lv_obj_is_hidden(obj)); + + /*Any non-zero value -> hidden*/ + lv_subject_set_int(&subject, 1); + TEST_ASSERT_EQUAL(true, lv_obj_is_hidden(obj)); + + lv_subject_set_int(&subject, 5); + TEST_ASSERT_EQUAL(true, lv_obj_is_hidden(obj)); + + lv_subject_set_int(&subject, 0); + TEST_ASSERT_EQUAL(false, lv_obj_is_hidden(obj)); + + /*After removing the observer the widget is no longer updated*/ + lv_observer_delete(observer); + lv_subject_set_int(&subject, 1); + TEST_ASSERT_EQUAL(false, lv_obj_is_hidden(obj)); + + lv_obj_delete(obj); +} + +void test_observer_obj_bind_bool_invalid(void) +{ + lv_obj_t * obj = lv_obj_create(lv_screen_active()); + static lv_subject_t subject; + lv_subject_init_int(&subject, 0); + + TEST_ASSERT_EQUAL_PTR(NULL, lv_obj_bind_bool(NULL, &subject, lv_obj_set_hidden)); + TEST_ASSERT_EQUAL_PTR(NULL, lv_obj_bind_bool(obj, NULL, lv_obj_set_hidden)); + TEST_ASSERT_EQUAL_PTR(NULL, lv_obj_bind_bool(obj, &subject, NULL)); + + lv_obj_delete(obj); +} + +/* The remaining lv_obj_bind_() functions just forward the subject's value + * to the setter; these tests confirm the value arrives on subscribe and update. */ +static int32_t captured_int; +static void capture_int_cb(lv_obj_t * obj, int32_t v) +{ + LV_UNUSED(obj); + captured_int = v; +} + +void test_observer_obj_bind_int(void) +{ + lv_obj_t * obj = lv_obj_create(lv_screen_active()); + static lv_subject_t subject; + lv_subject_init_int(&subject, 10); + + TEST_ASSERT_NOT_NULL(lv_obj_bind_int(obj, &subject, capture_int_cb)); + TEST_ASSERT_EQUAL_INT(10, captured_int); + lv_subject_set_int(&subject, 42); + TEST_ASSERT_EQUAL_INT(42, captured_int); + + lv_obj_delete(obj); +} + +#if LV_USE_FLOAT +static float captured_float; +static void capture_float_cb(lv_obj_t * obj, float v) +{ + LV_UNUSED(obj); + captured_float = v; +} + +void test_observer_obj_bind_float(void) +{ + lv_obj_t * obj = lv_obj_create(lv_screen_active()); + static lv_subject_t subject; + lv_subject_init_float(&subject, 1.5f); + + TEST_ASSERT_NOT_NULL(lv_obj_bind_float(obj, &subject, capture_float_cb)); + TEST_ASSERT_EQUAL_FLOAT(1.5f, captured_float); + lv_subject_set_float(&subject, 2.5f); + TEST_ASSERT_EQUAL_FLOAT(2.5f, captured_float); + + lv_obj_delete(obj); +} +#endif + +static const char * captured_string; +static void capture_string_cb(lv_obj_t * obj, const char * v) +{ + LV_UNUSED(obj); + captured_string = v; +} + +void test_observer_obj_bind_string(void) +{ + lv_obj_t * obj = lv_obj_create(lv_screen_active()); + static char buf[32]; + static char prev_buf[32]; + static lv_subject_t subject; + lv_subject_init_string(&subject, buf, prev_buf, sizeof(buf), "hello"); + + TEST_ASSERT_NOT_NULL(lv_obj_bind_string(obj, &subject, capture_string_cb)); + TEST_ASSERT_EQUAL_STRING("hello", captured_string); + lv_subject_copy_string(&subject, "world"); + TEST_ASSERT_EQUAL_STRING("world", captured_string); + + lv_obj_delete(obj); +} + +static lv_color_t captured_color; +static void capture_color_cb(lv_obj_t * obj, lv_color_t v) +{ + LV_UNUSED(obj); + captured_color = v; +} + +void test_observer_obj_bind_color(void) +{ + lv_obj_t * obj = lv_obj_create(lv_screen_active()); + static lv_subject_t subject; + lv_subject_init_color(&subject, lv_color_hex(0x123456)); + + TEST_ASSERT_NOT_NULL(lv_obj_bind_color(obj, &subject, capture_color_cb)); + TEST_ASSERT_TRUE(lv_color_eq(lv_color_hex(0x123456), captured_color)); + lv_subject_set_color(&subject, lv_color_hex(0xabcdef)); + TEST_ASSERT_TRUE(lv_color_eq(lv_color_hex(0xabcdef), captured_color)); + + lv_obj_delete(obj); +} + +static const void * captured_pointer; +static void capture_pointer_cb(lv_obj_t * obj, const void * v) +{ + LV_UNUSED(obj); + captured_pointer = v; +} + +void test_observer_obj_bind_pointer(void) +{ + lv_obj_t * obj = lv_obj_create(lv_screen_active()); + static const char * a = "a"; + static const char * b = "b"; + static lv_subject_t subject; + lv_subject_init_pointer(&subject, (void *)a); + + TEST_ASSERT_NOT_NULL(lv_obj_bind_pointer(obj, &subject, capture_pointer_cb)); + TEST_ASSERT_EQUAL_PTR(a, captured_pointer); + lv_subject_set_pointer(&subject, (void *)b); + TEST_ASSERT_EQUAL_PTR(b, captured_pointer); + + lv_obj_delete(obj); +} + +/* Recommended replacement for lv_obj_bind_state_if_*: toggle a state from a + * custom observer added with lv_subject_add_observer_obj. */ +static void set_disabled_observer_cb(lv_observer_t * observer, lv_subject_t * subject) +{ + lv_obj_set_state(lv_observer_get_target_obj(observer), LV_STATE_DISABLED, lv_subject_get_int(subject)); +} + +void test_observer_obj_state_via_observer(void) +{ + lv_obj_t * obj = lv_obj_create(lv_screen_active()); + static lv_subject_t subject; + lv_subject_init_int(&subject, 0); + + lv_observer_t * observer = + lv_subject_add_observer_obj(&subject, set_disabled_observer_cb, obj, NULL); + TEST_ASSERT_NOT_NULL(observer); + + /*Applied immediately*/ + TEST_ASSERT_EQUAL(false, lv_obj_has_state(obj, LV_STATE_DISABLED)); + + lv_subject_set_int(&subject, 1); + TEST_ASSERT_EQUAL(true, lv_obj_has_state(obj, LV_STATE_DISABLED)); + + lv_subject_set_int(&subject, 0); + TEST_ASSERT_EQUAL(false, lv_obj_has_state(obj, LV_STATE_DISABLED)); + + lv_obj_delete(obj); } void test_observer_button_checked(void)