diff --git a/docs/src/details/auxiliary-modules/observer/observer.rst b/docs/src/details/auxiliary-modules/observer/observer.rst index ba3ce3f663..b4ed5ce62c 100644 --- a/docs/src/details/auxiliary-modules/observer/observer.rst +++ b/docs/src/details/auxiliary-modules/observer/observer.rst @@ -17,7 +17,7 @@ This implementation consists of: :Subjects: (in global memory or heap) are "logic packages", each containing the value being "observed" and its type (integer (``int32_t``), a string, a - pointer, an :cpp:type:`lv_color_t`, or a group); + pointer, an :cpp:type:`lv_color_t`, a :cpp_type:`float`, or a group); :Observers: (zero or more per Subject, always dynamically-allocated) are always attached to exactly one Subject, and provide user-defined notifications @@ -114,6 +114,7 @@ To initialize a Subject use ``lv_subject_init_(&subject, params, init_valu The following initialization functions exist, one for each of the Subject types: :Integer: void :cpp:expr:`lv_subject_init_int(subject, int_value)` +:Float: void :cpp:expr:`lv_subject_init_float(subject, float_value)` :String: void :cpp:expr:`lv_subject_init_string(subject, buf, prev_buf, buf_size, initial_string)` :Pointer: void :cpp:expr:`lv_subject_init_pointer(subject, ptr)` :Color: void :cpp:expr:`lv_subject_init_color(subject, color)` @@ -141,6 +142,7 @@ The following functions are used to get a Subject's current value: :Integer: int32_t :cpp:expr:`lv_subject_get_int(subject)` +:Float: float :cpp:expr:`lv_subject_get_float(subject)` :String: const char * :cpp:expr:`lv_subject_get_string(subject)` :Pointer: const void * :cpp:expr:`lv_subject_get_pointer(subject)` :Color: lv_color_t :cpp:expr:`lv_subject_get_color(subject)` @@ -153,6 +155,7 @@ The following functions are used to get a Subject's previous value: :Integer: int32_t :cpp:expr:`lv_subject_get_previous_int(subject)` +:Float: float :cpp:expr:`lv_subject_get_previous_float(subject)` :String: const char * :cpp:expr:`lv_subject_get_previous_string(subject)` :Pointer: const void * :cpp:expr:`lv_subject_get_previous_pointer(subject)` :Color: lv_color_t :cpp:expr:`lv_subject_get_previous_color(subject)` @@ -492,11 +495,14 @@ printf-like format specifier and be one of the following: cross-platform equivalent where ``xx`` can be ``8``, ``16``, ``32`` or ``64``, depending on the platform). -If NULL is passed for the ``format_string`` argument: +:float Subject: "%f" format specifier, or e.g. "%0.2f" to display two digits after the decimal point. + + +If ``NULL`` is passed for the ``format_string`` argument: :string or pointer Subject: Updates expect the pointer to point to a NUL-terminated string. - -:integer Subject: The Label will display an empty string (i.e. nothing). +:integer Subject: The Label will simply display the number. Equivalent to "%d". +:float Subject: The Label will display the value with "%0.1f" format string. **Example:** "%d |deg|\C" @@ -509,6 +515,9 @@ value directly. Note that this is a two-way binding (Subject <===> Widget) so a user's direct interaction with the Arc Widget updates the Subject's value and vice versa. (Requires :c:macro:`LV_USE_ARC` to be configured to ``1``.) +It support integer and float subjects. + + - :cpp:expr:`lv_arc_bind_value(arc, &subject)` @@ -520,6 +529,8 @@ value directly. Note that this is a two-way binding (Subject <===> Widget) so a user's direct interaction with the Slider Widget updates the Subject's value and vice versa. (Requires :c:macro:`LV_USE_SLIDER` to be configured to ``1``.) +It support integer and float subjects. + - :cpp:expr:`lv_slider_bind_value(slider, &subject)` @@ -531,6 +542,8 @@ value directly. Note that this is a two-way binding (Subject <===> Widget) so a user's direct interaction with the Slider Widget updates the Subject's value and vice versa. (Requires :c:macro:`LV_USE_ROLLER` to be configured to ``1``.) +It support only integer subjects. + - :cpp:expr:`lv_roller_bind_value(roller, &subject)` @@ -542,6 +555,8 @@ value directly. Note that this is a two-way binding (Subject <===> Widget) so a user's direct interaction with the Drop-Down Widget updates the Subject's value and vice versa. (Requires :c:macro:`LV_USE_DROPDOWN` to be configured to ``1``.) +It support only integer subjects. + - :cpp:expr:`lv_dropdown_bind_value(dropdown, &subject)` diff --git a/src/others/observer/lv_observer.c b/src/others/observer/lv_observer.c index ab1c27bf62..44a02da358 100644 --- a/src/others/observer/lv_observer.c +++ b/src/others/observer/lv_observer.c @@ -158,6 +158,51 @@ int32_t lv_subject_get_previous_int(lv_subject_t * subject) return subject->prev_value.num; } +#if LV_USE_FLOAT + +void lv_subject_init_float(lv_subject_t * subject, float value) +{ + lv_memzero(subject, sizeof(lv_subject_t)); + subject->type = LV_SUBJECT_TYPE_FLOAT; + subject->value.float_v = value; + subject->prev_value.float_v = value; + lv_ll_init(&(subject->subs_ll), sizeof(lv_observer_t)); +} + +void lv_subject_set_float(lv_subject_t * subject, float value) +{ + if(subject->type != LV_SUBJECT_TYPE_FLOAT) { + LV_LOG_WARN("Subject type is not LV_SUBJECT_TYPE_FLOAT"); + return; + } + + subject->prev_value.float_v = subject->value.float_v; + subject->value.float_v = value; + lv_subject_notify_if_changed(subject); +} + +float lv_subject_get_float(lv_subject_t * subject) +{ + if(subject->type != LV_SUBJECT_TYPE_FLOAT) { + LV_LOG_WARN("Subject type is not LV_SUBJECT_TYPE_FLOAT"); + return 0; + } + + return subject->value.float_v; +} + +float lv_subject_get_previous_float(lv_subject_t * subject) +{ + if(subject->type != LV_SUBJECT_TYPE_FLOAT) { + LV_LOG_WARN("Subject type is not LV_SUBJECT_TYPE_FLOAT"); + return 0; + } + + return subject->prev_value.float_v; +} + +#endif /*LV_USE_FLOAT*/ + 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)); @@ -683,6 +728,11 @@ lv_observer_t * lv_label_bind_text(lv_obj_t * obj, lv_subject_t * subject, const if(subject->type == LV_SUBJECT_TYPE_INT) { fmt = "%d"; } +#if LV_USE_FLOAT + else if(subject->type == LV_SUBJECT_TYPE_FLOAT) { + fmt = "%0.1f"; + } +#endif else if(subject->type != LV_SUBJECT_TYPE_STRING && subject->type != LV_SUBJECT_TYPE_POINTER) { LV_LOG_WARN("Incompatible subject type: %d", subject->type); return NULL; @@ -690,7 +740,7 @@ lv_observer_t * lv_label_bind_text(lv_obj_t * obj, lv_subject_t * subject, const } else { if(subject->type != LV_SUBJECT_TYPE_STRING && subject->type != LV_SUBJECT_TYPE_POINTER && - subject->type != LV_SUBJECT_TYPE_INT) { + subject->type != LV_SUBJECT_TYPE_INT && subject->type != LV_SUBJECT_TYPE_FLOAT) { LV_LOG_WARN("Incompatible subject type: %d", subject->type); return NULL; } @@ -707,7 +757,7 @@ lv_observer_t * lv_arc_bind_value(lv_obj_t * obj, lv_subject_t * subject) LV_ASSERT_NULL(subject); LV_ASSERT_NULL(obj); - if(subject->type != LV_SUBJECT_TYPE_INT) { + if(subject->type != LV_SUBJECT_TYPE_INT && subject->type != LV_SUBJECT_TYPE_FLOAT) { LV_LOG_WARN("Incompatible subject type: %d", subject->type); return NULL; } @@ -725,7 +775,7 @@ lv_observer_t * lv_slider_bind_value(lv_obj_t * obj, lv_subject_t * subject) LV_ASSERT_NULL(subject); LV_ASSERT_NULL(obj); - if(subject->type != LV_SUBJECT_TYPE_INT) { + if(subject->type != LV_SUBJECT_TYPE_INT && subject->type != LV_SUBJECT_TYPE_FLOAT) { LV_LOG_WARN("Incompatible subject type: %d", subject->type); return NULL; } @@ -937,6 +987,13 @@ static void lv_subject_notify_if_changed(lv_subject_t * subject) lv_subject_notify(subject); } break; +#if LV_USE_FLOAT + case LV_SUBJECT_TYPE_FLOAT : + if(subject->value.float_v != subject->prev_value.float_v) { + lv_subject_notify(subject); + } + break; +#endif case LV_SUBJECT_TYPE_GROUP : case LV_SUBJECT_TYPE_POINTER : /* Always notify as we don't know how to compare this */ @@ -970,6 +1027,11 @@ static void label_text_observer_cb(lv_observer_t * observer, lv_subject_t * subj case LV_SUBJECT_TYPE_INT: lv_label_set_text_fmt(observer->target, fmt, subject->value.num); break; +#if LV_USE_FLOAT + case LV_SUBJECT_TYPE_FLOAT: + lv_label_set_text_fmt(observer->target, fmt, subject->value.float_v); + break; +#endif case LV_SUBJECT_TYPE_STRING: case LV_SUBJECT_TYPE_POINTER: lv_label_set_text_fmt(observer->target, fmt, subject->value.pointer); @@ -989,12 +1051,26 @@ 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)); + if(subject->type == LV_SUBJECT_TYPE_INT) { + lv_subject_set_int(subject, lv_arc_get_value(arc)); + } +#if LV_USE_FLOAT + else { + lv_subject_set_float(subject, (float)lv_arc_get_value(arc)); + } +#endif } static void arc_value_observer_cb(lv_observer_t * observer, lv_subject_t * subject) { - lv_arc_set_value(observer->target, subject->value.num); + if(subject->type == LV_SUBJECT_TYPE_INT) { + lv_arc_set_value(observer->target, subject->value.num); + } +#if LV_USE_FLOAT + else { + lv_arc_set_value(observer->target, (int32_t)subject->value.float_v); + } +#endif } #endif /*LV_USE_ARC*/ @@ -1006,12 +1082,26 @@ 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)); + if(subject->type == LV_SUBJECT_TYPE_INT) { + lv_subject_set_int(subject, lv_slider_get_value(slider)); + } +#if LV_USE_FLOAT + else { + lv_subject_set_float(subject, (float)lv_slider_get_value(slider)); + } +#endif } static void slider_value_observer_cb(lv_observer_t * observer, lv_subject_t * subject) { - lv_slider_set_value(observer->target, subject->value.num, LV_ANIM_OFF); + if(subject->type == LV_SUBJECT_TYPE_INT) { + lv_slider_set_value(observer->target, subject->value.num, LV_ANIM_OFF); + } +#if LV_USE_FLOAT + else { + lv_slider_set_value(observer->target, (int32_t)subject->value.float_v, LV_ANIM_OFF); + } +#endif } #endif /*LV_USE_SLIDER*/ diff --git a/src/others/observer/lv_observer.h b/src/others/observer/lv_observer.h index aa092b9ce0..f5cb132e93 100644 --- a/src/others/observer/lv_observer.h +++ b/src/others/observer/lv_observer.h @@ -32,10 +32,11 @@ typedef enum { LV_SUBJECT_TYPE_INVALID = 0, /**< indicates Subject not initialized yet */ LV_SUBJECT_TYPE_NONE = 1, /**< a null value like None or NILt */ LV_SUBJECT_TYPE_INT = 2, /**< an int32_t */ - LV_SUBJECT_TYPE_POINTER = 3, /**< a void pointer */ - LV_SUBJECT_TYPE_COLOR = 4, /**< an lv_color_t */ - LV_SUBJECT_TYPE_GROUP = 5, /**< an array of Subjects */ - LV_SUBJECT_TYPE_STRING = 6, /**< a char pointer */ + LV_SUBJECT_TYPE_FLOAT = 3, /**< a float, requires `LV_USE_FLOAT 1` */ + LV_SUBJECT_TYPE_POINTER = 4, /**< a void pointer */ + LV_SUBJECT_TYPE_COLOR = 5, /**< an lv_color_t */ + LV_SUBJECT_TYPE_GROUP = 6, /**< an array of Subjects */ + LV_SUBJECT_TYPE_STRING = 7, /**< a char pointer */ } lv_subject_type_t; /** @@ -45,6 +46,9 @@ 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 */ +#if LV_USE_FLOAT + float float_v; /**< Floating point value*/ +#endif } lv_subject_value_t; /** @@ -100,6 +104,38 @@ int32_t lv_subject_get_int(lv_subject_t * subject); */ int32_t lv_subject_get_previous_int(lv_subject_t * subject); +#if LV_USE_FLOAT + +/** + * Initialize an float-type Subject. + * @param subject pointer to Subject + * @param value initial value + */ +void lv_subject_init_float(lv_subject_t * subject, float value); + +/** + * Set value of an float Subject and notify Observers. + * @param subject pointer to Subject + * @param value new value + */ +void lv_subject_set_float(lv_subject_t * subject, float value); + +/** + * Get current value of an float Subject. + * @param subject pointer to Subject + * @return current value + */ +float lv_subject_get_float(lv_subject_t * subject); + +/** + * Get previous value of an float Subject. + * @param subject pointer to Subject + * @return current value + */ +float lv_subject_get_previous_float(lv_subject_t * subject); + +#endif /*LV_USE_FLOAT*/ + /** * Initialize a string-type Subject. * @param subject pointer to Subject diff --git a/src/others/xml/lv_xml_component.c b/src/others/xml/lv_xml_component.c index 8e11547f92..5351725c88 100644 --- a/src/others/xml/lv_xml_component.c +++ b/src/others/xml/lv_xml_component.c @@ -460,6 +460,9 @@ static void process_subject_element(lv_xml_parser_state_t * state, const char * lv_subject_t * subject = lv_zalloc(sizeof(lv_subject_t)); if(lv_streq(type, "int")) lv_subject_init_int(subject, lv_xml_atoi(value)); +#if LV_USE_FLOAT + else if(lv_streq(type, "float")) lv_subject_init_float(subject, lv_xml_atof(value)); +#endif else if(lv_streq(type, "color")) lv_subject_init_color(subject, lv_xml_to_color(value)); else if(lv_streq(type, "string")) { /*Simple solution for now. Will be improved later*/ diff --git a/src/others/xml/lv_xml_utils.c b/src/others/xml/lv_xml_utils.c index 99ecb03305..459da26ea2 100644 --- a/src/others/xml/lv_xml_utils.c +++ b/src/others/xml/lv_xml_utils.c @@ -109,6 +109,7 @@ int32_t lv_xml_atoi_split(const char ** str, char delimiter) } result = result * sign; + while(*s != delimiter && *s != '\0') s++; /*Make sure to find the delimiter*/ if(*s != '\0') s++; /*Skip the delimiter*/ *str = s; @@ -120,6 +121,72 @@ int32_t lv_xml_atoi(const char * str) return lv_xml_atoi_split(&str, '\0'); } +#if LV_USE_FLOAT +float lv_xml_atof_split(const char ** str, char delimiter) +{ + const char * s = *str; + float result = 0.0f; + int sign = 1; + + /* Skip leading whitespace */ + while(*s == ' ' || *s == '\t') s++; + + /* Handle optional sign */ + if(*s == '-') { + sign = -1; + s++; + } + else if(*s == '+') { + s++; + } + + /* Convert the integer part */ + while(*s != delimiter && *s != '.' && *s != '\0') { + if(*s >= '0' && *s <= '9') { + float digit = *s - '0'; + result = result * 10.0f + digit; + s++; + } + else { + break; /* Non-digit character */ + } + } + + /* Convert the fractional part */ + if(*s == '.') { + s++; /* Skip the decimal point */ + float fraction = 0.0f; + float divisor = 10.0f; + + while(*s != delimiter && *s != '\0') { + if(*s >= '0' && *s <= '9') { + float digit = *s - '0'; + fraction += digit / divisor; + divisor *= 10.0f; + s++; + } + else { + break; /* Non-digit character */ + } + } + result += fraction; + } + + result = result * sign; + while(*s != delimiter && *s != '\0') s++; /*Make sure to find the delimiter*/ + + if(*s != '\0') s++; /*Skip the delimiter*/ + *str = s; + return result; +} + + +float lv_xml_atof(const char * str) +{ + return lv_xml_atof_split(&str, '\0'); +} +#endif + int32_t lv_xml_strtol(const char * str, char ** endptr, int32_t base) { const char * s = str; diff --git a/src/others/xml/lv_xml_utils.h b/src/others/xml/lv_xml_utils.h index d5bd5ec34c..31bb2df969 100644 --- a/src/others/xml/lv_xml_utils.h +++ b/src/others/xml/lv_xml_utils.h @@ -26,7 +26,6 @@ const char * lv_xml_get_value_of(const char ** attrs, const char * name); int32_t lv_xml_atoi(const char * str); - /** * Convert sections of a string to int. * The end of the string is indicated by the `delimiter`. @@ -36,6 +35,19 @@ int32_t lv_xml_atoi(const char * str); */ int32_t lv_xml_atoi_split(const char ** str, char delimiter); +#if LV_USE_FLOAT +float lv_xml_atof(const char * str); + +/** + * Convert sections of a string to float. + * The end of the string is indicated by the `delimiter`. + * @param str pointer to a string, it will point to the character after the delimiter + * @param delimiter a character to indicate the end of the float + * @return the float before the next delimiter + */ +float lv_xml_atof_split(const char ** str, char delimiter); +#endif + lv_color_t lv_xml_to_color(const char * str); /** diff --git a/tests/ref_imgs/xml/lv_label.png b/tests/ref_imgs/xml/lv_label.png index 38704a98f8..b3d3f367db 100644 Binary files a/tests/ref_imgs/xml/lv_label.png and b/tests/ref_imgs/xml/lv_label.png differ diff --git a/tests/ref_imgs_vg_lite/xml/lv_label.png b/tests/ref_imgs_vg_lite/xml/lv_label.png index 7853c079c5..0887bc5db8 100644 Binary files a/tests/ref_imgs_vg_lite/xml/lv_label.png and b/tests/ref_imgs_vg_lite/xml/lv_label.png differ diff --git a/tests/src/test_cases/test_observer.c b/tests/src/test_cases/test_observer.c index 66180392bc..5eb990fa58 100644 --- a/tests/src/test_cases/test_observer.c +++ b/tests/src/test_cases/test_observer.c @@ -192,6 +192,52 @@ void test_observer_int(void) lv_observer_remove(basic_observer); } +void test_observer_float(void) +{ + static lv_subject_t subject; + lv_subject_init_float(&subject, 5.25); + lv_observer_t * basic_observer = + lv_subject_add_observer(&subject, observer_basic, NULL); + + TEST_ASSERT_EQUAL_FLOAT(5.25, lv_subject_get_float(&subject)); + TEST_ASSERT_EQUAL_FLOAT(5.25, lv_subject_get_previous_float(&subject)); + TEST_ASSERT_EQUAL(1, observer_called); + + lv_subject_set_float(&subject, 10.5); + TEST_ASSERT_EQUAL_FLOAT(10.5, lv_subject_get_float(&subject)); + TEST_ASSERT_EQUAL_FLOAT(5.25, lv_subject_get_previous_float(&subject)); + TEST_ASSERT_EQUAL(2, observer_called); + + lv_subject_set_float(&subject, 15.75); + TEST_ASSERT_EQUAL_FLOAT(15.75, lv_subject_get_float(&subject)); + TEST_ASSERT_EQUAL_FLOAT(10.5, lv_subject_get_previous_float(&subject)); + TEST_ASSERT_EQUAL(3, observer_called); + + /* Observer shouldn't be called if value is the same */ + lv_subject_set_float(&subject, 15.75); + TEST_ASSERT_EQUAL_FLOAT(15.75, lv_subject_get_float(&subject)); + TEST_ASSERT_EQUAL_FLOAT(15.75, lv_subject_get_previous_float(&subject)); + TEST_ASSERT_EQUAL(3, observer_called); + + /*Ignore incorrect types*/ + lv_subject_set_pointer(&subject, NULL); + TEST_ASSERT_EQUAL_FLOAT(15.75, lv_subject_get_float(&subject)); + TEST_ASSERT_EQUAL_FLOAT(15.75, lv_subject_get_previous_float(&subject)); + TEST_ASSERT_EQUAL(3, observer_called); + + lv_subject_set_color(&subject, lv_color_black()); + TEST_ASSERT_EQUAL_FLOAT(15.75, lv_subject_get_float(&subject)); + TEST_ASSERT_EQUAL_FLOAT(15.75, lv_subject_get_previous_float(&subject)); + TEST_ASSERT_EQUAL(3, observer_called); + + lv_subject_copy_string(&subject, "hello"); + TEST_ASSERT_EQUAL_FLOAT(15.75, lv_subject_get_float(&subject)); + TEST_ASSERT_EQUAL_FLOAT(15.75, lv_subject_get_previous_float(&subject)); + TEST_ASSERT_EQUAL(3, observer_called); + + lv_observer_remove(basic_observer); +} + void test_observer_string(void) { char buf_current[32]; @@ -756,6 +802,17 @@ void test_observer_label_text_normal(void) observer = lv_label_bind_text(obj, &subject_int, NULL); TEST_ASSERT_EQUAL_STRING("10", lv_label_get_text(obj)); + /*Bind it with "%0.1f" if NULL is passed*/ + static lv_subject_t subject_float; + lv_subject_init_float(&subject_float, 10.5); + observer = lv_label_bind_text(obj, &subject_float, NULL); + TEST_ASSERT_EQUAL_STRING("10.5", lv_label_get_text(obj)); + + /*Bind it with "%0.1f" if NULL is passed*/ + lv_subject_set_float(&subject_float, 81.5); + observer = lv_label_bind_text(obj, &subject_float, "Value: %0.2f"); + TEST_ASSERT_EQUAL_STRING("Value: 81.50", lv_label_get_text(obj)); + /*Bind to string*/ static char buf[32]; static lv_subject_t subject_string; diff --git a/tests/src/test_cases/xml/test_xml_label.c b/tests/src/test_cases/xml/test_xml_label.c index 31da8559fe..98b5e19d94 100644 --- a/tests/src/test_cases/xml/test_xml_label.c +++ b/tests/src/test_cases/xml/test_xml_label.c @@ -35,6 +35,7 @@ void test_xml_label_with_attrs(void) lv_subject_init_int(&s1, 20); lv_xml_register_subject(NULL, "s1", &s1); + const char * label2_attrs[] = { "bind_text", "s1", "bind_text-fmt", "We have %d users", @@ -45,6 +46,20 @@ void test_xml_label_with_attrs(void) lv_xml_create(scr, "lv_label", label2_attrs); + static lv_subject_t s2; + lv_subject_init_float(&s2, 12.3f); + lv_xml_register_subject(NULL, "s2", &s2); + + const char * label3_attrs[] = { + "bind_text", "s2", + "bind_text-fmt", "We have measured: %0.3f mW", + "y", "30", + "x", "5", + NULL, NULL, + }; + + lv_xml_create(scr, "lv_label", label3_attrs); + TEST_ASSERT_EQUAL_SCREENSHOT("xml/lv_label.png"); }