diff --git a/docs/others/observer.rst b/docs/others/observer.rst new file mode 100644 index 000000000..b220b5536 --- /dev/null +++ b/docs/others/observer.rst @@ -0,0 +1,318 @@ +======== +Observer +======== + +Overview +******** + +The ``lv_observer`` module implements a standard `Observer pattern `__. +It consists of + +- subjects: each containing a value +- observers: attached to subjects to be notified on value change + + +A typical use case looks like this: + +.. code:: c + //It's a global variable + lv_subject_t my_subject; + + /*------- + * main.c + *-------*/ + + extern lv_subject_t my_subject; + + void main(void) + { + //Initialize the 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 the related subject's value changes + static void some_observer_cb(lv_subject_t * subject, lv_observer_t * observer) + { + int32_t v = lv_subject_get_int(subject); + do_something(v); + } + + void some_module_init(void) + { + //Subscribe to a subject + lv_subject_add_observer(&some_subject, some_observer_cb, NULL); + } + + /*-------------- + * some_system.c + *--------------*/ + + extern lv_subject_t some_subject; + + void some_event(void) + { + //Set the subject's value to 30. It will notify `some_observer_cb` + lv_subject_set_int(&some_subject, 30); + } + + + +Subject +******* + +Subject initialization +---------------------- + +Subjects have to be static or global :c:expr:`lv_subject_t` type variables. + +To initialize a subject use :c:expr:`lv_subject_init_(&subject, , init_value)`. +The following initializations exist for types: + +- **Integer** :c:expr:`void lv_subject_init_int(lv_subject_t * subject, int32_t value)` +- **String** :c:expr:`void lv_subject_init_string(lv_subject_t * subject, char * buf, char * prev_buf, size_t size, const char * value)` +- **Pointer** :c:expr:`void lv_subject_init_pointer(lv_subject_t * subject, void * value)` +- **Color** :c:expr:`void lv_subject_init_color(lv_subject_t * subject, lv_color_t color)` +- **Group** :c:expr:`void lv_subject_init_group(lv_subject_t * subject, lv_subject_t * list[], uint32_t list_len)` + + +Set subject value +----------------- + +The following functions can be used to set a subject's value: + +- **Integer** :c:expr:`void lv_subject_set_int(lv_subject_t * subject, int32_t value)` +- **String** :c:expr:`void lv_subject_copy_string(lv_subject_t * subject, char * buf)` +- **Pointer** :c:expr:`void lv_subject_set_pointer(lv_subject_t * subject, void * ptr)` +- **Color** :c:expr:`void lv_subject_set_color(lv_subject_t * subject, lv_color_t color)` + +Get subject's value +------------------- + +The following functions can be used to get a subject's value: + + +- **Integer** :c:expr:`int32_t lv_subject_get_int(lv_subject_t * subject)` +- **String** :c:expr:`const char * lv_subject_get_string(lv_subject_t * subject)` +- **Pointer** :c:expr:`const void * lv_subject_get_pointer(lv_subject_t * subject)` +- **Color** :c:expr:`lv_color_t lv_subject_get_color(lv_subject_t * subject)` + + +Get subject's previous value +---------------------------- + +The following functions can be used to get a subject's previous value: + + +- **Integer** :c:expr:`int32_t lv_subject_get_previous_int(lv_subject_t * subject)` +- **String** :c:expr:`const char * lv_subject_get_previous_string(lv_subject_t * subject)` +- **Pointer** :c:expr:`const void * lv_subject_get_previous_pointer(lv_subject_t * subject)` +- **Color** :c:expr:`lv_color_t lv_subject_get_previous_color(lv_subject_t * subject)` + + +Observer +******** + +Subscribe to a subject +---------------------- + +to subscribe to a subject the following function can be used: + +.. code:: c + + lv_observer_t * observer = lv_subject_add_observer(&some_subject, some_observer_cb, user_data); + + +Where the observer callback should look like this: +.. code:: c + + static void some_observer_cb(lv_subject_t * subject, lv_observer_t * observer) + { + ... + } + + +It's also possible to save a target widget when subscribing to a subject. +In this case when widget is deleted, it will automatically unsubscribe from the subject. + +In the observer callback ``lv_observer_get_target(observer)`` can be used to get the saved widget. +.. code:: c + + lv_observer_t * observer = lv_subject_add_observer_obj(&some_subject, some_observer_cb, obj, user_data); + + +In more generic case any pointer can be saved a target: +.. code:: c + + lv_observer_t * observer = lv_subject_add_observer_with_target(&some_subject, some_observer_cb, some_pointer, user_data); + + + +Unsubscribe from a subject +-------------------------- + +.. code:: c + + lv_observer_remove(observer) + +To unsubscribe from a subject with all widgets you can use: + +.. code:: c + + lv_subject_remove_obj(subject, obj) + + +Subject groups +************** + +There are cases when a subject changes, the value of some other subjects are also required. +As practical example imagine an instrument which measures either voltage or current. +To display the measured value on a label 3 things are required: + +1. What we measure (current or voltage)? +2. What is the measured value? +3. What is the unit (mV, V, mA, A)? + +What any of these 3 parameters changes the label needs to be updated, +and it needs to know all 3 parameters to compose its text. + +For these kind of scenarios subject groups can be created. +For the above example it looks like this: + +.. code:: c + 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; //It will be the subject group + 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(&subject_all, all_observer_cb, some_label, NULL); + + ... + + static void some_observer_cb(lv_subject_t * subject, lv_observer_t * observer) + { + 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 +************** + +Base object +----------- + +Set an object flag if an integer subject's value is equal to a reference value, clear the flag otherwise + +.. code:: c + + observer = lv_obj_bind_flag_if_eq(obj, &subject, LV_OBJ_FLAG_*, ref_value); + +Set an object flag if an integer subject's value is not equal to a reference value, clear the flag otherwise + +.. code:: c + + observer = lv_obj_bind_flag_if_not_eq(obj, &subject, LV_OBJ_FLAG_*, ref_value); + +Set an object state if an integer subject's value is equal to a reference value, clear the flag otherwise + +.. code:: c + + observer = lv_obj_bind_state_if_eq(obj, &subject, LV_STATE_*, ref_value); + +Set an object state if an integer subject's value is not equal to a reference value, clear the flag otherwise + +.. code:: c + + observer = lv_obj_bind_state_if_not_eq(obj, &subject, LV_STATE_*, ref_value); + +Button +------ + +Set an integer subject to 1 when a button is checked and set it 0 when unchecked. + +.. code:: c + + observer = lv_button_bind_checked(obj, &subject); + +Label +----- + +Bind an integer, string, or pointer (pointing to a string) subject to a label. +An optional format string can be added with 1 format specifier (e.g. "%d °C") +If the format string is NULL the value will be used directly. In this case on string ans pointer type subjects can be used. + +.. code:: c + + observer = lv_label_bind_text(obj, &subject, format_string); + + +Arc +--- + +Bind an integer subject to an arc's value. + +.. code:: c + + observer = lv_arc_bind_value(obj, &subject); + +Slider +------ + +Bind an integer subject to a slider's value + +.. code:: c + + observer = lv_slider_bind_value(obj, &subject); + +Roller +------ + +Bind an integer subject to a roller's value + +.. code:: c + + observer = lv_roller_bind_value(obj, &subject); + + +Drop-down +--------- +Bind an integer subject to a drop-down's value + +.. code:: c + + observer = lv_dropdown_bind_value(obj, &subject); + + +Example +------- + +.. include:: ../examples/others/observer/index.rst + +API +--- + + + diff --git a/examples/others/lv_example_others.h b/examples/others/lv_example_others.h index 8fb77ccd8..c32ee554c 100644 --- a/examples/others/lv_example_others.h +++ b/examples/others/lv_example_others.h @@ -20,6 +20,7 @@ extern "C" { #include "imgfont/lv_example_imgfont.h" #include "monkey/lv_example_monkey.h" #include "msg/lv_example_msg.h" +#include "observer/lv_example_observer.h" #include "snapshot/lv_example_snapshot.h" /********************* diff --git a/examples/others/observer/index.rst b/examples/others/observer/index.rst new file mode 100644 index 000000000..916c5e8d6 --- /dev/null +++ b/examples/others/observer/index.rst @@ -0,0 +1,38 @@ + +Bind a slider's value to a label +-------------------------------- + +.. lv_example:: others/observer/lv_example_observer_1 + :language: c + +Handling login and its states +----------------------------- + +.. lv_example:: others/observer/lv_example_observer_2 + :language: c + +Set time with 12/24 mode and AM/PM +---------------------------------- + +.. lv_example:: others/observer/lv_example_observer_3 + :language: c + +Custom tab view with state management +------------------------------------- + +.. lv_example:: others/observer/lv_example_observer_4 + :language: c + +Firmware update process +----------------------- + +.. lv_example:: others/observer/lv_example_observer_5 + :language: c + +Modular style update on theme change +------------------------------------ + +.. lv_example:: others/observer/lv_example_observer_6 + :language: c + + diff --git a/examples/others/observer/lv_example_observer.h b/examples/others/observer/lv_example_observer.h new file mode 100644 index 000000000..a43fca7b3 --- /dev/null +++ b/examples/others/observer/lv_example_observer.h @@ -0,0 +1,43 @@ +/** + * @file lv_example_observer.h + * + */ + +#ifndef LV_EXAMPLE_OBSERVER_H +#define LV_EXAMPLE_OBSERVER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void lv_example_observer_1(void); +void lv_example_observer_2(void); +void lv_example_observer_3(void); +void lv_example_observer_4(void); +void lv_example_observer_5(void); +void lv_example_observer_6(void); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_EXAMPLE_OBSERVER_H*/ diff --git a/examples/others/observer/lv_example_observer_1.c b/examples/others/observer/lv_example_observer_1.c new file mode 100644 index 000000000..042ec43fe --- /dev/null +++ b/examples/others/observer/lv_example_observer_1.c @@ -0,0 +1,25 @@ +#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_scr_act()); + 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_scr_act()); + lv_obj_align(label, LV_ALIGN_CENTER, 0, 30); + lv_label_bind_text(label, &temperature_subject, "%d °C"); +} + + +#endif diff --git a/examples/others/observer/lv_example_observer_2.c b/examples/others/observer/lv_example_observer_2.c new file mode 100644 index 000000000..adb90f4c3 --- /dev/null +++ b/examples/others/observer/lv_example_observer_2.c @@ -0,0 +1,136 @@ +#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_subject_t * subject, lv_observer_t * observer) +{ + LV_UNUSED(observer); + + int32_t v = lv_subject_get_int(subject); + /*In a real application set/clear a pin here*/ + LV_LOG_USER("Engine state: %d", 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(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_subject_t * subject, lv_observer_t * observer) +{ + 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_scr_act()); + 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(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_scr_act()); + 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_scr_act()); + lv_obj_set_pos(btn, 220, 10); + lv_obj_add_event(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_scr_act()); + 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_scr_act()); + 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 diff --git a/examples/others/observer/lv_example_observer_3.c b/examples/others/observer/lv_example_observer_3.c new file mode 100644 index 000000000..08227b055 --- /dev/null +++ b/examples/others/observer/lv_example_observer_3.c @@ -0,0 +1,160 @@ +#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_subject_t * subject, lv_observer_t * observer); +static void time_observer_cb(lv_subject_t * subject, lv_observer_t * observer); + +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_scr_act()); + 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_scr_act()); + lv_obj_set_pos(set_btn, 180, 24); + lv_obj_add_event(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_scr_act()); + 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(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_clear_state(set_btn, LV_STATE_DISABLED); + lv_obj_del(cont); +} + +/*Watch all related subject to display the current time correctly*/ +static void time_observer_cb(lv_subject_t * subject, lv_observer_t * observer) +{ + 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, "%d:%02d", hour, minute); + } + else { + lv_label_set_text_fmt(label, "%d:%02d %s", hour + 1, minute, am_pm == TIME_AM ? "am" : "pm"); + } +} + +/*Change the hour options on format change*/ +static void hour_roller_options_update(lv_subject_t * subject, lv_observer_t * observer) +{ + 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 diff --git a/examples/others/observer/lv_example_observer_4.c b/examples/others/observer/lv_example_observer_4.c new file mode 100644 index 000000000..68abffa97 --- /dev/null +++ b/examples/others/observer/lv_example_observer_4.c @@ -0,0 +1,206 @@ +#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_subject_t * subject, lv_observer_t * observer); +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_subject_t * subject, lv_observer_t * observer); +static void indicator_observer_cb(lv_subject_t * subject, lv_observer_t * observer); + +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_scr_act()); + 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_subject_t * subject, lv_observer_t * observer) +{ + 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_time(&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_ready_cb(&a, lv_obj_del_anim_ready_cb); + + uint32_t i; + uint32_t delay = 0; + uint32_t child_cnt_prev = lv_obj_get_child_cnt(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_ready_cb(&a, NULL); + for(i = child_cnt_prev; i < lv_obj_get_child_cnt(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(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_subject_t * subject, lv_observer_t * observer) +{ + 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_clear_state(btn, LV_STATE_CHECKED); + if(idx == cur_v) lv_obj_add_state(btn, LV_STATE_CHECKED); +} + +static void indicator_observer_cb(lv_subject_t * subject, lv_observer_t * observer) +{ + 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_time(&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 diff --git a/examples/others/observer/lv_example_observer_5.c b/examples/others/observer/lv_example_observer_5.c new file mode 100644 index 000000000..e8a2fddb7 --- /dev/null +++ b/examples/others/observer/lv_example_observer_5.c @@ -0,0 +1,167 @@ +#include "../../lv_examples.h" +#if LV_USE_OBSERVER && LV_USE_ARC && LV_USE_LABEL && LV_USE_BTN && 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_subject_t * subject, lv_observer_t * observer); +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_subject_t * subject, lv_observer_t * observer); + +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_btn_create(lv_scr_act()); + lv_obj_add_event(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_scr_act()); + 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_ofs_x(win, 2, 0); + lv_obj_set_style_shadow_ofs_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(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_del(win); + lv_subject_set_int(&fw_update_status_subject, FW_UPDATE_STATE_IDLE); +} + +static void fw_update_win_observer_cb(lv_subject_t * subject, lv_observer_t * observer) +{ + 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_clear_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(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_del(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_del(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_del(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_del(t); + } +} + +/** + * Emulate connection and FW dowlading by timers + */ +static void fw_upload_manager_observer_cb(lv_subject_t * subject, lv_observer_t * observer) +{ + 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 diff --git a/examples/others/observer/lv_example_observer_6.c b/examples/others/observer/lv_example_observer_6.c new file mode 100644 index 000000000..b71e6e788 --- /dev/null +++ b/examples/others/observer/lv_example_observer_6.c @@ -0,0 +1,192 @@ +#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_scr_act()); + 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_subject_t * subject, lv_observer_t * observer) +{ + 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_ofs_x(&styles.style_main, 4); + lv_style_set_shadow_ofs_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_subject_t * subject, lv_observer_t * observer) +{ + 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_ofs_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_btn_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(btn, event_cb, LV_EVENT_CLICKED, NULL); + + lv_obj_t * label = lv_label_create(btn); + lv_label_set_text(label, text); + + return btn; +} + +#endif diff --git a/examples/others/observer/lv_example_observer_7.c b/examples/others/observer/lv_example_observer_7.c new file mode 100644 index 000000000..c8e382761 --- /dev/null +++ b/examples/others/observer/lv_example_observer_7.c @@ -0,0 +1 @@ +typedef int dummy_int_t; diff --git a/lv_conf_template.h b/lv_conf_template.h index c1db477b7..a6661de8e 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -699,6 +699,9 @@ /*1: Enable a published subscriber based messaging system */ #define LV_USE_MSG 0 +/*1: Enable an observer pattern implementation*/ +#define LV_USE_OBSERVER 0 + /*1: Enable Pinyin input method*/ /*Requires: lv_keyboard*/ #define LV_USE_IME_PINYIN 0 diff --git a/lvgl.h b/lvgl.h index a3334d7ea..cdb14d7da 100644 --- a/lvgl.h +++ b/lvgl.h @@ -86,6 +86,7 @@ extern "C" { #include "src/others/fragment/lv_fragment.h" #include "src/others/imgfont/lv_imgfont.h" #include "src/others/msg/lv_msg.h" +#include "src/others/observer/lv_observer.h" #include "src/others/ime/lv_ime_pinyin.h" #include "src/others/file_explorer/lv_file_explorer.h" diff --git a/src/core/lv_obj_event.c b/src/core/lv_obj_event.c index e5aaeb966..1df77ce2b 100644 --- a/src/core/lv_obj_event.c +++ b/src/core/lv_obj_event.c @@ -124,6 +124,23 @@ bool lv_obj_remove_event(lv_obj_t * obj, uint32_t index) return lv_event_remove(&obj->spec_attr->event_list, index); } +bool lv_obj_remove_event_cb(lv_obj_t * obj, lv_event_cb_t event_cb) +{ + LV_ASSERT_NULL(obj); + + uint32_t event_cnt = lv_obj_get_event_count(obj); + uint32_t i; + for(i = 0; i < event_cnt; i++) { + lv_event_dsc_t * dsc = lv_obj_get_event_dsc(obj, i); + if(dsc->cb == event_cb) { + lv_obj_remove_event(obj, i); + return true; + } + } + + return false; +} + lv_obj_t * lv_event_get_current_target_obj(lv_event_t * e) { return lv_event_get_current_target(e); diff --git a/src/core/lv_obj_event.h b/src/core/lv_obj_event.h index dc2cc5a84..b7ba46856 100644 --- a/src/core/lv_obj_event.h +++ b/src/core/lv_obj_event.h @@ -112,6 +112,8 @@ lv_event_dsc_t * lv_obj_get_event_dsc(struct _lv_obj_t * obj, uint32_t index); bool lv_obj_remove_event(struct _lv_obj_t * obj, uint32_t index); +bool lv_obj_remove_event_cb(struct _lv_obj_t * obj, lv_event_cb_t event_cb); + /** * Get the input device passed as parameter to indev related events. * @param e pointer to an event diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h index c541b27ef..907fe48ed 100644 --- a/src/lv_conf_internal.h +++ b/src/lv_conf_internal.h @@ -2283,6 +2283,15 @@ #endif #endif +/*1: Enable an observer pattern implementation*/ +#ifndef LV_USE_OBSERVER + #ifdef CONFIG_LV_USE_OBSERVER + #define LV_USE_OBSERVER CONFIG_LV_USE_OBSERVER + #else + #define LV_USE_OBSERVER 0 + #endif +#endif + /*1: Enable Pinyin input method*/ /*Requires: lv_keyboard*/ #ifndef LV_USE_IME_PINYIN diff --git a/src/misc/lv_color.c b/src/misc/lv_color.c index 580893d98..1d45ef8b2 100644 --- a/src/misc/lv_color.c +++ b/src/misc/lv_color.c @@ -21,6 +21,13 @@ /********************** * STATIC PROTOTYPES **********************/ +static lv_color_t lv_color_filter_shade_cb(const lv_color_filter_dsc_t * dsc, lv_color_t c, lv_opa_t opa); + +/********************** + * GLOBAL VARIABLES + **********************/ + +const lv_color_filter_dsc_t lv_color_filter_shade = {.filter_cb = lv_color_filter_shade_cb}; /********************** * STATIC VARIABLES @@ -261,3 +268,29 @@ lv_color_hsv_t lv_color_to_hsv(lv_color_t c) { return lv_color_rgb_to_hsv(c.red, c.green, c.blue); } + + +/********************** + * STATIC FUNCTIONS + **********************/ + +/** + * Helper function to easily create color filters + * @param dsc pointer to a color filter descriptor + * @param c the color to modify + * @param opa the intensity of the modification + * - LV_OPA_50: do nothing + * - < LV_OPA_50: darken + * - LV_OPA_0: fully black + * - > LV_OPA_50: lighten + * - LV_OPA_100: fully white + * @return the modified color + */ +static lv_color_t lv_color_filter_shade_cb(const lv_color_filter_dsc_t * dsc, lv_color_t c, lv_opa_t opa) +{ + LV_UNUSED(dsc); + if(opa == LV_OPA_50) return c; + if(opa < LV_OPA_50) return lv_color_lighten(c, (LV_OPA_50 - opa) * 2); + else return lv_color_darken(c, (opa - LV_OPA_50 * LV_OPA_50) * 2); +} + diff --git a/src/misc/lv_color.h b/src/misc/lv_color.h index f136e4ecf..932853f87 100644 --- a/src/misc/lv_color.h +++ b/src/misc/lv_color.h @@ -295,6 +295,7 @@ static inline lv_color_t lv_color_black(void) return lv_color_make(0x00, 0x00, 0x00); } + /********************** * MACROS **********************/ @@ -302,6 +303,8 @@ static inline lv_color_t lv_color_black(void) #include "lv_palette.h" #include "lv_color_op.h" +extern const lv_color_filter_dsc_t lv_color_filter_shade; + #ifdef __cplusplus } /*extern "C"*/ #endif diff --git a/src/others/observer/lv_observer.c b/src/others/observer/lv_observer.c new file mode 100644 index 000000000..a0232aa20 --- /dev/null +++ b/src/others/observer/lv_observer.c @@ -0,0 +1,643 @@ +/** + * @file lv_observer.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_observer.h" +#if LV_USE_OBSERVER + +#include "../../lvgl.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + uint32_t flag; + lv_subject_value_t value; + uint32_t inv : 1; +} flag_and_cond_t; + +/********************** + * STATIC PROTOTYPES + **********************/ +static void unsubscribe_on_delete_cb(lv_event_t * e); +static void group_notify_cb(lv_subject_t * subject, lv_observer_t * observer); +static lv_observer_t * bind_to_bitfield(lv_subject_t * subject, lv_obj_t * obj, lv_observer_cb_t cb, uint32_t flag, + int32_t ref_value, bool inv); +static void obj_flag_observer_cb(lv_subject_t * subject, lv_observer_t * observer); +static void obj_state_observer_cb(lv_subject_t * subject, lv_observer_t * observer); +static void btn_value_changed_event_cb(lv_event_t * e); + +static void label_text_observer_cb(lv_subject_t * subject, lv_observer_t * observer); + +static void arc_value_changed_event_cb(lv_event_t * e); +static void arc_value_observer_cb(lv_subject_t * subject, lv_observer_t * observer); + +static void slider_value_changed_event_cb(lv_event_t * e); +static void slider_value_observer_cb(lv_subject_t * subject, lv_observer_t * observer); + +static void roller_value_changed_event_cb(lv_event_t * e); +static void roller_value_observer_cb(lv_subject_t * subject, lv_observer_t * observer); + +static void dropdown_value_changed_event_cb(lv_event_t * e); +static void dropdown_value_observer_cb(lv_subject_t * subject, lv_observer_t * observer); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_subject_init_int(lv_subject_t * subject, int32_t value) +{ + lv_memzero(subject, sizeof(lv_subject_t)); + subject->type = LV_SUBJECT_TYPE_INT; + subject->value.num = value; + subject->prev_value.num = value; + _lv_ll_init(&(subject->subs_ll), sizeof(lv_observer_t)); +} + + +void lv_subject_set_int(lv_subject_t * subject, int32_t value) +{ + if(subject->type != LV_SUBJECT_TYPE_INT) { + LV_LOG_WARN("Subject type is not LV_SUBJECT_TYPE_INT"); + return; + } + + subject->prev_value.num = subject->value.num; + subject->value.num = value; + lv_subject_notify(subject); +} + +int32_t lv_subject_get_int(lv_subject_t * subject) +{ + if(subject->type != LV_SUBJECT_TYPE_INT) { + LV_LOG_WARN("Subject type is not LV_SUBJECT_TYPE_INT"); + return 0; + } + + return subject->value.num; +} + +int32_t lv_subject_get_previous_int(lv_subject_t * subject) +{ + if(subject->type != LV_SUBJECT_TYPE_INT) { + LV_LOG_WARN("Subject type is not LV_SUBJECT_TYPE_INT"); + return 0; + } + + return subject->prev_value.num; +} + +void lv_subject_init_string(lv_subject_t * subject, char * buf, char * prev_buf, size_t size, const char * value) +{ + lv_memzero(subject, sizeof(lv_subject_t)); + lv_strncpy(buf, value, size); + if(prev_buf) lv_strncpy(prev_buf, value, size); + + subject->type = LV_SUBJECT_TYPE_STRING; + subject->size = size; + subject->value.pointer = buf; + subject->prev_value.pointer = prev_buf; + + _lv_ll_init(&(subject->subs_ll), sizeof(lv_observer_t)); +} + +void lv_subject_copy_string(lv_subject_t * subject, const char * buf) +{ + if(subject->type != LV_SUBJECT_TYPE_STRING) { + LV_LOG_WARN("Subject type is not LV_SUBJECT_TYPE_INT"); + return; + } + + if(subject->size < 1) return; + if(subject->prev_value.pointer) { + lv_strncpy((char *)subject->prev_value.pointer, subject->value.pointer, subject->size - 1); + } + + lv_strncpy((char *)subject->value.pointer, buf, subject->size - 1); + + lv_subject_notify(subject); + +} + +const char * lv_subject_get_string(lv_subject_t * subject) +{ + if(subject->type != LV_SUBJECT_TYPE_STRING) { + LV_LOG_WARN("Subject type is not LV_SUBJECT_TYPE_STRING"); + return ""; + } + + return subject->value.pointer; +} + +const char * lv_subject_get_previous_string(lv_subject_t * subject) +{ + if(subject->type != LV_SUBJECT_TYPE_STRING) { + LV_LOG_WARN("Subject type is not LV_SUBJECT_TYPE_STRING"); + return NULL; + } + + return subject->prev_value.pointer; +} + +void lv_subject_init_pointer(lv_subject_t * subject, void * value) +{ + lv_memzero(subject, sizeof(lv_subject_t)); + subject->type = LV_SUBJECT_TYPE_POINTER; + subject->value.pointer = value; + subject->prev_value.pointer = value; + _lv_ll_init(&(subject->subs_ll), sizeof(lv_observer_t)); +} + +void lv_subject_set_pointer(lv_subject_t * subject, void * ptr) +{ + if(subject->type != LV_SUBJECT_TYPE_POINTER) { + LV_LOG_WARN("Subject type is not LV_SUBJECT_TYPE_POINTER"); + return; + } + + subject->prev_value.pointer = subject->value.pointer; + subject->value.pointer = ptr; + lv_subject_notify(subject); +} + +const void * lv_subject_get_pointer(lv_subject_t * subject) +{ + if(subject->type != LV_SUBJECT_TYPE_POINTER) { + LV_LOG_WARN("Subject type is not LV_SUBJECT_TYPE_POINTER"); + return NULL; + } + + return subject->value.pointer; +} + +const void * lv_subject_get_previous_pointer(lv_subject_t * subject) +{ + if(subject->type != LV_SUBJECT_TYPE_POINTER) { + LV_LOG_WARN("Subject type is not LV_SUBJECT_TYPE_POINTER"); + return NULL; + } + + return subject->prev_value.pointer; +} + +void lv_subject_init_color(lv_subject_t * subject, lv_color_t color) +{ + lv_memzero(subject, sizeof(lv_subject_t)); + subject->type = LV_SUBJECT_TYPE_COLOR; + subject->value.color = color; + subject->prev_value.color = color; + _lv_ll_init(&(subject->subs_ll), sizeof(lv_observer_t)); +} + + +void lv_subject_set_color(lv_subject_t * subject, lv_color_t color) +{ + if(subject->type != LV_SUBJECT_TYPE_COLOR) { + LV_LOG_WARN("Subject type is not LV_SUBJECT_TYPE_COLOR"); + return; + } + + subject->prev_value.color = subject->value.color; + subject->value.color = color; + lv_subject_notify(subject); +} + +lv_color_t lv_subject_get_color(lv_subject_t * subject) +{ + if(subject->type != LV_SUBJECT_TYPE_COLOR) { + LV_LOG_WARN("Subject type is not LV_SUBJECT_TYPE_COLOR"); + return lv_color_black(); + } + + return subject->value.color; +} + +lv_color_t lv_subject_get_previous_color(lv_subject_t * subject) +{ + if(subject->type != LV_SUBJECT_TYPE_COLOR) { + LV_LOG_WARN("Subject type is not LV_SUBJECT_TYPE_COLOR"); + return lv_color_black(); + } + + return subject->prev_value.color; +} + +void lv_subject_init_group(lv_subject_t * subject, lv_subject_t * list[], uint32_t list_len) +{ + subject->type = LV_SUBJECT_TYPE_GROUP; + subject->size = list_len; + _lv_ll_init(&(subject->subs_ll), sizeof(lv_observer_t)); + subject->value.pointer = list; + + /* bind all subjects to this subject */ + uint32_t i; + for(i = 0; i < list_len; i++) { + /*If a subject in the group changes notify the group itself*/ + lv_subject_add_observer(list[i], group_notify_cb, subject); + } +} + +lv_subject_t * lv_subject_get_group_element(lv_subject_t * subject, int32_t index) +{ + if(subject->type != LV_SUBJECT_TYPE_GROUP) { + LV_LOG_WARN("Subject type is not LV_SUBJECT_TYPE_GROUP"); + return NULL; + } + + if(index >= subject->size) return NULL; + + return ((lv_subject_t **)(subject->value.pointer))[index]; +} + + +lv_observer_t * lv_subject_add_observer(lv_subject_t * subject, lv_observer_cb_t cb, void * user_data) +{ + lv_observer_t * observer = lv_subject_add_observer_obj(subject, cb, NULL, user_data); + return observer; +} + +lv_observer_t * lv_subject_add_observer_obj(lv_subject_t * subject, lv_observer_cb_t cb, lv_obj_t * obj, + void * user_data) +{ + lv_observer_t * observer = _lv_ll_ins_tail(&(subject->subs_ll)); + LV_ASSERT_MALLOC(observer); + if(observer == NULL) return NULL; + + lv_memzero(observer, sizeof(*observer)); + + observer->subject = subject; + observer->cb = cb; + observer->user_data = user_data; + observer->target = obj; + /* subscribe to delete event of the object */ + if(obj != NULL) { + lv_obj_add_event(obj, unsubscribe_on_delete_cb, LV_EVENT_DELETE, observer); + } + + /* update object immediately */ + if(observer->cb) observer->cb(subject, observer); + + return observer; +} + + +lv_observer_t * lv_subject_add_observer_with_target(lv_subject_t * subject, lv_observer_cb_t cb, void * target, + void * user_data) +{ + lv_observer_t * observer = _lv_ll_ins_tail(&(subject->subs_ll)); + LV_ASSERT_MALLOC(observer); + if(observer == NULL) return NULL; + + lv_memzero(observer, sizeof(*observer)); + + observer->subject = subject; + observer->cb = cb; + observer->user_data = user_data; + observer->target = target; + + /* update object immediately */ + if(observer->cb) observer->cb(subject, observer); + + return observer; +} + +void lv_observer_remove(lv_observer_t * observer) +{ + LV_ASSERT_NULL(observer); + + observer->subject->notify_restart_query = 1; + + _lv_ll_remove(&(observer->subject->subs_ll), observer); + + if(observer->auto_free_user_data) { + lv_free(observer->user_data); + } + lv_free(observer); +} + +void lv_subject_remove_all_obj(lv_subject_t * subject, lv_obj_t * obj) +{ + while(lv_obj_remove_event_cb(obj, unsubscribe_on_delete_cb)); + while(lv_obj_remove_event_cb(obj, btn_value_changed_event_cb)); + while(lv_obj_remove_event_cb(obj, arc_value_changed_event_cb)); + while(lv_obj_remove_event_cb(obj, roller_value_changed_event_cb)); + while(lv_obj_remove_event_cb(obj, dropdown_value_changed_event_cb)); + + lv_observer_t * observer = _lv_ll_get_head(&subject->subs_ll); + while(observer) { + lv_observer_t * observer_next = _lv_ll_get_next(&subject->subs_ll, observer); + if(observer->target == obj) { + lv_observer_remove(observer); + } + observer = observer_next; + } +} + +void * lv_observer_get_target(lv_observer_t * observer) +{ + LV_ASSERT_NULL(observer); + + return observer->target; +} + +void lv_subject_notify(lv_subject_t * subject) +{ + LV_ASSERT_NULL(subject); + + lv_observer_t * observer; + _LV_LL_READ(&(subject->subs_ll), observer) { + observer->notified = 0; + } + + do { + subject->notify_restart_query = 0; + _LV_LL_READ(&(subject->subs_ll), observer) { + if(observer->cb && observer->notified == 0) { + observer->cb(subject, observer); + if(subject->notify_restart_query) break; + observer->notified = 1; + } + } + } while(subject->notify_restart_query); +} + + +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) +{ + lv_observer_t * observable = bind_to_bitfield(subject, obj, obj_flag_observer_cb, flag, ref_value, false); + return observable; +} + +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) +{ + lv_observer_t * observable = bind_to_bitfield(subject, obj, obj_flag_observer_cb, flag, ref_value, true); + return observable; +} + +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) +{ + lv_observer_t * observable = bind_to_bitfield(subject, obj, obj_state_observer_cb, state, ref_value, false); + return observable; +} + +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) +{ + lv_observer_t * observable = bind_to_bitfield(subject, obj, obj_state_observer_cb, state, ref_value, true); + return observable; +} + +lv_observer_t * lv_button_bind_checked(lv_obj_t * obj, lv_subject_t * subject) +{ + lv_observer_t * observable = bind_to_bitfield(subject, obj, obj_state_observer_cb, LV_STATE_CHECKED, 1, false); + lv_obj_add_event(obj, btn_value_changed_event_cb, LV_EVENT_VALUE_CHANGED, subject); + return observable; +} +lv_observer_t * lv_label_bind_text(lv_obj_t * obj, lv_subject_t * subject, const char * fmt) +{ + if(fmt == NULL) { + if(subject->type != LV_SUBJECT_TYPE_STRING && subject->type != LV_SUBJECT_TYPE_POINTER) { + LV_LOG_WARN("Incompatible subject type: %d", subject->type); + return NULL; + } + } + else { + if(subject->type != LV_SUBJECT_TYPE_STRING && subject->type != LV_SUBJECT_TYPE_POINTER && + subject->type != LV_SUBJECT_TYPE_INT) { + LV_LOG_WARN("Incompatible subject type: %d", subject->type); + return NULL; + } + } + + lv_observer_t * observer = lv_subject_add_observer_obj(subject, label_text_observer_cb, obj, (void *)fmt); + return observer; +} + +lv_observer_t * lv_arc_bind_value(lv_obj_t * obj, lv_subject_t * subject) +{ + if(subject->type != LV_SUBJECT_TYPE_INT) { + LV_LOG_WARN("Incompatible subject type: %d", subject->type); + return NULL; + } + + lv_obj_add_event(obj, arc_value_changed_event_cb, LV_EVENT_VALUE_CHANGED, subject); + + lv_observer_t * observer = lv_subject_add_observer_obj(subject, arc_value_observer_cb, obj, NULL); + return observer; +} + + +lv_observer_t * lv_slider_bind_value(lv_obj_t * obj, lv_subject_t * subject) +{ + if(subject->type != LV_SUBJECT_TYPE_INT) { + LV_LOG_WARN("Incompatible subject type: %d", subject->type); + return NULL; + } + + lv_obj_add_event(obj, slider_value_changed_event_cb, LV_EVENT_VALUE_CHANGED, subject); + + lv_observer_t * observer = lv_subject_add_observer_obj(subject, slider_value_observer_cb, obj, NULL); + return observer; +} + +lv_observer_t * lv_roller_bind_value(lv_obj_t * obj, lv_subject_t * subject) +{ + if(subject->type != LV_SUBJECT_TYPE_INT) { + LV_LOG_WARN("Incompatible subject type: %d", subject->type); + return NULL; + } + + lv_obj_add_event(obj, roller_value_changed_event_cb, LV_EVENT_VALUE_CHANGED, subject); + + lv_observer_t * observer = lv_subject_add_observer_obj(subject, roller_value_observer_cb, obj, NULL); + return observer; + +} + +lv_observer_t * lv_dropdown_bind_value(lv_obj_t * obj, lv_subject_t * subject) +{ + if(subject->type != LV_SUBJECT_TYPE_INT) { + LV_LOG_WARN("Incompatible subject type: %d", subject->type); + return NULL; + } + + lv_obj_add_event(obj, dropdown_value_changed_event_cb, LV_EVENT_VALUE_CHANGED, subject); + + lv_observer_t * observer = lv_subject_add_observer_obj(subject, dropdown_value_observer_cb, obj, NULL); + return observer; + +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void group_notify_cb(lv_subject_t * subject, lv_observer_t * observer) +{ + LV_UNUSED(subject); + lv_subject_t * subject_group = observer->user_data; + lv_subject_notify(subject_group); +} + +static void unsubscribe_on_delete_cb(lv_event_t * e) +{ + lv_observer_t * observer = lv_event_get_user_data(e); + lv_observer_remove(observer); +} + +static lv_observer_t * bind_to_bitfield(lv_subject_t * subject, lv_obj_t * obj, lv_observer_cb_t cb, uint32_t flag, + int32_t ref_value, bool inv) +{ + if(subject->type != LV_SUBJECT_TYPE_INT) { + LV_LOG_WARN("Incompatible subject type: %d", subject->type); + return NULL; + } + + flag_and_cond_t * p = lv_malloc(sizeof(flag_and_cond_t)); + if(p == NULL) { + LV_LOG_WARN("Out of memory"); + return NULL; + } + + p->flag = flag; + p->value.num = ref_value; + p->inv = inv; + + lv_observer_t * observable = lv_subject_add_observer_obj(subject, cb, obj, p); + observable->auto_free_user_data = 1; + return observable; +} + +static void obj_flag_observer_cb(lv_subject_t * subject, lv_observer_t * observer) +{ + flag_and_cond_t * p = observer->user_data; + + bool res = subject->value.num == p->value.num; + if(p->inv) res = !res; + + if(res) { + lv_obj_add_flag(observer->target, p->flag); + } + else { + lv_obj_clear_flag(observer->target, p->flag); + } +} + +static void obj_state_observer_cb(lv_subject_t * subject, lv_observer_t * observer) +{ + flag_and_cond_t * p = observer->user_data; + + bool res = subject->value.num == p->value.num; + if(p->inv) res = !res; + + if(res) { + lv_obj_add_state(observer->target, p->flag); + } + else { + lv_obj_clear_state(observer->target, p->flag); + } +} + + +static void btn_value_changed_event_cb(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_current_target(e); + lv_subject_t * subject = lv_event_get_user_data(e); + + lv_subject_set_int(subject, lv_obj_has_state(obj, LV_STATE_CHECKED)); +} + +static void label_text_observer_cb(lv_subject_t * subject, lv_observer_t * observer) +{ + const char * fmt = observer->user_data; + + if(fmt == NULL) { + lv_label_set_text(observer->target, subject->value.pointer); + } + else { + switch(subject->type) { + case LV_SUBJECT_TYPE_INT: + lv_label_set_text_fmt(observer->target, fmt, subject->value.num); + break; + case LV_SUBJECT_TYPE_STRING: + case LV_SUBJECT_TYPE_POINTER: + lv_label_set_text_fmt(observer->target, fmt, subject->value.pointer); + break; + default: + break; + } + } +} + +static void arc_value_changed_event_cb(lv_event_t * e) +{ + lv_obj_t * arc = lv_event_get_current_target(e); + lv_subject_t * subject = lv_event_get_user_data(e); + + lv_subject_set_int(subject, lv_arc_get_value(arc)); +} + +static void arc_value_observer_cb(lv_subject_t * subject, lv_observer_t * observer) +{ + lv_arc_set_value(observer->target, subject->value.num); +} + +static void slider_value_changed_event_cb(lv_event_t * e) +{ + lv_obj_t * slider = lv_event_get_current_target(e); + lv_subject_t * subject = lv_event_get_user_data(e); + + lv_subject_set_int(subject, lv_slider_get_value(slider)); +} + +static void slider_value_observer_cb(lv_subject_t * subject, lv_observer_t * observer) +{ + lv_slider_set_value(observer->target, subject->value.num, LV_ANIM_OFF); +} + +static void roller_value_changed_event_cb(lv_event_t * e) +{ + lv_obj_t * roller = lv_event_get_current_target(e); + lv_subject_t * subject = lv_event_get_user_data(e); + + lv_subject_set_int(subject, lv_roller_get_selected(roller)); +} + +static void roller_value_observer_cb(lv_subject_t * subject, lv_observer_t * observer) +{ + if((int32_t)lv_roller_get_selected(observer->target) != subject->value.num) { + lv_roller_set_selected(observer->target, subject->value.num, LV_ANIM_OFF); + } +} + +static void dropdown_value_changed_event_cb(lv_event_t * e) +{ + lv_obj_t * dropdown = lv_event_get_current_target(e); + lv_subject_t * subject = lv_event_get_user_data(e); + + lv_subject_set_int(subject, lv_dropdown_get_selected(dropdown)); +} + +static void dropdown_value_observer_cb(lv_subject_t * subject, lv_observer_t * observer) +{ + lv_dropdown_set_selected(observer->target, subject->value.num); +} + +#endif /*LV_USE_OBSERVER*/ diff --git a/src/others/observer/lv_observer.h b/src/others/observer/lv_observer.h new file mode 100644 index 000000000..0091c0b39 --- /dev/null +++ b/src/others/observer/lv_observer.h @@ -0,0 +1,381 @@ +/** + * @file lv_observer.h + * + */ + +#ifndef LV_OBSERVER_H +#define LV_OBSERVER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "../../core/lv_obj.h" +#if LV_USE_OBSERVER + +/********************* + * DEFINES + *********************/ + + +/********************** + * TYPEDEFS + **********************/ + +struct _lv_observer_t; + +typedef enum { + LV_SUBJECT_TYPE_NONE = 0, + LV_SUBJECT_TYPE_INT = 1, /**< an int32_t*/ + LV_SUBJECT_TYPE_POINTER = 2, /**< a void pointer*/ + LV_SUBJECT_TYPE_COLOR = 3, /**< an lv_color_t*/ + LV_SUBJECT_TYPE_GROUP = 4, /**< an array of subjects*/ + LV_SUBJECT_TYPE_STRING = 5, /**< a char pointer*/ +} lv_subject_type_t; + + +/** + * A common type to handle all the various observable types in the same way + */ +typedef union { + int32_t num; /**< Integer number (opacity, enums, booleans or "normal" numbers)*/ + const void * pointer; /**< Constant pointer (string buffer, format string, font, cone text, etc)*/ + lv_color_t color; /**< Color */ +} lv_subject_value_t; + +/** + * The subject (an observable value) + */ +typedef struct { + lv_ll_t subs_ll; /**< Subscribers*/ + uint32_t type : 4; + uint32_t size : 28; /**< Might be used to store a size related to `type`*/ + lv_subject_value_t value; /**< Actual value*/ + lv_subject_value_t prev_value; /**< Previous value*/ + uint32_t notify_restart_query : 1; /**< If an observer deleted start notifying from the beginning. */ +} lv_subject_t; + +/** + * Callback called when the observed value changes + * @param s the lv_observer_t object created when the object was subscribed to the value + */ +typedef void (*lv_observer_cb_t)(lv_subject_t * subject, struct _lv_observer_t * observer); + +/** + * The observer object: a descriptor returned when subscribing LVGL widgets to subjects + */ +typedef struct _lv_observer_t { + lv_subject_t * subject; /**< The observed value */ + lv_observer_cb_t cb; /**< Callback that should be called when the value changes*/ + void * target; /**< A target for the observer, e.g. a widget or style*/ + void * user_data; /**< Additional parameter supplied when subscribing*/ + uint32_t auto_free_user_data : 1; /**< Automatically free user data when the observer is removed */ + uint32_t notified : 1; /**< Mark if this observer was already notified*/ +} lv_observer_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialize an integer type subject + * @param subject pointer to the subject + * @param value initial value + */ +void lv_subject_init_int(lv_subject_t * subject, int32_t value); + +/** + * Set the value of an integer subject. It will notify all the observers as well. + * @param subject pointer to the subject + * @param value the new value + */ +void lv_subject_set_int(lv_subject_t * subject, int32_t value); + +/** + * Get the current value of an integer subject + * @param subject pointer to the subject + * @return the current value + */ +int32_t lv_subject_get_int(lv_subject_t * subject); + +/** + * Get the previous value of an integer subject + * @param subject pointer to the subject + * @return the current value + */ +int32_t lv_subject_get_previous_int(lv_subject_t * subject); + +/** + * Initialize a string type subject + * @param subject pointer to the subject + * @param buf pointer to a buffer to store the string + * @param prev_buf pointer to a buffer to store the previous string, can be NULL if not used + * @param size size of the buffer + * @param value initial value as a string, e.g. "hello" + * @note the string subject stores the whole string, not only a pointer + */ +void lv_subject_init_string(lv_subject_t * subject, char * buf, char * prev_buf, size_t size, const char * value); + +/** + * Copy a string to a subject. It will notify all the observers as well. + * @param subject pointer to the subject + * @param buf the new string + */ +void lv_subject_copy_string(lv_subject_t * subject, const char * buf); + +/** + * Get the current value of an string subject + * @param subject pointer to the subject + * @return pointer to the buffer containing the current value + */ +const char * lv_subject_get_string(lv_subject_t * subject); + +/** + * Get the previous value of an string subject + * @param subject pointer to the subject + * @return pointer to the buffer containing the current value + * @note NULL will be returned if NULL was passed in `lv_subject_init_string()` + * as `prev_buf` + */ +const char * lv_subject_get_previous_string(lv_subject_t * subject); + +/** + * Initialize an pointer type subject + * @param subject pointer to the subject + * @param value initial value + */ +void lv_subject_init_pointer(lv_subject_t * subject, void * value); + +/** + * Set the value of a pointer subject. It will notify all the observers as well. + * @param subject pointer to the subject + * @param value the new value + */ +void lv_subject_set_pointer(lv_subject_t * subject, void * ptr); + +/** + * Get the current value of a pointer subject + * @param subject pointer to the subject + * @return the current value + */ +const void * lv_subject_get_pointer(lv_subject_t * subject); + +/** + * Get the previous value of a pointer subject + * @param subject pointer to the subject + * @return the current value + */ +const void * lv_subject_get_previous_pointer(lv_subject_t * subject); + +/** + * Initialize an color type subject + * @param subject pointer to the subject + * @param value initial value + */ +void lv_subject_init_color(lv_subject_t * subject, lv_color_t color); + +/** + * Set the value of a color subject. It will notify all the observers as well. + * @param subject pointer to the subject + * @param value the new value + */ +void lv_subject_set_color(lv_subject_t * subject, lv_color_t color); + +/** + * Get the current value of a color subject + * @param subject pointer to the subject + * @return the current value + */ +lv_color_t lv_subject_get_color(lv_subject_t * subject); + +/** + * Get the previous value of a color subject + * @param subject pointer to the subject + * @return the current value + */ +lv_color_t lv_subject_get_previous_color(lv_subject_t * subject); + +/** + * Initialize a subject group + * @param subject pointer to the subject + * @param list list of other subject addresses, any of these changes `subject` will be notified + * @param list_len number of elements in `list` + */ +void lv_subject_init_group(lv_subject_t * subject, lv_subject_t * list[], uint32_t list_len); + +/** + * Get an element from the subject group's list + * @param subject pointer to the subject + * @param index index of the element to get + * @return pointer a subject from the list, or NULL if the index is out of bounds + */ +lv_subject_t * lv_subject_get_group_element(lv_subject_t * subject, int32_t index); + +/** + * Add an observer to a subject. When the subject changes `observer_cb` will be called. + * @param subject pointer to the subject + * @param observer_cb the callback to call + * @param user_data optional user data + * @return pointer to the created observer + */ +lv_observer_t * lv_subject_add_observer(lv_subject_t * subject, lv_observer_cb_t observer_cb, void * user_data); + +/** + * Add an observer to a subject for an object. + * When the object is deleted, it will be removed from the subject automatically. + * @param subject pointer to the subject + * @param observer_cb the callback to call + * @param obj pointer to an object + * @param user_data optional user data + * @return pointer to the created observer + */ +lv_observer_t * lv_subject_add_observer_obj(lv_subject_t * subject, lv_observer_cb_t observer_cb, lv_obj_t * obj, + void * user_data); + + +/** + * Add an observer to a subject and also save a target. + * @param subject pointer to the subject + * @param observer_cb the callback to call + * @param target pointer to any data + * @param user_data optional user data + * @return pointer to the created observer + */ +lv_observer_t * lv_subject_add_observer_with_target(lv_subject_t * subject, lv_observer_cb_t cb, void * target, + void * user_data); + +/** + * Remove an observer from its subject + * @param observer pointer to an observer + */ +void lv_observer_remove(lv_observer_t * observer); + +/** + * Remove all observers from their subject related to an object + * @param observer pointer to an observer + * @param obj pointer to an object + */ +void lv_subject_remove_all_obj(lv_subject_t * subject, lv_obj_t * obj); + +/** + * Get the target of an observer + * @param observer pointer to an observer + * @return pointer to the saved target + */ +void * lv_observer_get_target(lv_observer_t * observer); + +/** + * Notify all observers of subject + * @param subject pointer to a subject + */ +void lv_subject_notify(lv_subject_t * subject); + +/** + * Set an object flag if an integer subject's value is equal to a reference value, clear the flag otherwise + * @param obj pointer to an object + * @param subject pointer to a subject + * @param flag a flag to set or clear (e.g. `LV_OBJ_FLAG_HIDDEN`) + * @param ref_value a reference value to compare the subject's value with + * @return pointer to the created observer + */ +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); + +/** + * Set an object flag if an integer subject's value is not equal to a reference value, clear the flag otherwise + * @param obj pointer to an object + * @param subject pointer to a subject + * @param flag a flag to set or clear (e.g. `LV_OBJ_FLAG_HIDDEN`) + * @param ref_value a reference value to compare the subject's value with + * @return pointer to the created observer + */ +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); + +/** + * Set an object state if an integer subject's value is equal to a reference value, clear the flag otherwise + * @param obj pointer to an object + * @param subject pointer to a subject + * @param flag a state to set or clear (e.g. `LV_STATE_CHECKED`) + * @param ref_value a reference value to compare the subject's value with + * @return pointer to the created observer + */ +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); + +/** + * Set an object state if an integer subject's value is not equal to a reference value, clear the flag otherwise + * @param obj pointer to an object + * @param subject pointer to a subject + * @param flag a state to set or clear (e.g. `LV_STATE_CHECKED`) + * @param ref_value a reference value to compare the subject's value with + * @return pointer to the created observer + */ +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); + +/** + * Set an integer subject to 1 when a button is checked and set it 0 when unchecked. + * @param obj pointer to a button + * @param subject pointer to a subject + * @return pointer to the created observer + */ +lv_observer_t * lv_button_bind_checked(lv_obj_t * obj, lv_subject_t * subject); + +/** + * Bind an integer, string, or pointer subject to a label. + * @param obj pointer to a label + * @param subject pointer to a subject + * @param fmt an optional format string with 1 format specifier (e.g. "%d °C") + * or NULL to bind the value directly. + * @return pointer to the created observer + * @note fmt == NULL can be used only with string and pointer subjects. + * @note if the subject is a pointer must point to a `\0` terminated string. + */ +lv_observer_t * lv_label_bind_text(lv_obj_t * obj, lv_subject_t * subject, const char * fmt); + +/** + * Bind an integer subject to an arc's value + * @param obj pointer to an arc + * @param subject pointer to a subject + * @return pointer to the created observer + */ +lv_observer_t * lv_arc_bind_value(lv_obj_t * obj, lv_subject_t * subject); + +/** + * Bind an integer subject to a slider's value + * @param obj pointer to a slider + * @param subject pointer to a subject + * @return pointer to the created observer + */ +lv_observer_t * lv_slider_bind_value(lv_obj_t * obj, lv_subject_t * subject); + +/** + * Bind an integer subject to a roller's value + * @param obj pointer to a roller + * @param subject pointer to a subject + * @return pointer to the created observer + */ +lv_observer_t * lv_roller_bind_value(lv_obj_t * obj, lv_subject_t * subject); + +/** + * Bind an integer subject to a dropdown's value + * @param obj pointer to a drop down + * @param subject pointer to a subject + * @return pointer to the created observer + */ +lv_observer_t * lv_dropdown_bind_value(lv_obj_t * obj, lv_subject_t * subject); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_OBSERVER*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_OBSERVER_H*/ diff --git a/src/themes/default/lv_theme_default.c b/src/themes/default/lv_theme_default.c index 8a4eecfa7..5187681b0 100644 --- a/src/themes/default/lv_theme_default.c +++ b/src/themes/default/lv_theme_default.c @@ -1005,6 +1005,7 @@ static void theme_apply(lv_theme_t * th, lv_obj_t * obj) lv_obj_add_style(obj, &theme->styles.outline_primary, LV_STATE_FOCUS_KEY); lv_obj_add_style(obj, &theme->styles.outline_secondary, LV_STATE_EDITED); lv_obj_add_style(obj, &theme->styles.transition_normal, LV_PART_INDICATOR); + lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED); } else if(lv_obj_check_type(obj, &lv_dropdownlist_class)) { lv_obj_add_style(obj, &theme->styles.card, 0); diff --git a/tests/src/lv_test_conf_full.h b/tests/src/lv_test_conf_full.h index 18572cc5a..acf8142c3 100644 --- a/tests/src/lv_test_conf_full.h +++ b/tests/src/lv_test_conf_full.h @@ -72,6 +72,7 @@ #define LV_USE_IMGFONT 1 #define LV_USE_IME_PINYIN 1 #define LV_USE_MSG 1 +#define LV_USE_OBSERVER 1 #define LV_USE_FILE_EXPLORER 1 #define LV_USE_TINY_TTF 1 #define LV_USE_SYSMON 1 diff --git a/tests/src/test_cases/test_observer.c b/tests/src/test_cases/test_observer.c new file mode 100644 index 000000000..3a8628cb2 --- /dev/null +++ b/tests/src/test_cases/test_observer.c @@ -0,0 +1,499 @@ +#if LV_BUILD_TEST == 1 +#include "../lvgl.h" + +#include "unity/unity.h" +#include "lv_test_indev.h" + +void setUp(void) +{ + /* Function run before every test */ +} + +void tearDown(void) +{ + /* Function run after every test */ + lv_obj_clean(lv_scr_act()); +} + +static int32_t prev_v; +static int32_t current_v; + +static void observer_int(lv_subject_t * subject, lv_observer_t * observer) +{ + LV_UNUSED(observer); + prev_v = lv_subject_get_previous_int(subject); + current_v = lv_subject_get_int(subject); +} + + +void test_observer_add_remove(void) +{ + static lv_subject_t subject; + lv_subject_init_int(&subject, 5); + + lv_observer_t * observer = lv_subject_add_observer(&subject, observer_int, NULL); + + current_v = 0; + lv_subject_set_int(&subject, 10); + TEST_ASSERT_EQUAL(10, lv_subject_get_int(&subject)); + TEST_ASSERT_EQUAL(10, current_v); + + lv_observer_remove(observer); + lv_subject_set_int(&subject, 15); + TEST_ASSERT_EQUAL(15, lv_subject_get_int(&subject)); + TEST_ASSERT_EQUAL(10, current_v); /*The observer cb is not called*/ +} + +void test_observer_int(void) +{ + static lv_subject_t subject; + lv_subject_init_int(&subject, 5); + TEST_ASSERT_EQUAL(5, lv_subject_get_int(&subject)); + TEST_ASSERT_EQUAL(5, lv_subject_get_previous_int(&subject)); + + lv_subject_set_int(&subject, 10); + TEST_ASSERT_EQUAL(10, lv_subject_get_int(&subject)); + TEST_ASSERT_EQUAL(5, lv_subject_get_previous_int(&subject)); + + lv_subject_set_int(&subject, 15); + TEST_ASSERT_EQUAL(15, lv_subject_get_int(&subject)); + TEST_ASSERT_EQUAL(10, lv_subject_get_previous_int(&subject)); + + /*Ignore incorrect types*/ + lv_subject_set_pointer(&subject, NULL); + TEST_ASSERT_EQUAL(15, lv_subject_get_int(&subject)); + TEST_ASSERT_EQUAL(10, lv_subject_get_previous_int(&subject)); + + lv_subject_set_color(&subject, lv_color_black()); + TEST_ASSERT_EQUAL(15, lv_subject_get_int(&subject)); + TEST_ASSERT_EQUAL(10, lv_subject_get_previous_int(&subject)); + + lv_subject_copy_string(&subject, "hello"); + TEST_ASSERT_EQUAL(15, lv_subject_get_int(&subject)); + TEST_ASSERT_EQUAL(10, lv_subject_get_previous_int(&subject)); +} + +void test_observer_string(void) +{ + char buf_current[32]; + char buf_previous[32]; + lv_subject_t subject; + lv_subject_init_string(&subject, buf_current, buf_previous, sizeof(buf_current), "hello"); + TEST_ASSERT_EQUAL_STRING("hello", lv_subject_get_string(&subject)); + TEST_ASSERT_EQUAL_STRING("hello", lv_subject_get_previous_string(&subject)); + + lv_subject_copy_string(&subject, "my name is John"); + TEST_ASSERT_EQUAL_STRING("my name is John", lv_subject_get_string(&subject)); + TEST_ASSERT_EQUAL_STRING("hello", lv_subject_get_previous_string(&subject)); + + lv_subject_copy_string(&subject, "how are you?"); + TEST_ASSERT_EQUAL_STRING("how are you?", lv_subject_get_string(&subject)); + TEST_ASSERT_EQUAL_STRING("my name is John", lv_subject_get_previous_string(&subject)); + + /*Clip long text*/ + lv_subject_copy_string(&subject, "text to be clipped to 32 chars.this should be clipped"); + TEST_ASSERT_EQUAL_STRING("text to be clipped to 32 chars", lv_subject_get_string(&subject)); + TEST_ASSERT_EQUAL_STRING("how are you?", lv_subject_get_previous_string(&subject)); + + /*Check if the previous string is clipped correctly*/ + lv_subject_copy_string(&subject, "a"); + TEST_ASSERT_EQUAL_STRING("a", lv_subject_get_string(&subject)); + TEST_ASSERT_EQUAL_STRING("text to be clipped to 32 chars", lv_subject_get_previous_string(&subject)); + + /*Ignore incorrect types*/ + lv_subject_set_pointer(&subject, NULL); + TEST_ASSERT_EQUAL_STRING("a", lv_subject_get_string(&subject)); + TEST_ASSERT_EQUAL_STRING("text to be clipped to 32 chars", lv_subject_get_previous_string(&subject)); + + lv_subject_set_color(&subject, lv_color_black()); + TEST_ASSERT_EQUAL_STRING("a", lv_subject_get_string(&subject)); + TEST_ASSERT_EQUAL_STRING("text to be clipped to 32 chars", lv_subject_get_previous_string(&subject)); + + lv_subject_set_int(&subject, 10); + TEST_ASSERT_EQUAL_STRING("a", lv_subject_get_string(&subject)); + TEST_ASSERT_EQUAL_STRING("text to be clipped to 32 chars", lv_subject_get_previous_string(&subject)); +} + +void test_observer_pointer(void) +{ + static int32_t a[3] = {0}; + static lv_subject_t subject; + + lv_subject_init_pointer(&subject, &a[0]); + TEST_ASSERT_EQUAL_PTR(&a[0], lv_subject_get_pointer(&subject)); + TEST_ASSERT_EQUAL_PTR(&a[0], lv_subject_get_previous_pointer(&subject)); + + lv_subject_set_pointer(&subject, &a[1]); + TEST_ASSERT_EQUAL_PTR(&a[1], lv_subject_get_pointer(&subject)); + TEST_ASSERT_EQUAL_PTR(&a[0], lv_subject_get_previous_pointer(&subject)); + + lv_subject_set_pointer(&subject, &a[2]); + TEST_ASSERT_EQUAL_PTR(&a[2], lv_subject_get_pointer(&subject)); + TEST_ASSERT_EQUAL_PTR(&a[1], lv_subject_get_previous_pointer(&subject)); + + /*Ignore incorrect types*/ + lv_subject_set_int(&subject, 10); + TEST_ASSERT_EQUAL_PTR(&a[2], lv_subject_get_pointer(&subject)); + TEST_ASSERT_EQUAL_PTR(&a[1], lv_subject_get_previous_pointer(&subject)); + + lv_subject_set_color(&subject, lv_color_black()); + TEST_ASSERT_EQUAL_PTR(&a[2], lv_subject_get_pointer(&subject)); + TEST_ASSERT_EQUAL_PTR(&a[1], lv_subject_get_previous_pointer(&subject)); + + lv_subject_copy_string(&subject, "hello"); + TEST_ASSERT_EQUAL_PTR(&a[2], lv_subject_get_pointer(&subject)); + TEST_ASSERT_EQUAL_PTR(&a[1], lv_subject_get_previous_pointer(&subject)); +} + +void test_observer_color(void) +{ + static lv_subject_t subject; + + lv_subject_init_color(&subject, lv_color_hex3(0x123)); + TEST_ASSERT_EQUAL_COLOR(lv_color_hex3(0x123), lv_subject_get_color(&subject)); + TEST_ASSERT_EQUAL_COLOR(lv_color_hex3(0x123), lv_subject_get_previous_color(&subject)); + + lv_subject_set_color(&subject, lv_color_hex3(0x456)); + TEST_ASSERT_EQUAL_COLOR(lv_color_hex3(0x456), lv_subject_get_color(&subject)); + TEST_ASSERT_EQUAL_COLOR(lv_color_hex3(0x123), lv_subject_get_previous_color(&subject)); + + lv_subject_set_color(&subject, lv_color_hex3(0xabc)); + TEST_ASSERT_EQUAL_COLOR(lv_color_hex3(0xabc), lv_subject_get_color(&subject)); + TEST_ASSERT_EQUAL_COLOR(lv_color_hex3(0x456), lv_subject_get_previous_color(&subject)); + + /*Ignore incorrect types*/ + lv_subject_set_pointer(&subject, NULL); + TEST_ASSERT_EQUAL_COLOR(lv_color_hex3(0xabc), lv_subject_get_color(&subject)); + TEST_ASSERT_EQUAL_COLOR(lv_color_hex3(0x456), lv_subject_get_previous_color(&subject)); + + lv_subject_set_int(&subject, 10); + TEST_ASSERT_EQUAL_COLOR(lv_color_hex3(0xabc), lv_subject_get_color(&subject)); + TEST_ASSERT_EQUAL_COLOR(lv_color_hex3(0x456), lv_subject_get_previous_color(&subject)); + + lv_subject_copy_string(&subject, "hello"); + TEST_ASSERT_EQUAL_COLOR(lv_color_hex3(0xabc), lv_subject_get_color(&subject)); + TEST_ASSERT_EQUAL_COLOR(lv_color_hex3(0x456), lv_subject_get_previous_color(&subject)); +} + +static int32_t group_observer_called; + +static void group_observer_cb(lv_subject_t * subject, lv_observer_t * observer) +{ + LV_UNUSED(observer); + LV_UNUSED(subject); + group_observer_called++; +} + +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_list[2] = {&subject_sub1, &subject_sub2}; + + lv_subject_init_int(&subject_sub1, 1); + lv_subject_init_int(&subject_sub2, 2); + + lv_subject_init_group(&subject_main, subject_list, 2); + TEST_ASSERT_EQUAL_PTR(&subject_sub1, lv_subject_get_group_element(&subject_main, 0)); + TEST_ASSERT_EQUAL_PTR(&subject_sub2, lv_subject_get_group_element(&subject_main, 1)); + TEST_ASSERT_EQUAL_PTR(NULL, lv_subject_get_group_element(&subject_main, 2)); + TEST_ASSERT_EQUAL_PTR(NULL, lv_subject_get_group_element(&subject_main, 1000)); + + group_observer_called = 0; + lv_subject_add_observer(&subject_main, group_observer_cb, NULL); + TEST_ASSERT_EQUAL(1, group_observer_called); + + lv_subject_set_int(&subject_sub1, 10); + TEST_ASSERT_EQUAL(2, group_observer_called); + + lv_subject_set_int(&subject_sub2, 20); + TEST_ASSERT_EQUAL(3, group_observer_called); +} + +void test_observer_obj_flag(void) +{ + lv_obj_t * obj = lv_obj_create(lv_scr_act()); + + /*Can bind only to int*/ + static lv_subject_t subject_wrong; + lv_subject_init_pointer(&subject_wrong, NULL); + lv_observer_t * observer = lv_obj_bind_state_if_eq(obj, &subject_wrong, LV_STATE_CHECKED, 5); + TEST_ASSERT_EQUAL_PTR(NULL, observer); + + static lv_subject_t subject; + lv_subject_init_int(&subject, 1); + + lv_obj_bind_flag_if_eq(obj, &subject, LV_OBJ_FLAG_HIDDEN, 5); + /*Should be applied immediately*/ + TEST_ASSERT_EQUAL(false, lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN)); + + lv_obj_bind_flag_if_not_eq(obj, &subject, LV_OBJ_FLAG_CHECKABLE, 10); + /*Should be applied immediately*/ + TEST_ASSERT_EQUAL(false, lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN)); + TEST_ASSERT_EQUAL(true, lv_obj_has_flag(obj, LV_OBJ_FLAG_CHECKABLE)); + + lv_subject_set_int(&subject, 5); + TEST_ASSERT_EQUAL(true, lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN)); + TEST_ASSERT_EQUAL(true, lv_obj_has_flag(obj, LV_OBJ_FLAG_CHECKABLE)); + + lv_subject_set_int(&subject, 10); + TEST_ASSERT_EQUAL(false, lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN)); + TEST_ASSERT_EQUAL(false, lv_obj_has_flag(obj, LV_OBJ_FLAG_CHECKABLE)); +} + +void test_observer_obj_state(void) +{ + lv_obj_t * obj = lv_obj_create(lv_scr_act()); + + /*Can bind only to int*/ + static lv_subject_t subject_wrong; + lv_subject_init_pointer(&subject_wrong, NULL); + lv_observer_t * observer = lv_obj_bind_state_if_eq(obj, &subject_wrong, LV_STATE_CHECKED, 5); + TEST_ASSERT_EQUAL_PTR(NULL, observer); + + static lv_subject_t subject; + lv_subject_init_int(&subject, 1); + + lv_obj_bind_state_if_eq(obj, &subject, LV_STATE_CHECKED, 5); + /*Should be applied immediately*/ + TEST_ASSERT_EQUAL(false, lv_obj_has_flag(obj, LV_STATE_CHECKED)); + + lv_obj_bind_state_if_not_eq(obj, &subject, LV_STATE_DISABLED, 10); + /*Should be applied immediately*/ + TEST_ASSERT_EQUAL(false, lv_obj_has_state(obj, LV_STATE_CHECKED)); + TEST_ASSERT_EQUAL(true, lv_obj_has_state(obj, LV_STATE_DISABLED)); + + lv_subject_set_int(&subject, 5); + TEST_ASSERT_EQUAL(true, lv_obj_has_state(obj, LV_STATE_CHECKED)); + TEST_ASSERT_EQUAL(true, lv_obj_has_state(obj, LV_STATE_DISABLED)); + + 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)); +} + +void test_observer_button_checked(void) +{ + lv_obj_t * obj = lv_button_create(lv_scr_act()); + lv_obj_set_size(obj, 100, 100); + lv_obj_add_flag(obj, LV_OBJ_FLAG_CHECKABLE); + lv_obj_update_layout(obj); + + /*Can bind only to int*/ + static lv_subject_t subject_wrong; + lv_subject_init_pointer(&subject_wrong, NULL); + lv_observer_t * observer = lv_button_bind_checked(obj, &subject_wrong); + TEST_ASSERT_EQUAL_PTR(NULL, observer); + + static lv_subject_t subject; + lv_subject_init_int(&subject, 1); + lv_button_bind_checked(obj, &subject); + + TEST_ASSERT_EQUAL(true, lv_obj_has_state(obj, LV_STATE_CHECKED)); + + lv_subject_set_int(&subject, 0); + TEST_ASSERT_EQUAL(false, lv_obj_has_state(obj, LV_STATE_CHECKED)); + + lv_test_mouse_click_at(10, 10); + TEST_ASSERT_EQUAL(true, lv_obj_has_state(obj, LV_STATE_CHECKED)); + TEST_ASSERT_EQUAL(1, lv_subject_get_int(&subject)); +} + +void test_observer_label_text_normal(void) +{ + lv_obj_t * obj = lv_label_create(lv_scr_act()); + + lv_observer_t * observer; + + /*Cannot bind color*/ + static lv_subject_t subject_color; + lv_subject_init_color(&subject_color, lv_color_black()); + observer = lv_label_bind_text(obj, &subject_color, NULL); + TEST_ASSERT_EQUAL_PTR(NULL, observer); + + /*Cannot bind int*/ + static lv_subject_t subject_int; + lv_subject_init_int(&subject_int, 0); + observer = lv_label_bind_text(obj, &subject_int, NULL); + TEST_ASSERT_EQUAL_PTR(NULL, observer); + + /*Bind to string*/ + static char buf[32]; + static lv_subject_t subject_string; + lv_subject_init_string(&subject_string, buf, NULL, 32, "hello"); + lv_label_bind_text(obj, &subject_string, NULL); + TEST_ASSERT_EQUAL_STRING("hello", lv_label_get_text(obj)); + + lv_subject_copy_string(&subject_string, "world"); + TEST_ASSERT_EQUAL_STRING("world", lv_label_get_text(obj)); + + /*Remove the label from the subject*/ + lv_subject_remove_all_obj(&subject_string, obj); + lv_subject_copy_string(&subject_string, "nothing"); + TEST_ASSERT_EQUAL_STRING("world", lv_label_get_text(obj)); + + /*Bind to pointer*/ + static lv_subject_t subject_pointer; + lv_subject_init_pointer(&subject_pointer, "HELLO"); + lv_label_bind_text(obj, &subject_pointer, NULL); + TEST_ASSERT_EQUAL_STRING("HELLO", lv_label_get_text(obj)); + + lv_subject_set_pointer(&subject_pointer, "WORLD"); + TEST_ASSERT_EQUAL_STRING("WORLD", lv_label_get_text(obj)); + + /*Remove the label from the subject*/ + lv_subject_remove_all_obj(&subject_pointer, obj); + lv_subject_copy_string(&subject_pointer, "NOTHING"); + TEST_ASSERT_EQUAL_STRING("WORLD", lv_label_get_text(obj)); +} + +void test_observer_label_text_formatted(void) +{ + lv_obj_t * obj = lv_label_create(lv_scr_act()); + + lv_observer_t * observer; + + /*Cannot bind color*/ + static lv_subject_t subject_color; + lv_subject_init_color(&subject_color, lv_color_black()); + observer = lv_label_bind_text(obj, &subject_color, NULL); + TEST_ASSERT_EQUAL_PTR(NULL, observer); + + /*Bind to int*/ + static lv_subject_t subject_int; + lv_subject_init_int(&subject_int, 10); + lv_label_bind_text(obj, &subject_int, "value: %d"); + TEST_ASSERT_EQUAL_STRING("value: 10", lv_label_get_text(obj)); + + lv_subject_set_int(&subject_int, -20); + TEST_ASSERT_EQUAL_STRING("value: -20", lv_label_get_text(obj)); + + /*Remove the label from the subject*/ + lv_subject_remove_all_obj(&subject_int, obj); + lv_subject_set_int(&subject_int, 100); + TEST_ASSERT_EQUAL_STRING("value: -20", lv_label_get_text(obj)); + + /*Bind to string*/ + static char buf[32]; + static lv_subject_t subject_string; + lv_subject_init_string(&subject_string, buf, NULL, 32, "hello"); + lv_label_bind_text(obj, &subject_string, "text: %s"); + TEST_ASSERT_EQUAL_STRING("text: hello", lv_label_get_text(obj)); + + lv_subject_copy_string(&subject_string, "world"); + TEST_ASSERT_EQUAL_STRING("text: world", lv_label_get_text(obj)); + + /*Remove the label from the subject*/ + lv_subject_remove_all_obj(&subject_string, obj); + lv_subject_copy_string(&subject_string, "nothing"); + TEST_ASSERT_EQUAL_STRING("text: world", lv_label_get_text(obj)); + + /*Bind to pointer*/ + static lv_subject_t subject_pointer; + lv_subject_init_pointer(&subject_pointer, "HELLO"); + lv_label_bind_text(obj, &subject_pointer, "pointer: %s"); + TEST_ASSERT_EQUAL_STRING("pointer: HELLO", lv_label_get_text(obj)); + + lv_subject_set_pointer(&subject_pointer, "WORLD"); + TEST_ASSERT_EQUAL_STRING("pointer: WORLD", lv_label_get_text(obj)); + + /*Remove the label from the subject*/ + lv_subject_remove_all_obj(&subject_pointer, obj); + lv_subject_copy_string(&subject_pointer, "NOTHING"); + TEST_ASSERT_EQUAL_STRING("pointer: WORLD", lv_label_get_text(obj)); +} + +void test_observer_arc_value(void) +{ + lv_obj_t * obj = lv_arc_create(lv_scr_act()); + + static lv_subject_t subject; + lv_subject_init_int(&subject, 30); + lv_arc_bind_value(obj, &subject); + + TEST_ASSERT_EQUAL(30, lv_arc_get_value(obj)); + + lv_subject_set_int(&subject, 40); + TEST_ASSERT_EQUAL(40, lv_arc_get_value(obj)); + + lv_obj_update_layout(obj); + lv_test_mouse_release(); + lv_test_mouse_move_to(65, 10); + lv_test_mouse_press(); + lv_test_indev_wait(100); + lv_test_mouse_release(); + + TEST_ASSERT_EQUAL(50, lv_arc_get_value(obj)); + TEST_ASSERT_EQUAL(50, lv_subject_get_int(&subject)); +} + +void test_observer_slider_value(void) +{ + lv_obj_t * obj = lv_slider_create(lv_scr_act()); + + static lv_subject_t subject; + lv_subject_init_int(&subject, 30); + lv_slider_bind_value(obj, &subject); + + TEST_ASSERT_EQUAL(30, lv_slider_get_value(obj)); + + lv_subject_set_int(&subject, 40); + TEST_ASSERT_EQUAL(40, lv_slider_get_value(obj)); + + lv_obj_update_layout(obj); + lv_test_mouse_release(); + lv_test_mouse_move_to(65, 10); + lv_test_mouse_press(); + lv_test_indev_wait(100); + lv_test_mouse_move_to(75, 10); + lv_test_mouse_press(); + lv_test_indev_wait(100); + lv_test_mouse_release(); + + TEST_ASSERT_EQUAL(29, lv_slider_get_value(obj)); + TEST_ASSERT_EQUAL(29, lv_subject_get_int(&subject)); +} + +void test_observer_roller_value(void) +{ + lv_obj_t * obj = lv_roller_create(lv_scr_act()); + + static lv_subject_t subject; + lv_subject_init_int(&subject, 1); + lv_roller_bind_value(obj, &subject); + + TEST_ASSERT_EQUAL(1, lv_roller_get_selected(obj)); + + lv_subject_set_int(&subject, 2); + TEST_ASSERT_EQUAL(2, lv_roller_get_selected(obj)); + + lv_obj_update_layout(obj); + lv_test_mouse_click_at(30, 10); + + TEST_ASSERT_EQUAL(1, lv_roller_get_selected(obj)); + TEST_ASSERT_EQUAL(1, lv_subject_get_int(&subject)); +} + +void test_observer_dropdown_value(void) +{ + lv_obj_t * obj = lv_dropdown_create(lv_scr_act()); + + static lv_subject_t subject; + lv_subject_init_int(&subject, 1); + lv_dropdown_bind_value(obj, &subject); + + TEST_ASSERT_EQUAL(1, lv_dropdown_get_selected(obj)); + + lv_subject_set_int(&subject, 2); + TEST_ASSERT_EQUAL(2, lv_dropdown_get_selected(obj)); + + lv_obj_update_layout(obj); + lv_test_mouse_click_at(30, 10); + lv_test_mouse_click_at(30, 60); + + TEST_ASSERT_EQUAL(0, lv_dropdown_get_selected(obj)); + TEST_ASSERT_EQUAL(0, lv_subject_get_int(&subject)); +} + +#endif