diff --git a/docs/src/details/common-widget-features/styles/local_styles.rst b/docs/src/details/common-widget-features/styles/local_styles.rst index 7ea41cc0c0..d5469cc57a 100644 --- a/docs/src/details/common-widget-features/styles/local_styles.rst +++ b/docs/src/details/common-widget-features/styles/local_styles.rst @@ -30,6 +30,22 @@ To set a local property use functions like lv_obj_set_style_bg_color(slider, lv_color_red(), LV_PART_INDICATOR | LV_STATE_FOCUSED); +Binding Local Styles +******************** + +By using :cpp:expr:`lv_obj_bind_style_prop`, it's possible to bind a style property +to a :ref:`Subject `\ 's value. + +It's a great way to map every slider's color or opacity to a subject and control it +externally. + +For example: + +.. code-block:: c + + lv_obj_bind_style_prop(slider1, LV_STYLE_BG_OPA, LV_PART_MAIN, &subject_bg_opa); + lv_obj_bind_style_prop(slider1, LV_STYLE_BG_COLOR, LV_PART_INDICATOR, &subject_bg_color); + .. Hyperlinks diff --git a/docs/src/details/common-widget-features/styles/style_sheets.rst b/docs/src/details/common-widget-features/styles/style_sheets.rst index b6f2472407..0a67df65c7 100644 --- a/docs/src/details/common-widget-features/styles/style_sheets.rst +++ b/docs/src/details/common-widget-features/styles/style_sheets.rst @@ -160,3 +160,25 @@ notified. There are 3 options to do this: is ``NULL`` all Widgets will be notified about a style change. +Binding Styles +************** + +By using :cpp:expr:`lv_obj_bind_style`, it's possible to add a style to a Widget +but enable it only if a :ref:`Subject `'s value is equal to +a reference value. + +It's a great way to implement a light/dark theme switch by normally adding the styles +for the light theme, and binding only a few styles for the dark theme to change +only a few colors if, e.g., a ``dark_theme`` subject is ``1``. + +For example: + +.. code-block:: c + + /*Style for the light theme*/ + lv_obj_add_style(btn, &style_btn, LV_PART_MAIN); + lv_obj_add_style(btn, &style_btn_pressed, LV_STATE_PRESSED); + + /*Style for the dark theme changing only a few colors*/ + lv_obj_bind_style(btn, &style_btn_dark, LV_PART_MAIN, &dark_theme_subject, 1); + lv_obj_bind_style(btn, &style_btn_pressed_dark, LV_STATE_PRESSED, &dark_theme_subject, 1); diff --git a/docs/src/details/xml/ui_elements/styles.rst b/docs/src/details/xml/ui_elements/styles.rst index 019bee1526..3ae4e1a954 100644 --- a/docs/src/details/xml/ui_elements/styles.rst +++ b/docs/src/details/xml/ui_elements/styles.rst @@ -36,7 +36,7 @@ Styles can be referenced like this in the ````: As shown in the example, parts and states can be set using ``selector``. -Style binding +Style Binding ************* Instead of directly adding styles to the UI elements it's also possible to add them conditionally @@ -78,6 +78,23 @@ Local styles can be used directly, for example: Selectors are not supported for local style properties yet. + +Local Style Binding +******************* + +Instead of setting local style properties directly, it's also possible to bind style properties +to :ref:`Subject ` values: + +.. code-block:: xml + + + + + + + + + Gradients ********* diff --git a/docs/src/details/xml/xml/syntax.rst b/docs/src/details/xml/xml/syntax.rst index 55a0294c13..dd70b902f4 100644 --- a/docs/src/details/xml/xml/syntax.rst +++ b/docs/src/details/xml/xml/syntax.rst @@ -70,6 +70,8 @@ The following simple built-in types are supported: :scale_1/256: Scale/Zoom, where 256 means 100%, 128 means 50%, 512 means 200% etc. +:style_prop: A style property, for example ``"bg_opa"`` meaning :cpp:enumerator:`LV_STYLE_BG_OPA`. + Name-based types ---------------- diff --git a/src/core/lv_obj_style.c b/src/core/lv_obj_style.c index 91cb91e8ee..762c21e8f0 100644 --- a/src/core/lv_obj_style.c +++ b/src/core/lv_obj_style.c @@ -15,6 +15,8 @@ #include "../misc/lv_color.h" #include "../stdlib/lv_string.h" #include "../core/lv_global.h" +#include "lv_observer_private.h" + /********************* * DEFINES *********************/ @@ -36,6 +38,17 @@ typedef struct { lv_style_value_t end_value; } trans_t; +typedef struct { + const lv_style_t * style; + lv_style_selector_t selector; + int32_t value; +} bind_style_t; + +typedef struct { + lv_style_prop_t prop; + lv_style_selector_t selector; +} bind_style_prop_t; + /********************** * GLOBAL PROTOTYPES **********************/ @@ -60,6 +73,10 @@ static void fade_in_anim_completed(lv_anim_t * a); static bool style_has_flag(const lv_style_t * style, uint32_t flag); static lv_style_res_t get_selector_style_prop(const lv_obj_t * obj, lv_style_selector_t selector, lv_style_prop_t prop, lv_style_value_t * value_act); +#if LV_USE_OBSERVER + static void bind_style_observer_cb(lv_observer_t * observer, lv_subject_t * subject); + static void bind_style_prop_observer_cb(lv_observer_t * observer, lv_subject_t * subject); +#endif /********************** * STATIC VARIABLES @@ -702,6 +719,68 @@ lv_color32_t lv_obj_get_style_recolor_recursive(const lv_obj_t * obj, lv_part_t return result; } +#if LV_USE_OBSERVER + +lv_observer_t * lv_obj_bind_style(lv_obj_t * obj, const lv_style_t * style, lv_style_selector_t selector, + lv_subject_t * subject, int32_t ref_value) +{ + LV_ASSERT_NULL(subject); + LV_ASSERT_NULL(obj); + + if(subject->type != LV_SUBJECT_TYPE_INT) { + LV_LOG_WARN("Subject type must be `int` (was %d)", subject->type); + return NULL; + } + + lv_obj_add_style(obj, style, selector); + + bind_style_t * p = lv_malloc(sizeof(bind_style_t)); + LV_ASSERT_MALLOC(p); + if(p == NULL) { + LV_LOG_WARN("Out of memory"); + return NULL; + } + + p->style = style; + p->selector = selector; + p->value = ref_value; + + lv_observer_t * observable = lv_subject_add_observer_obj(subject, bind_style_observer_cb, obj, p); + observable->auto_free_user_data = 1; + return observable; +} + +lv_observer_t * lv_obj_bind_style_prop(lv_obj_t * obj, lv_style_prop_t prop, lv_style_selector_t selector, + lv_subject_t * subject) +{ + LV_ASSERT_NULL(subject); + LV_ASSERT_NULL(obj); + + if(subject->type != LV_SUBJECT_TYPE_INT && subject->type != LV_SUBJECT_TYPE_COLOR && + subject->type != LV_SUBJECT_TYPE_POINTER) { + LV_LOG_WARN("Subject type must be `int`, `color`, or `pointer` (was %"LV_PRIu32")", subject->type); + return NULL; + } + + bind_style_prop_t * p = lv_malloc(sizeof(bind_style_prop_t)); + LV_ASSERT_MALLOC(p); + if(p == NULL) { + LV_LOG_WARN("Out of memory"); + return NULL; + } + + p->prop = prop; + p->selector = selector; + + lv_observer_t * observable = lv_subject_add_observer_obj(subject, bind_style_prop_observer_cb, obj, p); + observable->auto_free_user_data = 1; + return observable; +} + + +#endif /*LV_USE_OBSERVER*/ + + /********************** * STATIC FUNCTIONS **********************/ @@ -1217,3 +1296,32 @@ static lv_style_res_t get_selector_style_prop(const lv_obj_t * obj, lv_style_sel return LV_STYLE_RES_NOT_FOUND; } + +#if LV_USE_OBSERVER + +static void bind_style_observer_cb(lv_observer_t * observer, lv_subject_t * subject) +{ + bind_style_t * p = observer->user_data; + + int32_t v = lv_subject_get_int(subject); + bool dis = (v != p->value); + lv_obj_style_set_disabled(observer->target, p->style, p->selector, dis); +} + +static void bind_style_prop_observer_cb(lv_observer_t * observer, lv_subject_t * subject) +{ + bind_style_prop_t * p = observer->user_data; + + lv_style_value_t style_v; + if(subject->type == LV_SUBJECT_TYPE_INT) style_v.num = lv_subject_get_int(subject); + else if(subject->type == LV_SUBJECT_TYPE_COLOR) style_v.color = lv_subject_get_color(subject); + else if(subject->type == LV_SUBJECT_TYPE_POINTER) style_v.ptr = lv_subject_get_pointer(subject); + else { + LV_LOG_WARN("Not supported subject type (%"LV_PRIu32")", subject->type); + return; + } + + lv_obj_set_local_style_prop(observer->target, p->prop, style_v, p->selector); +} + +#endif /*LV_USE_OBSERVER*/ diff --git a/src/core/lv_obj_style.h b/src/core/lv_obj_style.h index a5306d2440..7f04c383cf 100644 --- a/src/core/lv_obj_style.h +++ b/src/core/lv_obj_style.h @@ -394,6 +394,32 @@ lv_color32_t lv_obj_style_apply_recolor(const lv_obj_t * obj, lv_part_t part, lv */ lv_color32_t lv_obj_get_style_recolor_recursive(const lv_obj_t * obj, lv_part_t part); +#if LV_USE_OBSERVER +/** + * Disable a style if a subject's value is not equal to a reference value + * @param obj pointer to Widget + * @param style pointer to a style + * @param selector pointer to a selector + * @param subject pointer to Subject + * @param ref_value reference value to compare Subject's value with + * @return pointer to newly-created Observer + */ +lv_observer_t * lv_obj_bind_style(lv_obj_t * obj, const lv_style_t * style, lv_style_selector_t selector, + lv_subject_t * subject, int32_t ref_value); + +/** + * Connect a subject's value to a style property of a widget. + * @param obj pointer to a Widget + * @param prop a style property + * @param selector a selector for which the property should be added, e.g. `LV_PART_KNOB | LV_STATE_PRESSED` + * @param subject pointer a Subject to which value the property should be bound + * @return pointer to newly-created Observer + */ +lv_observer_t * lv_obj_bind_style_prop(lv_obj_t * obj, lv_style_prop_t prop, lv_style_selector_t selector, + lv_subject_t * subject); + +#endif + /********************** * MACROS **********************/ diff --git a/src/core/lv_observer.c b/src/core/lv_observer.c index 47cb5ff6b0..8ecfd15bfb 100644 --- a/src/core/lv_observer.c +++ b/src/core/lv_observer.c @@ -38,12 +38,6 @@ typedef struct { flag_cond_t cond : 3; } flag_and_cond_t; -typedef struct { - const lv_style_t * style; - lv_style_selector_t selector; - int32_t value; -} bind_style_t; - typedef struct { lv_subject_t * subject; int32_t value; @@ -77,7 +71,6 @@ static void group_notify_cb(lv_observer_t * observer, lv_subject_t * subject); 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, flag_cond_t cond); -static void bind_style_observer_cb(lv_observer_t * observer, lv_subject_t * subject); static void obj_flag_observer_cb(lv_observer_t * observer, lv_subject_t * subject); static void obj_state_observer_cb(lv_observer_t * observer, lv_subject_t * subject); static void obj_value_changed_event_cb(lv_event_t * e); @@ -734,34 +727,6 @@ void lv_obj_add_subject_set_string_event(lv_obj_t * obj, lv_subject_t * subject, lv_obj_add_event_cb(obj, subject_set_string_free_user_data_event_cb, LV_EVENT_DELETE, user_data); } -lv_observer_t * lv_obj_bind_style(lv_obj_t * obj, const lv_style_t * style, lv_style_selector_t selector, - lv_subject_t * subject, int32_t ref_value) -{ - LV_ASSERT_NULL(subject); - LV_ASSERT_NULL(obj); - - if(subject->type != LV_SUBJECT_TYPE_INT) { - LV_LOG_WARN("Subject type must be `int` (was %d)", subject->type); - return NULL; - } - - lv_obj_add_style(obj, style, selector); - - bind_style_t * p = lv_malloc(sizeof(bind_style_t)); - LV_ASSERT_MALLOC(p); - if(p == NULL) { - LV_LOG_WARN("Out of memory"); - return NULL; - } - - p->style = style; - p->selector = selector; - p->value = ref_value; - - lv_observer_t * observable = lv_subject_add_observer_obj(subject, bind_style_observer_cb, obj, p); - observable->auto_free_user_data = 1; - return observable; -} lv_observer_t * lv_obj_bind_flag_if_eq(lv_obj_t * obj, lv_subject_t * subject, lv_obj_flag_t flag, int32_t ref_value) { @@ -1002,15 +967,6 @@ static lv_observer_t * bind_to_bitfield(lv_subject_t * subject, lv_obj_t * obj, return observable; } -static void bind_style_observer_cb(lv_observer_t * observer, lv_subject_t * subject) -{ - bind_style_t * p = observer->user_data; - - int32_t current_v = lv_subject_get_int(subject); - bool dis = current_v != p->value; - lv_obj_style_set_disabled(observer->target, p->style, p->selector, dis); -} - static void obj_flag_observer_cb(lv_observer_t * observer, lv_subject_t * subject) { flag_and_cond_t * p = observer->user_data; diff --git a/src/core/lv_observer.h b/src/core/lv_observer.h index 9f4b75db9a..95538da672 100644 --- a/src/core/lv_observer.h +++ b/src/core/lv_observer.h @@ -15,6 +15,7 @@ extern "C" { *********************/ #include "lv_obj.h" + #if LV_USE_OBSERVER /********************* @@ -54,7 +55,7 @@ typedef union { /** * The Subject (an observable value) */ -typedef struct { +struct _lv_subject_t { lv_ll_t subs_ll; /**< Subscribers */ lv_subject_value_t value; /**< Current value */ lv_subject_value_t prev_value; /**< Previous value */ @@ -65,7 +66,7 @@ typedef struct { uint32_t size : 24; /**< String buffer size or group length */ uint32_t notify_restart_query : 1; /**< If an Observer was deleted during notification, * start notifying from the beginning. */ -} lv_subject_t; +}; /** * Callback called to notify Observer that Subject's value has changed @@ -449,18 +450,6 @@ void lv_obj_add_subject_set_float_event(lv_obj_t * obj, lv_subject_t * subject, void lv_obj_add_subject_set_string_event(lv_obj_t * obj, lv_subject_t * subject, lv_event_code_t trigger, const char * value); -/** - * Disable a style if a subject's value is not equal to a reference value - * @param obj pointer to Widget - * @param style pointer to a style - * @param selector pointer to a selector - * @param subject pointer to Subject - * @param ref_value reference value to compare Subject's value with - * @return pointer to newly-created Observer - */ -lv_observer_t * lv_obj_bind_style(lv_obj_t * obj, const lv_style_t * style, lv_style_selector_t selector, - lv_subject_t * subject, int32_t ref_value); - /** * Set Widget's flag(s) if an integer Subject's value is equal to a reference value, clear flag otherwise. * @param obj pointer to Widget diff --git a/src/misc/lv_types.h b/src/misc/lv_types.h index c27d45ae81..a27d8dfd5f 100644 --- a/src/misc/lv_types.h +++ b/src/misc/lv_types.h @@ -282,8 +282,8 @@ typedef struct _lv_gltf_t lv_gltf_t; typedef struct _lv_gltf_model_t lv_gltf_model_t; +typedef struct _lv_subject_t lv_subject_t; typedef struct _lv_observer_t lv_observer_t; - typedef struct _lv_subject_increment_dsc_t lv_subject_increment_dsc_t; typedef struct _lv_monkey_config_t lv_monkey_config_t; diff --git a/src/xml/lv_xml.c b/src/xml/lv_xml.c index 32f36117c5..eb2d72d977 100644 --- a/src/xml/lv_xml.c +++ b/src/xml/lv_xml.c @@ -237,6 +237,7 @@ void lv_xml_init(void) lv_obj_xml_play_timeline_event_apply); lv_xml_register_widget("lv_obj-bind_style", lv_obj_xml_bind_style_create, lv_obj_xml_bind_style_apply); + lv_xml_register_widget("lv_obj-bind_style_prop", lv_obj_xml_bind_style_prop_create, lv_obj_xml_bind_style_prop_apply); lv_xml_register_widget("lv_obj-bind_flag_if_eq", lv_obj_xml_bind_flag_create, lv_obj_xml_bind_flag_apply); lv_xml_register_widget("lv_obj-bind_flag_if_not_eq", lv_obj_xml_bind_flag_create, lv_obj_xml_bind_flag_apply); lv_xml_register_widget("lv_obj-bind_flag_if_gt", lv_obj_xml_bind_flag_create, lv_obj_xml_bind_flag_apply); diff --git a/src/xml/parsers/lv_xml_obj_parser.c b/src/xml/parsers/lv_xml_obj_parser.c index 79fef67a07..7bd497a671 100644 --- a/src/xml/parsers/lv_xml_obj_parser.c +++ b/src/xml/parsers/lv_xml_obj_parser.c @@ -492,6 +492,43 @@ void lv_obj_xml_bind_style_apply(lv_xml_parser_state_t * state, const char ** at lv_obj_bind_style(item, &xml_style->style, selector, subject, ref_value); } + +void * lv_obj_xml_bind_style_prop_create(lv_xml_parser_state_t * state, const char ** attrs) +{ + LV_UNUSED(attrs); + void * item = lv_xml_state_get_parent(state); + return item; +} + +void lv_obj_xml_bind_style_prop_apply(lv_xml_parser_state_t * state, const char ** attrs) +{ + const char * prop_str = lv_xml_get_value_of(attrs, "prop"); + if(prop_str == NULL) { + LV_LOG_WARN("`prop` is missing in lv_obj bind_style_prop"); + return; + } + lv_style_prop_t prop = lv_xml_style_prop_to_enum(prop_str); + + const char * subject_str = lv_xml_get_value_of(attrs, "subject"); + + if(subject_str == NULL) { + LV_LOG_WARN("`subject` is missing in lv_obj bind_style_prop"); + return; + } + + lv_subject_t * subject = lv_xml_get_subject(&state->scope, subject_str); + if(subject == NULL) { + LV_LOG_WARN("Subject `%s` doesn't exist in lv_obj bind_style_prop", subject_str); + return; + } + + const char * selector_str = lv_xml_get_value_of(attrs, "selector"); + lv_style_selector_t selector = lv_xml_style_selector_text_to_enum(selector_str); + + void * item = lv_xml_state_get_parent(state); + lv_obj_bind_style_prop(item, prop, selector, subject); +} + void * lv_obj_xml_bind_flag_create(lv_xml_parser_state_t * state, const char ** attrs) { LV_UNUSED(attrs); diff --git a/src/xml/parsers/lv_xml_obj_parser.h b/src/xml/parsers/lv_xml_obj_parser.h index 618fe86a1f..706138596e 100644 --- a/src/xml/parsers/lv_xml_obj_parser.h +++ b/src/xml/parsers/lv_xml_obj_parser.h @@ -51,6 +51,9 @@ void lv_obj_xml_subject_increment_apply(lv_xml_parser_state_t * state, const cha void * lv_obj_xml_bind_style_create(lv_xml_parser_state_t * state, const char ** attrs); void lv_obj_xml_bind_style_apply(lv_xml_parser_state_t * state, const char ** attrs); +void * lv_obj_xml_bind_style_prop_create(lv_xml_parser_state_t * state, const char ** attrs); +void lv_obj_xml_bind_style_prop_apply(lv_xml_parser_state_t * state, const char ** attrs); + void * lv_obj_xml_bind_flag_create(lv_xml_parser_state_t * state, const char ** attrs); void lv_obj_xml_bind_flag_apply(lv_xml_parser_state_t * state, const char ** attrs); diff --git a/tests/src/test_cases/xml/test_xml_style.c b/tests/src/test_cases/xml/test_xml_style.c index dfeda17388..458adeabb4 100644 --- a/tests/src/test_cases/xml/test_xml_style.c +++ b/tests/src/test_cases/xml/test_xml_style.c @@ -208,4 +208,40 @@ void test_xml_style_binding(void) lv_subject_set_int(subject, 5); TEST_ASSERT_EQUAL_COLOR(lv_color_hex(0xff0000), lv_obj_get_style_bg_color(obj, LV_PART_SCROLLBAR)); } + +void test_xml_style_prop_binding(void) +{ + const char * comp1_xml = { + "" + " " + " " + " " + " " + " " + " " + " " + " " + "" + }; + + lv_xml_register_component_from_data("comp1", comp1_xml); + + lv_obj_t * obj = lv_xml_create(lv_screen_active(), "comp1", NULL); + lv_obj_add_state(obj, LV_STATE_CHECKED); + lv_test_wait(1000); /*Wait for transitions*/ + + TEST_ASSERT_EQUAL_INT(128, lv_obj_get_style_bg_opa(obj, LV_PART_SCROLLBAR | LV_STATE_CHECKED)); + TEST_ASSERT_EQUAL_COLOR(lv_color_hex(0xff0000), lv_obj_get_style_bg_color(obj, LV_PART_SCROLLBAR)); + + lv_subject_t * subject1 = lv_xml_get_subject(lv_xml_component_get_scope("comp1"), "subject1"); + lv_subject_t * subject2 = lv_xml_get_subject(lv_xml_component_get_scope("comp1"), "subject2"); + + lv_subject_set_int(subject1, 20); + lv_subject_set_color(subject2, lv_color_hex(0xff00ff)); + lv_test_wait(1000); /*Wait for transitions*/ + + TEST_ASSERT_EQUAL_INT(20, lv_obj_get_style_bg_opa(obj, LV_PART_SCROLLBAR | LV_STATE_CHECKED)); + TEST_ASSERT_EQUAL_COLOR(lv_color_hex(0xff00ff), lv_obj_get_style_bg_color(obj, LV_PART_SCROLLBAR | LV_STATE_CHECKED)); +} + #endif diff --git a/xmls/lv_obj.xml b/xmls/lv_obj.xml index 1cec1b2977..6d2efc4216 100644 --- a/xmls/lv_obj.xml +++ b/xmls/lv_obj.xml @@ -60,6 +60,12 @@ Example + + + + + +