Observer
Overview
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, anlv_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:
Simply subscribe to a Subject and get notified when the Subject's value changes.
Subscribe to a group Subject (connects a group of Subjects) to get notified when any of the Subjects' values change in the group.
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).
Usage
Using Observer first requires 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:
// 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);
}
Subject
Subject Initialization
Subjects have to be static or global variables, or dynamically-allocated
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 lv_subject_init_int(subject, int_value)
- String:
void lv_subject_init_string(subject, buf, prev_buf, buf_size, initial_string)
- Pointer:
void lv_subject_init_pointer(subject, ptr)
- Color:
void lv_subject_init_color(subject, color)
- Group:
void 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 lv_subject_set_int(subject, int_value)
- String:
void lv_subject_copy_string(subject, buf)
- Pointer:
void lv_subject_set_pointer(subject, ptr)
- Color:
void 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 lv_subject_get_int(subject)
- String:
const char * lv_subject_get_string(subject)
- Pointer:
const void * lv_subject_get_pointer(subject)
- Color:
lv_color_t 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 lv_subject_get_previous_int(subject)
- String:
const char * lv_subject_get_previous_string(subject)
- Pointer:
const void * lv_subject_get_previous_pointer(subject)
- Color:
lv_color_t lv_subject_get_previous_color(subject)
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 Widget Binding section.
For the most basic use case, subscribe to a Subject by using the following function:
lv_observer_t * observer = lv_subject_add_observer(&some_subject, some_observer_cb, user_data)
where the Observer's notification callback should look like this:
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 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 = 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 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 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
Widget Binding.
lv_observer_t * observer = 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
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
lv_observer_get_target_obj(observer).
(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:
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:
lv_obj_remove_from_subject(widget, subject) which will delete all Observers associated with
widget
, orlv_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
lv_observer_remove(observer), where observer
is the return value from
either the lv_subject_add_observer()
or
lv_subject_add_observer_with_target()
functions.
To unsubscribe an Observer created through lv_subject_add_observer_obj()
,
use 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 Widget Binding covered below), use lv_subject_deinit(subject).
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:
What is being measured (current or voltage)?
What is the measured value?
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.
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:
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);
}
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
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. lv_observer_remove(observer)
must never be called when using the lv_..._bind_...()
functions, but
lv_subject_deinit(subject) and
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:
lv_obj_bind_flag_if_eq(widget, &subject, flags, ref_value)
- not equal:
lv_obj_bind_flag_if_not_eq(widget, &subject, flags, ref_value)
- greater than:
lv_obj_bind_flag_if_gt(widget, &subject, flags, ref_value)
- greater than or equal:
lv_obj_bind_flag_if_ge(widget, &subject, flags, ref_value)
- less than:
lv_obj_bind_flag_if_lt(widget, &subject, flags, ref_value)
- less than or equal:
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:
lv_obj_bind_state_if_eq(widget, &subject, states, ref_value)
- not equal:
lv_obj_bind_state_if_not_eq(widget, &subject, states, ref_value)
- greater than:
lv_obj_bind_state_if_gt(widget, &subject, states, ref_value)
- greater than or equal:
lv_obj_bind_state_if_ge(widget, &subject, states, ref_value)
- less than:
lv_obj_bind_state_if_lt(widget, &subject, states, ref_value)
- less than or equal:
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
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 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
LV_STATE_CHECKED
state to be SET or CLEARED also causes the
Subject's value to be set to 1
or 0
respectively.
lv_obj_bind_checked(widget, &subject)
Label Widgets
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 The format_string Argument for other options.- integer Subject:
Subject's integer value is used with the
format_string
argument. See See The format_string Argument for details.
Note that this is a one-way binding (Subject ===> Widget).
lv_label_bind_text(label, &subject, 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 wherexx
can be8
,16
,32
or64
, 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 °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 LV_USE_ARC
to be configured to 1
.)
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 LV_USE_SLIDER
to be configured to 1
.)
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 LV_USE_ROLLER
to be configured to 1
.)
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 LV_USE_DROPDOWN
to be configured to 1
.)
lv_dropdown_bind_value(dropdown, &subject)
Examples
Bind a slider's value to a label
C code
View on GitHub#include "../../lv_examples.h"
#if LV_USE_OBSERVER && LV_USE_SLIDER && LV_USE_LABEL && LV_BUILD_EXAMPLES
static lv_subject_t temperature_subject;
/**
* A slider sends a message on value change and a label display's that value
*/
void lv_example_observer_1(void)
{
lv_subject_init_int(&temperature_subject, 28);
/*Create a slider in the center of the display*/
lv_obj_t * slider = lv_slider_create(lv_screen_active());
lv_obj_center(slider);
lv_slider_bind_value(slider, &temperature_subject);
/*Create a label below the slider*/
lv_obj_t * label = lv_label_create(lv_screen_active());
lv_obj_align(label, LV_ALIGN_CENTER, 0, 30);
lv_label_bind_text(label, &temperature_subject, "%d °C");
}
#endif
Handling login and its states
C code
View on GitHub#include "../../lv_examples.h"
#if LV_USE_OBSERVER && LV_USE_SLIDER && LV_USE_LABEL && LV_BUILD_EXAMPLES
/*This the only interface between the UI and the application*/
static lv_subject_t engine_subject;
static void app_init(void);
static void ui_init(void);
/**
* Simple PIN login screen to start an engine.
* The only interface between the UI and the application is a single "subject".
*/
void lv_example_observer_2(void)
{
lv_subject_init_int(&engine_subject, 0);
app_init();
ui_init();
}
/*--------------------------------------------------
* APPLICATION
*
* This part contains a demo application logic.
* It doesn't know anything about the internals of the UI
* and uses any the `engine_subject` as an interface.
* -------------------------------------------------*/
static void engine_state_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
LV_UNUSED(observer);
int32_t v = lv_subject_get_int(subject);
LV_UNUSED(v);
/*In a real application set/clear a pin here*/
LV_LOG_USER("Engine state: %" LV_PRId32, v);
}
static void app_init(void)
{
lv_subject_add_observer(&engine_subject, engine_state_observer_cb, NULL);
}
/*--------------------------------------------------
* USER INTERFACE
*
* This part contains only UI related code and data.
* In a project it would a separate file and the
* application couldn't see its internals
* -------------------------------------------------*/
typedef enum {
LOGGED_OUT,
LOGGED_IN,
AUTH_FAILED,
} auth_state_t;
static lv_subject_t auth_state_subject;
static void textarea_event_cb(lv_event_t * e)
{
lv_obj_t * ta = lv_event_get_target(e);
if(lv_strcmp(lv_textarea_get_text(ta), "hello") == 0) {
lv_subject_set_int(&auth_state_subject, LOGGED_IN);
}
else {
lv_subject_set_int(&auth_state_subject, AUTH_FAILED);
}
}
static void info_label_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
lv_obj_t * label = lv_observer_get_target(observer);
switch(lv_subject_get_int(subject)) {
case LOGGED_IN:
lv_label_set_text(label, "Login successful");
break;
case LOGGED_OUT:
lv_label_set_text(label, "Logged out");
break;
case AUTH_FAILED:
lv_label_set_text(label, "Login failed");
break;
}
}
static void log_out_click_event_cb(lv_event_t * e)
{
LV_UNUSED(e);
lv_subject_set_int(&auth_state_subject, LOGGED_OUT);
}
static void ui_init(void)
{
lv_subject_init_int(&auth_state_subject, LOGGED_OUT);
/*Create a slider in the center of the display*/
lv_obj_t * ta = lv_textarea_create(lv_screen_active());
lv_obj_set_pos(ta, 10, 10);
lv_obj_set_width(ta, 200);
lv_textarea_set_one_line(ta, true);
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_obj_t * kb = lv_keyboard_create(lv_screen_active());
lv_keyboard_set_textarea(kb, ta);
lv_obj_t * btn;
lv_obj_t * label;
/*Create a log out button which will be active only when logged in*/
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);
label = lv_label_create(btn);
lv_label_set_text(label, "LOG OUT");
/*Create a label to show info*/
label = lv_label_create(lv_screen_active());
lv_obj_set_pos(label, 10, 60);
lv_subject_add_observer_obj(&auth_state_subject, info_label_observer_cb, label, NULL);
/*Create button which will be active only when logged in*/
btn = lv_button_create(lv_screen_active());
lv_obj_set_pos(btn, 10, 80);
lv_obj_add_flag(btn, LV_OBJ_FLAG_CHECKABLE);
lv_obj_bind_state_if_not_eq(btn, &auth_state_subject, LV_STATE_DISABLED, LOGGED_IN);
lv_button_bind_checked(btn, &engine_subject);
label = lv_label_create(btn);
lv_label_set_text(label, "START ENGINE");
}
#endif
Set time with 12/24 mode and AM/PM
C code
View on GitHub#include "../../lv_examples.h"
#if LV_USE_OBSERVER && LV_USE_SLIDER && LV_USE_LABEL && LV_USE_ROLLER && LV_USE_DROPDOWN && LV_FONT_MONTSERRAT_30 && LV_BUILD_EXAMPLES
static lv_subject_t hour_subject;
static lv_subject_t minute_subject;
static lv_subject_t format_subject;
static lv_subject_t am_pm_subject;
static lv_subject_t time_subject;
static lv_subject_t * time_group_array_subject[] = {&hour_subject, &minute_subject, &format_subject, &am_pm_subject};
const char * hour12_options = "01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12";
const char * hour24_options =
"00\n01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23";
const char * minute_options =
"00\n01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59";
static void set_btn_clicked_event_cb(lv_event_t * e);
static void close_clicked_event_cb(lv_event_t * e);
static void hour_roller_options_update(lv_observer_t * observer, lv_subject_t * subject);
static void time_observer_cb(lv_observer_t * observer, lv_subject_t * subject);
typedef enum {
TIME_FORMAT_12,
TIME_FORMAT_24,
} time_format_t;
typedef enum {
TIME_AM,
TIME_PM,
} time_am_pm_t;
/**
* Show how to handle a complex time setting with hour, minute, 12/24 hour mode, and AM/PM switch
* In a real application the time can be displayed on multiple screens and it's not trivial
* how and where to store the current values and how to get them.
* In this example the widgets to set the time are create/deleted dynamically,
* yet they always know what the current values are by using subjects.
*/
void lv_example_observer_3(void)
{
/*Initialize the subjects.
*The UI will update these and read the current values from here,
*however the application can update these values at any time and
*the widgets will be updated automatically. */
lv_subject_init_int(&hour_subject, 7);
lv_subject_init_int(&minute_subject, 45);
lv_subject_init_int(&format_subject, TIME_FORMAT_12);
lv_subject_init_int(&am_pm_subject, TIME_AM);
lv_subject_init_group(&time_subject, time_group_array_subject, 4);
/*Create the UI*/
lv_obj_t * time_label = lv_label_create(lv_screen_active());
lv_obj_set_style_text_font(time_label, &lv_font_montserrat_30, 0);
lv_subject_add_observer_obj(&time_subject, time_observer_cb, time_label, NULL);
lv_obj_set_pos(time_label, 24, 24);
lv_obj_t * set_btn = lv_button_create(lv_screen_active());
lv_obj_set_pos(set_btn, 180, 24);
lv_obj_add_event_cb(set_btn, set_btn_clicked_event_cb, LV_EVENT_CLICKED, NULL);
lv_obj_t * set_label = lv_label_create(set_btn);
lv_label_set_text(set_label, "Set");
/*Update some subjects to see if the UI is updated as well*/
lv_subject_set_int(&hour_subject, 9);
lv_subject_set_int(&minute_subject, 30);
lv_subject_set_int(&am_pm_subject, TIME_PM);
}
static void set_btn_clicked_event_cb(lv_event_t * e)
{
lv_obj_t * set_btn = lv_event_get_target(e);
lv_obj_add_state(set_btn, LV_STATE_DISABLED);
lv_obj_t * cont = lv_obj_create(lv_screen_active());
lv_obj_set_size(cont, lv_pct(100), LV_SIZE_CONTENT);
lv_obj_align(cont, LV_ALIGN_BOTTOM_MID, 0, 0);
lv_obj_t * hour_roller = lv_roller_create(cont);
lv_obj_add_flag(hour_roller, LV_OBJ_FLAG_FLEX_IN_NEW_TRACK);
lv_subject_add_observer_obj(&format_subject, hour_roller_options_update, hour_roller, NULL);
lv_roller_bind_value(hour_roller, &hour_subject);
lv_obj_set_pos(hour_roller, 0, 0);
lv_obj_t * min_roller = lv_roller_create(cont);
lv_roller_set_options(min_roller, minute_options, LV_ROLLER_MODE_NORMAL);
lv_roller_bind_value(min_roller, &minute_subject);
lv_obj_set_pos(min_roller, 64, 0);
lv_obj_t * format_dropdown = lv_dropdown_create(cont);
lv_dropdown_set_options(format_dropdown, "12\n24");
lv_dropdown_bind_value(format_dropdown, &format_subject);
lv_obj_set_pos(format_dropdown, 128, 0);
lv_obj_set_width(format_dropdown, 80);
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_obj_set_pos(am_pm_dropdown, 128, 48);
lv_obj_set_width(am_pm_dropdown, 80);
lv_obj_t * close_btn = lv_button_create(cont);
lv_obj_align(close_btn, LV_ALIGN_TOP_RIGHT, 0, 0);
/*Pass the set_btn as user_data to make it non-disabled on close*/
lv_obj_add_event_cb(close_btn, close_clicked_event_cb, LV_EVENT_CLICKED, set_btn);
lv_obj_t * close_label = lv_label_create(close_btn);
lv_label_set_text(close_label, LV_SYMBOL_CLOSE);
}
static void close_clicked_event_cb(lv_event_t * e)
{
lv_obj_t * set_btn = lv_event_get_user_data(e);
lv_obj_t * close_btn = lv_event_get_target(e);
lv_obj_t * cont = lv_obj_get_parent(close_btn);
lv_obj_remove_state(set_btn, LV_STATE_DISABLED);
lv_obj_delete(cont);
}
/*Watch all related subject to display the current time correctly*/
static void time_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
int32_t hour = lv_subject_get_int(lv_subject_get_group_element(subject, 0));
int32_t minute = lv_subject_get_int(lv_subject_get_group_element(subject, 1));
int32_t format = lv_subject_get_int(lv_subject_get_group_element(subject, 2));
int32_t am_pm = lv_subject_get_int(lv_subject_get_group_element(subject, 3));
lv_obj_t * label = lv_observer_get_target(observer);
if(format == TIME_FORMAT_24) {
lv_label_set_text_fmt(label, "%" LV_PRId32 ":%02" LV_PRId32, hour, minute);
}
else {
lv_label_set_text_fmt(label, "%"LV_PRId32":%02"LV_PRId32" %s", hour + 1, minute, am_pm == TIME_AM ? "am" : "pm");
}
}
/*Change the hour options on format change*/
static void hour_roller_options_update(lv_observer_t * observer, lv_subject_t * subject)
{
lv_obj_t * roller = lv_observer_get_target(observer);
int32_t prev_selected = lv_roller_get_selected(roller);
int32_t v = lv_subject_get_int(subject);
if(v == TIME_FORMAT_12) {
prev_selected--;
if(prev_selected > 12) prev_selected -= 12;
lv_roller_set_options(roller, hour12_options, LV_ROLLER_MODE_NORMAL);
}
else {
prev_selected++;
lv_roller_set_options(roller, hour24_options, LV_ROLLER_MODE_NORMAL);
}
lv_roller_set_selected(roller, prev_selected, LV_ANIM_OFF);
lv_obj_send_event(roller, LV_EVENT_VALUE_CHANGED, NULL);
}
#endif
Custom tab view with state management
C code
View on GitHub#include "../../lv_examples.h"
#if LV_USE_OBSERVER && LV_USE_SLIDER && LV_USE_LABEL && LV_USE_ROLLER && LV_USE_DROPDOWN && LV_FONT_MONTSERRAT_30 && LV_BUILD_EXAMPLES
static void cont_observer_cb(lv_observer_t * observer, lv_subject_t * subject);
static void btn_create(lv_obj_t * parent, const char * text);
static void btn_click_event_cb(lv_event_t * e);
static void btn_observer_cb(lv_observer_t * observer, lv_subject_t * subject);
static void indicator_observer_cb(lv_observer_t * observer, lv_subject_t * subject);
static lv_subject_t current_tab_subject;
static lv_subject_t slider_subject[4];
static lv_subject_t dropdown_subject[3];
static lv_subject_t roller_subject[2];
void lv_example_observer_4(void)
{
lv_subject_init_int(¤t_tab_subject, 0);
lv_subject_init_int(&slider_subject[0], 0);
lv_subject_init_int(&slider_subject[1], 0);
lv_subject_init_int(&slider_subject[2], 0);
lv_subject_init_int(&slider_subject[3], 0);
lv_subject_init_int(&dropdown_subject[0], 0);
lv_subject_init_int(&dropdown_subject[1], 0);
lv_subject_init_int(&dropdown_subject[2], 0);
lv_subject_init_int(&roller_subject[0], 0);
lv_subject_init_int(&roller_subject[1], 0);
lv_obj_t * main_cont = lv_obj_create(lv_screen_active());
lv_obj_remove_style_all(main_cont);
lv_obj_set_size(main_cont, lv_pct(100), lv_pct(100));
lv_obj_set_style_pad_all(main_cont, 0, 0);
lv_obj_set_flex_flow(main_cont, LV_FLEX_FLOW_COLUMN);
lv_obj_t * cont = lv_obj_create(main_cont);
lv_obj_remove_style_all(cont);
lv_obj_set_flex_grow(cont, 1);
lv_obj_set_style_pad_all(cont, 8, 0);
lv_obj_set_width(cont, lv_pct(100));
lv_subject_add_observer_obj(¤t_tab_subject, cont_observer_cb, cont, NULL);
lv_obj_set_scroll_dir(cont, LV_DIR_VER);
lv_obj_t * footer = lv_obj_create(main_cont);
lv_obj_remove_style_all(footer);
lv_obj_set_style_pad_column(footer, 8, 0);
lv_obj_set_style_pad_all(footer, 8, 0);
lv_obj_set_flex_flow(footer, LV_FLEX_FLOW_ROW);
lv_obj_set_size(footer, lv_pct(100), 60);
lv_obj_align(footer, LV_ALIGN_BOTTOM_MID, 0, 0);
btn_create(footer, "First");
btn_create(footer, "Second");
btn_create(footer, "Third");
lv_obj_t * indicator = lv_obj_create(footer);
lv_obj_remove_style(indicator, NULL, 0);
lv_obj_set_style_bg_opa(indicator, LV_OPA_40, 0);
lv_subject_add_observer_obj(¤t_tab_subject, indicator_observer_cb, indicator, NULL);
lv_obj_set_height(indicator, 10);
lv_obj_align(indicator, LV_ALIGN_BOTTOM_LEFT, 0, 0);
lv_obj_add_flag(indicator, LV_OBJ_FLAG_IGNORE_LAYOUT);
/*Be sure the indicator has the correct size*/
lv_obj_update_layout(indicator);
lv_subject_notify(¤t_tab_subject);
}
static int32_t anim_get_x_cb(lv_anim_t * a)
{
return lv_obj_get_x_aligned(a->var);
}
static void anim_set_x_cb(void * obj, int32_t v)
{
lv_obj_set_x(obj, v);
}
static void cont_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
int32_t prev_v = lv_subject_get_previous_int(subject);
int32_t cur_v = lv_subject_get_int(subject);
lv_obj_t * cont = lv_observer_get_target(observer);
/*Animate out the previous content*/
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_duration(&a, 300);
lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out);
lv_anim_set_exec_cb(&a, anim_set_x_cb);
lv_anim_set_get_value_cb(&a, anim_get_x_cb);
lv_anim_set_completed_cb(&a, lv_obj_delete_anim_completed_cb);
uint32_t i;
uint32_t delay = 0;
uint32_t child_cnt_prev = lv_obj_get_child_count(cont);
for(i = 0; i < child_cnt_prev; i++) {
lv_obj_t * child = lv_obj_get_child(cont, i);
lv_anim_set_var(&a, child);
if(prev_v < cur_v) {
lv_anim_set_values(&a, 0, -20);
}
else {
lv_anim_set_values(&a, 0, 20);
}
lv_anim_set_delay(&a, delay);
lv_anim_start(&a);
lv_obj_fade_out(child, 200, delay);
delay += 50;
}
/*Create the widgets according to the current value*/
if(cur_v == 0) {
for(i = 0; i < 4; i++) {
lv_obj_t * slider = lv_slider_create(cont);
lv_slider_bind_value(slider, &slider_subject[i]);
lv_obj_align(slider, LV_ALIGN_TOP_MID, 0, 10 + i * 30);
}
}
if(cur_v == 1) {
for(i = 0; i < 3; i++) {
lv_obj_t * dropdown = lv_dropdown_create(cont);
lv_dropdown_bind_value(dropdown, &dropdown_subject[i]);
lv_obj_align(dropdown, LV_ALIGN_TOP_MID, 0, i * 50);
}
}
if(cur_v == 2) {
for(i = 0; i < 2; i++) {
lv_obj_t * roller = lv_roller_create(cont);
lv_roller_bind_value(roller, &roller_subject[i]);
lv_obj_align(roller, LV_ALIGN_CENTER, - 80 + i * 160, 0);
}
}
/*Animate in the new widgets*/
lv_anim_set_completed_cb(&a, NULL);
for(i = child_cnt_prev; i < lv_obj_get_child_count(cont); i++) {
lv_obj_t * child = lv_obj_get_child(cont, i);
lv_anim_set_var(&a, child);
if(prev_v < cur_v) {
lv_anim_set_values(&a, 20, 0);
}
else {
lv_anim_set_values(&a, -20, -0);
}
lv_anim_set_delay(&a, delay);
lv_anim_start(&a);
lv_obj_fade_in(child, 200, delay);
delay += 50;
}
}
static void btn_create(lv_obj_t * parent, const char * text)
{
lv_obj_t * btn = lv_button_create(parent);
lv_obj_set_flex_grow(btn, 1);
lv_obj_set_height(btn, lv_pct(100));
lv_obj_set_style_radius(btn, 0, 0);
lv_subject_add_observer_obj(¤t_tab_subject, btn_observer_cb, btn, NULL);
lv_obj_add_event_cb(btn, btn_click_event_cb, LV_EVENT_CLICKED, NULL);
lv_obj_t * label = lv_label_create(btn);
lv_label_set_text(label, text);
lv_obj_center(label);
}
static void btn_click_event_cb(lv_event_t * e)
{
lv_obj_t * btn = lv_event_get_target(e);
uint32_t idx = lv_obj_get_index(btn);
lv_subject_set_int(¤t_tab_subject, idx);
}
static void btn_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
int32_t prev_v = lv_subject_get_previous_int(subject);
int32_t cur_v = lv_subject_get_int(subject);
lv_obj_t * btn = lv_observer_get_target(observer);
int32_t idx = (int32_t)lv_obj_get_index(btn);
if(idx == prev_v) lv_obj_remove_state(btn, LV_STATE_CHECKED);
if(idx == cur_v) lv_obj_add_state(btn, LV_STATE_CHECKED);
}
static void indicator_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
int32_t cur_v = lv_subject_get_int(subject);
lv_obj_t * indicator = lv_observer_get_target(observer);
lv_obj_t * footer = lv_obj_get_parent(indicator);
lv_obj_t * btn_act = lv_obj_get_child(footer, cur_v);
lv_obj_set_width(indicator, lv_obj_get_width(btn_act));
lv_anim_t a;
lv_anim_init(&a);
lv_anim_set_exec_cb(&a, anim_set_x_cb);
lv_anim_set_duration(&a, 300);
lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out);
lv_anim_set_var(&a, indicator);
lv_anim_set_values(&a, lv_obj_get_x(indicator), lv_obj_get_x(btn_act));
lv_anim_start(&a);
}
#endif
Firmware update process
C code
View on GitHub#include "../../lv_examples.h"
#if LV_USE_OBSERVER && LV_USE_ARC && LV_USE_LABEL && LV_USE_BUTTON && LV_USE_SPINNER && LV_BUILD_EXAMPLES
typedef enum {
FW_UPDATE_STATE_IDLE,
FW_UPDATE_STATE_CONNECTING,
FW_UPDATE_STATE_CONNECTED,
FW_UPDATE_STATE_DOWNLOADING,
FW_UPDATE_STATE_CANCEL,
FW_UPDATE_STATE_READY,
} fw_update_state_t;
static void fw_upload_manager_observer_cb(lv_observer_t * observer, lv_subject_t * subject);
static void fw_update_btn_clicked_event_cb(lv_event_t * e);
static void fw_update_close_event_cb(lv_event_t * e);
static void fw_update_win_observer_cb(lv_observer_t * observer, lv_subject_t * subject);
static lv_subject_t fw_download_percent_subject;
static lv_subject_t fw_update_status_subject;
/**
* Show how to handle a complete firmware update process with observers.
* Normally it's hard to implement a firmware update process because in some cases
* - the App needs to was for the UI (wait for a button press)
* - the UI needs to wait for the App (connecting or downloading)
* With observers these complex mechanisms can be implemented a simple and clean way.
*/
void lv_example_observer_5(void)
{
lv_subject_init_int(&fw_download_percent_subject, 0);
lv_subject_init_int(&fw_update_status_subject, FW_UPDATE_STATE_IDLE);
lv_subject_add_observer(&fw_update_status_subject, fw_upload_manager_observer_cb, NULL);
/*Create start FW update button*/
lv_obj_t * btn = lv_button_create(lv_screen_active());
lv_obj_add_event_cb(btn, fw_update_btn_clicked_event_cb, LV_EVENT_CLICKED, NULL);
lv_obj_center(btn);
lv_obj_t * label = lv_label_create(btn);
lv_label_set_text(label, "Firmware update");
}
static void fw_update_btn_clicked_event_cb(lv_event_t * e)
{
LV_UNUSED(e);
lv_obj_t * win = lv_win_create(lv_screen_active());
lv_obj_set_size(win, lv_pct(90), lv_pct(90));
lv_obj_set_height(lv_win_get_header(win), 40);
lv_obj_set_style_radius(win, 8, 0);
lv_obj_set_style_shadow_width(win, 24, 0);
lv_obj_set_style_shadow_offset_x(win, 2, 0);
lv_obj_set_style_shadow_offset_y(win, 3, 0);
lv_obj_set_style_shadow_color(win, lv_color_hex3(0x888), 0);
lv_win_add_title(win, "Firmware update");
lv_obj_t * btn = lv_win_add_button(win, LV_SYMBOL_CLOSE, 40);
lv_obj_add_event_cb(btn, fw_update_close_event_cb, LV_EVENT_CLICKED, NULL);
lv_obj_center(win);
lv_subject_set_int(&fw_update_status_subject, FW_UPDATE_STATE_IDLE);
lv_subject_add_observer_obj(&fw_update_status_subject, fw_update_win_observer_cb, win, NULL);
}
static void fw_update_close_event_cb(lv_event_t * e)
{
LV_UNUSED(e);
lv_subject_set_int(&fw_update_status_subject, FW_UPDATE_STATE_CANCEL);
}
static void restart_btn_click_event_cb(lv_event_t * e)
{
lv_obj_t * win = lv_event_get_user_data(e);
lv_obj_delete(win);
lv_subject_set_int(&fw_update_status_subject, FW_UPDATE_STATE_IDLE);
}
static void fw_update_win_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
lv_obj_t * win = lv_observer_get_target(observer);
lv_obj_t * cont = lv_win_get_content(win);
fw_update_state_t status = lv_subject_get_int(&fw_update_status_subject);
if(status == FW_UPDATE_STATE_IDLE) {
lv_obj_clean(cont);
lv_obj_t * spinner = lv_spinner_create(cont);
lv_obj_center(spinner);
lv_obj_set_size(spinner, 130, 130);
lv_obj_t * label = lv_label_create(cont);
lv_label_set_text(label, "Connecting");
lv_obj_center(label);
lv_subject_set_int(subject, FW_UPDATE_STATE_CONNECTING);
}
else if(status == FW_UPDATE_STATE_DOWNLOADING) {
lv_obj_clean(cont);
lv_obj_t * arc = lv_arc_create(cont);
lv_arc_bind_value(arc, &fw_download_percent_subject);
lv_obj_center(arc);
lv_obj_set_size(arc, 130, 130);
lv_obj_remove_flag(arc, LV_OBJ_FLAG_CLICKABLE);
lv_obj_t * label = lv_label_create(cont);
lv_label_bind_text(label, &fw_download_percent_subject, "%d %%");
lv_obj_center(label);
}
else if(status == FW_UPDATE_STATE_READY) {
lv_obj_clean(cont);
lv_obj_t * label = lv_label_create(cont);
lv_label_set_text(label, "Firmware update is ready");
lv_obj_align(label, LV_ALIGN_CENTER, 0, -20);
lv_obj_t * btn = lv_button_create(cont);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 20);
lv_obj_add_event_cb(btn, restart_btn_click_event_cb, LV_EVENT_CLICKED, win);
label = lv_label_create(btn);
lv_label_set_text(label, "Restart");
}
else if(status == FW_UPDATE_STATE_CANCEL) {
lv_obj_delete(win);
}
}
static void connect_timer_cb(lv_timer_t * t)
{
if(lv_subject_get_int(&fw_update_status_subject) != FW_UPDATE_STATE_CANCEL) {
lv_subject_set_int(&fw_update_status_subject, FW_UPDATE_STATE_CONNECTED);
}
lv_timer_delete(t);
}
static void download_timer_cb(lv_timer_t * t)
{
if(lv_subject_get_int(&fw_update_status_subject) == FW_UPDATE_STATE_CANCEL) {
lv_timer_delete(t);
return;
}
int32_t v = lv_subject_get_int(&fw_download_percent_subject);
if(v < 100) {
lv_subject_set_int(&fw_download_percent_subject, v + 1);
}
else {
lv_subject_set_int(&fw_update_status_subject, FW_UPDATE_STATE_READY);
lv_timer_delete(t);
}
}
/**
* Emulate connection and FW downloading by timers
*/
static void fw_upload_manager_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
LV_UNUSED(subject);
LV_UNUSED(observer);
fw_update_state_t state = lv_subject_get_int(&fw_update_status_subject);
if(state == FW_UPDATE_STATE_CONNECTING) {
lv_timer_create(connect_timer_cb, 2000, NULL);
}
else if(state == FW_UPDATE_STATE_CONNECTED) {
lv_subject_set_int(&fw_download_percent_subject, 0);
lv_subject_set_int(&fw_update_status_subject, FW_UPDATE_STATE_DOWNLOADING);
lv_timer_create(download_timer_cb, 50, NULL);
}
}
#endif
Modular style update on theme change
C code
View on GitHub#include "../../lv_examples.h"
#if LV_USE_OBSERVER && LV_USE_SLIDER && LV_USE_LABEL && LV_BUILD_EXAMPLES
typedef enum {
THEME_MODE_LIGHT,
THEME_MODE_DARK,
} theme_mode_t;
static lv_obj_t * my_panel_create(lv_obj_t * parent);
static lv_obj_t * my_button_create(lv_obj_t * parent, const char * text, lv_event_cb_t event_cb);
static void switch_theme_event_cb(lv_event_t * e);
static lv_subject_t theme_subject;
/**
* Change between light and dark mode
*/
void lv_example_observer_6(void)
{
lv_subject_init_int(&theme_subject, THEME_MODE_DARK);
lv_obj_t * panel1 = my_panel_create(lv_screen_active());
lv_obj_set_flex_flow(panel1, LV_FLEX_FLOW_COLUMN);
lv_obj_set_flex_align(panel1, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
lv_obj_set_size(panel1, lv_pct(90), lv_pct(90));
lv_obj_center(panel1);
my_button_create(panel1, "Button 1", switch_theme_event_cb);
my_button_create(panel1, "Button 2", switch_theme_event_cb);
my_button_create(panel1, "Button 3", switch_theme_event_cb);
my_button_create(panel1, "Button 4", switch_theme_event_cb);
my_button_create(panel1, "Button 5", switch_theme_event_cb);
my_button_create(panel1, "Button 6", switch_theme_event_cb);
my_button_create(panel1, "Button 7", switch_theme_event_cb);
my_button_create(panel1, "Button 8", switch_theme_event_cb);
my_button_create(panel1, "Button 9", switch_theme_event_cb);
my_button_create(panel1, "Button 10", switch_theme_event_cb);
}
static void switch_theme_event_cb(lv_event_t * e)
{
LV_UNUSED(e);
if(lv_subject_get_int(&theme_subject) == THEME_MODE_LIGHT) lv_subject_set_int(&theme_subject, THEME_MODE_DARK);
else lv_subject_set_int(&theme_subject, THEME_MODE_LIGHT);
}
/*-----------------------------------------
* my_panel.c
*
* It would be a separate file with its own
* local types, data, and functions
*------------------------------------------*/
typedef struct {
lv_style_t style_main;
lv_style_t style_scrollbar;
} my_panel_styles_t;
static void my_panel_style_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
LV_UNUSED(subject);
LV_UNUSED(observer);
theme_mode_t m = lv_subject_get_int(&theme_subject);
my_panel_styles_t * styles = lv_observer_get_target(observer);
if(m == THEME_MODE_LIGHT) {
lv_style_set_bg_color(&styles->style_main, lv_color_hex3(0xfff));
lv_style_set_shadow_color(&styles->style_main, lv_color_hex3(0x888));
lv_style_set_text_color(&styles->style_main, lv_color_hex3(0x222));
lv_style_set_bg_color(&styles->style_scrollbar, lv_color_hex3(0x888));
}
if(m == THEME_MODE_DARK) {
lv_style_set_bg_color(&styles->style_main, lv_color_hex(0x040038));
lv_style_set_shadow_color(&styles->style_main, lv_color_hex3(0xaaa));
lv_style_set_text_color(&styles->style_main, lv_color_hex3(0xeee));
lv_style_set_bg_color(&styles->style_scrollbar, lv_color_hex3(0xaaa));
}
lv_obj_report_style_change(&styles->style_main);
lv_obj_report_style_change(&styles->style_scrollbar);
}
static lv_obj_t * my_panel_create(lv_obj_t * parent)
{
static bool inited = false;
static my_panel_styles_t styles;
if(!inited) {
inited = true;
lv_style_init(&styles.style_main);
lv_style_set_radius(&styles.style_main, 12);
lv_style_set_bg_opa(&styles.style_main, LV_OPA_COVER);
lv_style_set_shadow_width(&styles.style_main, 24);
lv_style_set_shadow_offset_x(&styles.style_main, 4);
lv_style_set_shadow_offset_y(&styles.style_main, 6);
lv_style_set_pad_all(&styles.style_main, 12);
lv_style_set_pad_gap(&styles.style_main, 16);
lv_style_init(&styles.style_scrollbar);
lv_style_set_width(&styles.style_scrollbar, 4);
lv_style_set_radius(&styles.style_scrollbar, 2);
lv_style_set_pad_right(&styles.style_scrollbar, 8);
lv_style_set_pad_ver(&styles.style_scrollbar, 8);
lv_style_set_bg_opa(&styles.style_scrollbar, LV_OPA_50);
lv_subject_add_observer_with_target(&theme_subject, my_panel_style_observer_cb, &styles, NULL);
}
lv_obj_t * panel = lv_obj_create(parent);
lv_obj_remove_style_all(panel);
lv_obj_add_style(panel, &styles.style_main, 0);
lv_obj_add_style(panel, &styles.style_scrollbar, LV_PART_SCROLLBAR);
return panel;
}
/*-----------------------------------------
* my_button.c
*
* It would be a separate file with its own
* local types, data, and functions
*------------------------------------------*/
typedef struct {
lv_style_t style_main;
lv_style_t style_pressed;
} my_button_styles_t;
static void my_button_style_observer_cb(lv_observer_t * observer, lv_subject_t * subject)
{
LV_UNUSED(subject);
LV_UNUSED(observer);
theme_mode_t m = lv_subject_get_int(&theme_subject);
my_button_styles_t * styles = lv_observer_get_target(observer);
if(m == THEME_MODE_LIGHT) {
lv_style_set_bg_color(&styles->style_main, lv_color_hex(0x3379de));
lv_style_set_bg_grad_color(&styles->style_main, lv_color_hex(0xd249a5));
lv_style_set_shadow_color(&styles->style_main, lv_color_hex(0x3379de));
lv_style_set_text_color(&styles->style_main, lv_color_hex3(0xfff));
lv_style_set_color_filter_opa(&styles->style_pressed, LV_OPA_70);
}
if(m == THEME_MODE_DARK) {
lv_style_set_bg_color(&styles->style_main, lv_color_hex(0xde1382));
lv_style_set_bg_grad_color(&styles->style_main, lv_color_hex(0x4b0c72));
lv_style_set_shadow_color(&styles->style_main, lv_color_hex(0x4b0c72));
lv_style_set_text_color(&styles->style_main, lv_color_hex3(0xfff));
lv_style_set_color_filter_opa(&styles->style_pressed, LV_OPA_30);
}
lv_obj_report_style_change(&styles->style_main);
lv_obj_report_style_change(&styles->style_pressed);
}
static lv_obj_t * my_button_create(lv_obj_t * parent, const char * text, lv_event_cb_t event_cb)
{
static bool inited = false;
static my_button_styles_t styles;
if(!inited) {
inited = true;
lv_style_init(&styles.style_main);
lv_style_set_radius(&styles.style_main, LV_RADIUS_CIRCLE);
lv_style_set_bg_opa(&styles.style_main, LV_OPA_COVER);
lv_style_set_bg_grad_dir(&styles.style_main, LV_GRAD_DIR_HOR);
lv_style_set_shadow_width(&styles.style_main, 24);
lv_style_set_shadow_offset_y(&styles.style_main, 6);
lv_style_set_pad_hor(&styles.style_main, 32);
lv_style_set_pad_ver(&styles.style_main, 12);
lv_style_init(&styles.style_pressed);
lv_style_set_color_filter_dsc(&styles.style_pressed, &lv_color_filter_shade);
lv_subject_add_observer_with_target(&theme_subject, my_button_style_observer_cb, &styles, NULL);
}
lv_obj_t * btn = lv_button_create(parent);
lv_obj_remove_style_all(btn);
lv_obj_add_style(btn, &styles.style_main, 0);
lv_obj_add_style(btn, &styles.style_pressed, LV_STATE_PRESSED);
lv_obj_add_event_cb(btn, event_cb, LV_EVENT_CLICKED, NULL);
lv_obj_t * label = lv_label_create(btn);
lv_label_set_text(label, text);
return btn;
}
#endif