Files
lvgl/docs/details/other-components/observer.rst
2025-02-11 22:06:14 +01:00

575 lines
21 KiB
ReStructuredText

.. _observer:
========
Observer
========
.. _observer_overview:
Overview
********
.. _observer pattern: https://en.wikipedia.org/wiki/Observer_pattern
The ``lv_observer`` module is an implemention of the `Observer Pattern`_.
This implementation consists of:
:Subjects: (in global memory or heap) are "logic packages", each containing the
value being "observed" and its type (integer (``int32_t``), a string, a
pointer, an :cpp:type:`lv_color_t`, or a group);
:Observers: (zero or more per Subject, always dynamically-allocated) are always
attached to exactly one Subject, and provide user-defined notifications
each the time Subject's value changes.
A Subject and its Observers can be used in various ways:
1. Simply subscribe to a Subject and get notified when the Subject's value changes.
2. Subscribe to a group Subject (connects a group of Subjects) to get notified when
any of the Subjects' values change in the group.
3. Bind Widgets to Subjects to automatically match the Widget's value with the
Subject (e.g. a Label's text or an Arc's value).
.. _observer_usage:
Usage
*****
Using Observer first requires :c:macro:`LV_USE_OBSERVER` be configured to ``1``.
(It is ``1`` by default, and can be set to ``0`` to save some program space if you
will not be using Observer.)
A typical use case looks like this:
.. code-block:: c
// Any typical global variable
lv_subject_t my_subject;
/*-------
* main.c
*-------*/
extern lv_subject_t my_subject;
void main(void)
{
// Initialize Subject as integer with the default value of 10.
lv_subject_init_int(&my_subject, 10);
some_module_init();
}
/*--------------
* some_module.c
*--------------*/
extern lv_subject_t some_subject;
// Will be called when Subject's value changes
static void some_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
int32_t v = lv_subject_get_int(subject);
do_something(v);
}
void some_module_init(void)
{
// Subscribe to Subject as an Observer.
lv_subject_add_observer(&some_subject, some_observer_cb, NULL);
}
/*--------------
* some_system.c
*--------------*/
extern lv_subject_t some_subject;
void some_event(void)
{
// The below call sets Subject's value to 30 and notifies current Observers.
lv_subject_set_int(&some_subject, 30);
}
.. _observer_subject:
Subject
-------
Subject Initialization
~~~~~~~~~~~~~~~~~~~~~~
Subjects have to be static or global variables, or dynamically-allocated
:cpp:type:`lv_subject_t` objects. Reason: their content must remain valid through
the life of the Subject.
To initialize a Subject use ``lv_subject_init_<type>(&subject, params, init_value)``.
The following initialization functions exist, one for each of the Subject types:
:Integer: void :cpp:expr:`lv_subject_init_int(subject, int_value)`
:String: void :cpp:expr:`lv_subject_init_string(subject, buf, prev_buf, buf_size, initial_string)`
:Pointer: void :cpp:expr:`lv_subject_init_pointer(subject, ptr)`
:Color: void :cpp:expr:`lv_subject_init_color(subject, color)`
:Group: void :cpp:expr:`lv_subject_init_group(group_subject, subject_list[], count)`
Setting a Subject's Value
~~~~~~~~~~~~~~~~~~~~~~~~~
The following functions are used to update a Subject's value:
:Integer: void :cpp:expr:`lv_subject_set_int(subject, int_value)`
:String: void :cpp:expr:`lv_subject_copy_string(subject, buf)`
:Pointer: void :cpp:expr:`lv_subject_set_pointer(subject, ptr)`
:Color: void :cpp:expr:`lv_subject_set_color(subject, color)`
At the end of each of these calls, if the new value differs from the previous value,
a notification is sent to all current Observers.
Getting a Subject's Value
~~~~~~~~~~~~~~~~~~~~~~~~~
The following functions are used to get a Subject's current value:
:Integer: int32_t :cpp:expr:`lv_subject_get_int(subject)`
:String: const char * :cpp:expr:`lv_subject_get_string(subject)`
:Pointer: const void * :cpp:expr:`lv_subject_get_pointer(subject)`
:Color: lv_color_t :cpp:expr:`lv_subject_get_color(subject)`
Getting a Subject's Previous Value
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following functions are used to get a Subject's previous value:
:Integer: int32_t :cpp:expr:`lv_subject_get_previous_int(subject)`
:String: const char * :cpp:expr:`lv_subject_get_previous_string(subject)`
:Pointer: const void * :cpp:expr:`lv_subject_get_previous_pointer(subject)`
:Color: lv_color_t :cpp:expr:`lv_subject_get_previous_color(subject)`
.. _observer_observer:
Observer
--------
Subscribing to a Subject
~~~~~~~~~~~~~~~~~~~~~~~~
The action of subscribing to a Subject:
- dynamically allocates an Observer object,
- attaches it to the Subject,
- performs an initial notification to the Observer (allowing the Observer to
update itself with the Subject's current value), and
- returns a pointer to the newly-created Observer.
Thereafter the Observer will receive a notification each time the Subject's value
changes, as long as that Observer remains attached (subscribed) to that Subject.
Notifications are performed by calling the callback function provided when
subscribing to the Subject.
To subscribe to a Subject one of the ``lv_subject_add_observer...()`` functions are
used. Alternately, if you want to bind a Subject's value to a Widget's property, one
of the ``lv_<widget_type>_bind_...()`` functions can be used. The former are covered
below. The latter are covered in the :ref:`observer_widget_binding` section.
For the most basic use case, subscribe to a Subject by using the following function:
lv_observer_t * observer = :cpp:expr:`lv_subject_add_observer(&some_subject, some_observer_cb, user_data)`
where the Observer's notification callback should look like this:
.. code-block:: c
static void some_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
...
}
This function returns a pointer to the newly-created Observer.
When using this method of subscribing, it is the responsibility of the user to call
:cpp:expr:`lv_observer_remove(observer)` when the Observer is no longer needed, which
both unsubscribes it from the Subject and deletes it from the LVGL heap.
Subscribing While Associating Observer with a Non-Widget Object
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The function subscribes to a Subject additionally associates the Observer with a
pointer to any type of object, a copy of which is saved in the Observer's ``target``
field. This function should be used when the pointer *does not* point to a Widget.
lv_observer_t * observer = :cpp:expr:`lv_subject_add_observer_with_target(&some_subject, some_observer_cb, some_pointer, user_data)`
A copy of the passed pointer can be retrieved by calling
:cpp:expr:`lv_observer_get_target(observer)`, e.g. inside the callback function.
When using this method of subscribing, it is the responsibility of the user to call
:cpp:expr:`lv_observer_remove(observer)` when the Observer is no longer needed, which
both unsubscribes it from the Subject and deletes it from the LVGL heap.
Subscribing While Associating Observer with a Widget
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The function below assocates a Widget with an Observer while subscribing to a
Subject. A copy of the pointer to that Widget is saved in the Observer's ``target``
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
:ref:`observer_widget_binding`.
lv_observer_t * observer = :cpp:expr:`lv_subject_add_observer_obj(&some_subject, some_observer_cb, widget, user_data)`
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
:cpp:expr:`lv_observer_get_target_obj(observer)`, e.g. inside the callback function.
Note that this function returns the stored pointer as a ``lv_obj_t *`` type, as
opposed to the ``void *`` type returned by
:cpp:expr:`lv_observer_get_target_obj(observer)`.
(:cpp:expr:`lv_observer_get_target(observer)` can still be used if you need that
pointer as a ``void *`` type for any reason, but in practice, this would be rare.)
**Important:**
When using this method of subscribing to a Subject:
- :cpp:expr:`lv_observer_remove(observer)` must *never* be called.
The Observer MUST ONLY BE unsubscribed and deleted by:
- If Widget needs to be deleted, simply delete the Widget, which will automatically
remove the Observer from the Subject.
- If Widget does NOT need to be deleted:
- :cpp:expr:`lv_obj_remove_from_subject(widget, subject)` which will delete all
Observers associated with ``widget``, or
- :cpp:expr:`lv_subject_deinit(subject)`, which gracefully disconnects ``subject``
from all associated Observers and Widget events.
Unsubscribing from a Subject
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To unsubscribe a normal Observer or one associated with a non-Widget object, use
:cpp:expr:`lv_observer_remove(observer)`, where ``observer`` is the return value from
either the :cpp:func:`lv_subject_add_observer()` or
:cpp:func:`lv_subject_add_observer_with_target()` functions.
To unsubscribe an Observer created through :cpp:func:`lv_subject_add_observer_obj()`,
use :cpp:expr:`lv_obj_remove_from_subject(widget, subject)`. ``subject`` can be NULL
to unsubscribe the Widget from all associated Subjects.
To unsubscribe all Observers from a Subject that were subscribed using any method
(including :ref:`observer_widget_binding` covered below), use
:cpp:expr:`lv_subject_deinit(subject)`.
.. _observer_subject_groups:
Subject Groups
--------------
When something in your system relies on more than one value (i.e. it needs to be
notified when any of a SET of two or more values changes), it can be made an
Observer of a Subject Group.
Let us consider an example of an instrument which measures either voltage or current.
To display the measured value on a label, 3 things are required:
1. What is being measured (current or voltage)?
2. What is the measured value?
3. What is the range or unit ("mV", "V", "mA", "A")?
When any of these 3 input values change, the label needs to be updated, and it needs
to know all 3 values to compose its text.
To handle this you can create an array from the addresses of all the Subjects that
are relied upon, and pass that array as a parameter when you initialize a Subject
with GROUP type.
.. code-block:: c
static lv_subject_t * subject_list[3] = {&subject_1, &subject_2, &subject_3};
lv_subject_init_group(&subject_all, subject_list, 3); /* Last argument is number of elements. */
Observers are then added to Subject Groups (e.g. ``subject_all``) in the usual way.
When this is done, a change to the value of any of the Subjects in the group triggers
a notification to all Observers subscribed to the Subject Group (e.g. ``subject_all``).
As an example, the above scenario with Voltage/Current measurement might look like this:
.. code-block:: c
lv_obj_t * label = lv_label_create(lv_screen_active());
lv_subject_t subject_mode; // Voltage or Current
lv_subject_t subject_value; // Measured value
lv_subject_t subject_unit; // The unit
lv_subject_t subject_all; // Subject group that connects the above 3 Subjects
lv_subject_t * subject_list[3] = {&subject_mode, &subject_value, &subject_unit}; // The elements of the group
lv_subject_init_int(&subject_mode, 0); // Let's say 0 is Voltage, 1 is Current
lv_subject_init_int(&subject_value, 0);
lv_subject_init_pointer(&subject_unit, "V");
lv_subject_init_group(&subject_all, subject_list, 3);
lv_subject_add_observer_obj(&subject_all, all_observer_cb, label, NULL);
...
static void all_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
lv_obj_t * label = lv_observer_get_target(observer);
lv_subject_t * subject_mode = lv_subject_get_group_element(subject, 0);
lv_subject_t * subject_value = lv_subject_get_group_element(subject, 1);
lv_subject_t * subject_unit = lv_subject_get_group_element(subject, 2);
int32_t mode = lv_subject_get_int(subject_mode);
int32_t value = lv_subject_get_int(subject_value);
const char * unit = lv_subject_get_pointer(subject_unit);
lv_label_set_text_fmt(label, "%s: %d %s", mode ? "Current" : "Voltage", value, unit);
}
.. _observer_widget_binding:
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:
- 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 value for Label Widgets;
- integer value for these Widget types:
- Arc
- Drop-Down
- Roller
- Slider
Any number of Observers can be created for a single Widget, each bound to ONE of
the above properties.
For all of the ``lv_..._bind_...()`` functions covered below, they are similar to
:cpp:expr:`lv_subject_add_observer_obj(&some_subject, some_observer_cb, widget, user_data)`
in that they create an Observer and associates the Widget with it. What is different
is that updates to the Widget's property thus bound are handled internally -- the
user *does not supply callback functions* for any of these subscribing methods -- the
callback methods are supplied by the Observer subsystem.
.. warning::
In all cases, the Observer is unsubscribed from the Subject and deleted from the
LVGL heap when the Widget is deleted. :cpp:expr:`lv_observer_remove(observer)`
must *never* be called when using the ``lv_..._bind_...()`` functions, but
:cpp:expr:`lv_subject_deinit(subject)` and
:cpp:expr:`lv_obj_remove_from_subject(widget, subject)` may both be used since
they both gracefully de-couple the Observer from the Widget before deleting the
Observer.
.. note::
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.
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.
:equal: :cpp:expr:`lv_obj_bind_flag_if_eq(widget, &subject, flags, ref_value)`
:not equal: :cpp:expr:`lv_obj_bind_flag_if_not_eq(widget, &subject, flags, ref_value)`
:greater than: :cpp:expr:`lv_obj_bind_flag_if_gt(widget, &subject, flags, ref_value)`
:greater than or equal: :cpp:expr:`lv_obj_bind_flag_if_ge(widget, &subject, flags, ref_value)`
:less than: :cpp:expr:`lv_obj_bind_flag_if_lt(widget, &subject, flags, ref_value)`
:less than or equal: :cpp:expr:`lv_obj_bind_flag_if_le(widget, &subject, flags, ref_value)`
States
^^^^^^
The following methods of subscribing to an integer Subject affect a Widget's states
(or OR-ed combination of states). When the subscribing occurs, and each time the
Subject's value is changed thereafter, the Subject's value is compared with the
specified reference value, and the specified state(s) is (are):
- SET when the Subject's integer value fulfills the indicated condition, and
- CLEARED otherwise.
Here are the functions that carry out this method of subscribing to a Subject. The
``states`` argument can contain a single, or bit-wise OR-ed combination of any of the
``LV_STATE_...`` enumeration values.
:equal: :cpp:expr:`lv_obj_bind_state_if_eq(widget, &subject, states, ref_value)`
:not equal: :cpp:expr:`lv_obj_bind_state_if_not_eq(widget, &subject, states, ref_value)`
:greater than: :cpp:expr:`lv_obj_bind_state_if_gt(widget, &subject, states, ref_value)`
:greater than or equal: :cpp:expr:`lv_obj_bind_state_if_ge(widget, &subject, states, ref_value)`
:less than: :cpp:expr:`lv_obj_bind_state_if_lt(widget, &subject, states, ref_value)`
:less than or equal: :cpp:expr:`lv_obj_bind_state_if_le(widget, &subject, states, ref_value)`
Checked State
^^^^^^^^^^^^^
The following method of subscribing to an integer Subject affects a Widget's
:cpp:enumerator:`LV_STATE_CHECKED` state. When the subscribing occurs, and each time
the Subject's value is changed thereafter, the Subject's value is compared to a
reference value of ``0``, and the :cpp:enumerator:`LV_STATE_CHECKED` state is:
- CLEARED when the Subject's value is 0, and
- SET when the Subject's integer value is non-zero.
Note that this is a two-way binding (Subject <===> Widget) so direct (or
programmatic) interaction with the Widget that causes its
:cpp:enumerator:`LV_STATE_CHECKED` state to be SET or CLEARED also causes the
Subject's value to be set to ``1`` or ``0`` respectively.
- :cpp:expr:`lv_obj_bind_checked(widget, &subject)`
Label Widgets
~~~~~~~~~~~~~
.. |deg| unicode:: U+000B0 .. DEGREE SIGN
This method of subscribing to an integer Subject affects a Label Widget's
``text``. The Subject can be an STRING, POINTER or INTEGER type.
When the subscribing occurs, and each time the Subject's value is changed thereafter,
the Subject's value is used to update the Label's text as follows:
:string Subject: Subject's string is used to directly update the Label's text.
:pointer Subject: If NULL is passed as the ``format_string`` argument when
subscribing, the Subject's pointer value is assumed to point to a
NUL-terminated string. and is used to directly update the Label's
text. See :ref:`observer_format_string` for other options.
:integer Subject: Subject's integer value is used with the ``format_string`` argument.
See See :ref:`observer_format_string` for details.
Note that this is a one-way binding (Subject ===> Widget).
- :cpp:expr:`lv_label_bind_text(label, &subject, format_string)`
.. _observer_format_string:
The ``format_string`` Argument
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``format_string`` argument is optional and if provided, must contain exactly 1
printf-like format specifier and be one of the following:
:string or pointer Subject: "%s" to format the new pointer value as a string or "%p"
to format the pointer as a pointer (typically the
pointer's address value is spelled out with 4, 8 or 16
hexadecimal characters depending on the platform).
:integer Subject: "%d" format specifier (``"%" PRIdxx`` --- a
cross-platform equivalent where ``xx`` can be ``8``,
``16``, ``32`` or ``64``, depending on the platform).
If NULL is passed for the ``format_string`` argument:
:string or pointer Subject: Updates expect the pointer to point to a NUL-terminated string.
:integer Subject: The Label will display an empty string (i.e. nothing).
**Example:** "%d |deg|\C"
Arc Widgets
~~~~~~~~~~~
This method of subscribing to an integer Subject affects an Arc Widget's integer
value directly. Note that this is a two-way binding (Subject <===> Widget) so an end
user's direct interaction with the Arc Widget updates the Subject's value and vice
versa. (Requires :c:macro:`LV_USE_ARC` to be configured to ``1``.)
- :cpp:expr:`lv_arc_bind_value(arc, &subject)`
Slider Widgets
~~~~~~~~~~~~~~
This method of subscribing to an integer Subject affects a Slider Widget's integer
value directly. Note that this is a two-way binding (Subject <===> Widget) so an end
user's direct interaction with the Slider Widget updates the Subject's value and vice
versa. (Requires :c:macro:`LV_USE_SLIDER` to be configured to ``1``.)
- :cpp:expr:`lv_slider_bind_value(slider, &subject)`
Roller Widgets
~~~~~~~~~~~~~~
This method of subscribing to an integer Subject affects a Roller Widget's integer
value directly. Note that this is a two-way binding (Subject <===> Widget) so an end
user's direct interaction with the Slider Widget updates the Subject's value and vice
versa. (Requires :c:macro:`LV_USE_ROLLER` to be configured to ``1``.)
- :cpp:expr:`lv_roller_bind_value(roller, &subject)`
Drop-Down Widgets
~~~~~~~~~~~~~~~~~
This method of subscribing to an integer Subject affects a Drop-Down Widget's integer
value directly. Note that this is a two-way binding (Subject <===> Widget) so an end
user's direct interaction with the Drop-Down Widget updates the Subject's value and
vice versa. (Requires :c:macro:`LV_USE_DROPDOWN` to be configured to ``1``.)
- :cpp:expr:`lv_dropdown_bind_value(dropdown, &subject)`
.. _observer_example:
Examples
********
.. include:: ../../examples/others/observer/index.rst
.. _observer_api:
API
***