From 8ae894ebd4c6fda92dcd4e11d30483525bd9230b Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Tue, 17 Aug 2021 06:33:55 -0400 Subject: [PATCH] feat(switch) add smooth animation when changing state (#2442) * feat(switch) add smooth animation when changing state * refactor(switch) improve code quality for animation feature * refactor(switch) flatten animation structure into widget Co-authored-by: HX2003 --- src/extra/themes/default/lv_theme_default.c | 8 + src/widgets/lv_switch.c | 158 ++++++++++++++++---- src/widgets/lv_switch.h | 6 +- 3 files changed, 135 insertions(+), 37 deletions(-) diff --git a/src/extra/themes/default/lv_theme_default.c b/src/extra/themes/default/lv_theme_default.c index 977ad1ff1..d97a94848 100644 --- a/src/extra/themes/default/lv_theme_default.c +++ b/src/extra/themes/default/lv_theme_default.c @@ -74,6 +74,7 @@ typedef struct { lv_style_t transition_delayed; lv_style_t transition_normal; lv_style_t anim; + lv_style_t anim_fast; /*Parts*/ lv_style_t knob; @@ -380,6 +381,9 @@ static void style_init(void) style_init_reset(&styles->anim); lv_style_set_anim_time(&styles->anim, 200); + style_init_reset(&styles->anim_fast); + lv_style_set_anim_time(&styles->anim_fast, 120); + #if LV_USE_ARC style_init_reset(&styles->arc_indic); lv_style_set_arc_color(&styles->arc_indic, color_grey); @@ -772,6 +776,7 @@ static void theme_apply(lv_theme_t * th, lv_obj_t * obj) else if(lv_obj_check_type(obj, &lv_switch_class)) { lv_obj_add_style(obj, &styles->bg_color_grey, 0); lv_obj_add_style(obj, &styles->circle, 0); + lv_obj_add_style(obj, &styles->anim_fast, 0); lv_obj_add_style(obj, &styles->disabled, LV_STATE_DISABLED); lv_obj_add_style(obj, &styles->outline_primary, LV_STATE_FOCUS_KEY); lv_obj_add_style(obj, &styles->bg_color_primary, LV_PART_INDICATOR | LV_STATE_CHECKED); @@ -781,6 +786,9 @@ static void theme_apply(lv_theme_t * th, lv_obj_t * obj) lv_obj_add_style(obj, &styles->bg_color_white, LV_PART_KNOB); lv_obj_add_style(obj, &styles->switch_knob, LV_PART_KNOB); lv_obj_add_style(obj, &styles->disabled, LV_PART_KNOB | LV_STATE_DISABLED); + + lv_obj_add_style(obj, &styles->transition_normal, LV_PART_INDICATOR | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->transition_normal, LV_PART_INDICATOR); } #endif diff --git a/src/widgets/lv_switch.c b/src/widgets/lv_switch.c index 0a4b55bcf..027fd98af 100644 --- a/src/widgets/lv_switch.c +++ b/src/widgets/lv_switch.c @@ -10,13 +10,9 @@ #if LV_USE_SWITCH != 0 -/*Testing of dependencies*/ -#if LV_USE_SLIDER == 0 - #error "lv_sw: lv_slider is required. Enable it in lv_conf.h (LV_USE_SLIDER 1)" -#endif - #include "../misc/lv_assert.h" #include "../misc/lv_math.h" +#include "../misc/lv_anim.h" #include "../core/lv_indev.h" #include "../core/lv_disp.h" #include "lv_img.h" @@ -26,6 +22,17 @@ *********************/ #define MY_CLASS &lv_switch_class +#define LV_SWITCH_IS_ANIMATING(sw) (((sw)->anim_state) != LV_SWITCH_ANIM_STATE_INV) + +/** Switch animation start value. (Not the real value of the switch just indicates process animation)*/ +#define LV_SWITCH_ANIM_STATE_START 0 + +/** Switch animation end value. (Not the real value of the switch just indicates process animation)*/ +#define LV_SWITCH_ANIM_STATE_END 256 + +/** Mark no animation is in progress*/ +#define LV_SWITCH_ANIM_STATE_INV -1 + /********************** * TYPEDEFS **********************/ @@ -34,14 +41,19 @@ * STATIC PROTOTYPES **********************/ static void lv_switch_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_switch_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); static void lv_switch_event(const lv_obj_class_t * class_p, lv_event_t * e); static void draw_main(lv_event_t * e); +static void lv_switch_anim_exec_cb(void * sw, int32_t value); +static void lv_switch_trigger_anim(lv_obj_t * obj); +static void lv_switch_anim_ready(lv_anim_t * a); /********************** * STATIC VARIABLES **********************/ const lv_obj_class_t lv_switch_class = { .constructor_cb = lv_switch_constructor, + .destructor_cb = lv_switch_destructor, .event_cb = lv_switch_event, .width_def = (4 * LV_DPI_DEF) / 10, .height_def = (4 * LV_DPI_DEF) / 17, @@ -75,13 +87,24 @@ static void lv_switch_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj LV_UNUSED(class_p); LV_TRACE_OBJ_CREATE("begin"); - lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE); - lv_obj_add_flag(obj, LV_OBJ_FLAG_CHECKABLE); - lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS); + lv_switch_t * sw = (lv_switch_t *)obj; - LV_TRACE_OBJ_CREATE("finished"); + sw->anim_state = LV_SWITCH_ANIM_STATE_INV; + + lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_add_flag(obj, LV_OBJ_FLAG_CHECKABLE); + lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS); + + LV_TRACE_OBJ_CREATE("finished"); } +static void lv_switch_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_switch_t * sw = (lv_switch_t *)obj; + + lv_anim_del(sw, NULL); +} static void lv_switch_event(const lv_obj_class_t * class_p, lv_event_t * e) { @@ -111,7 +134,8 @@ static void lv_switch_event(const lv_obj_class_t * class_p, lv_event_t * e) *s = LV_MAX(*s, knob_size); *s = LV_MAX(*s, lv_obj_calculate_ext_draw_size(obj, LV_PART_INDICATOR)); } - else if(code == LV_EVENT_CLICKED) { + else if(code == LV_EVENT_VALUE_CHANGED) { + lv_switch_trigger_anim(obj); lv_obj_invalidate(obj); } else if(code == LV_EVENT_DRAW_MAIN) { @@ -122,6 +146,8 @@ static void lv_switch_event(const lv_obj_class_t * class_p, lv_event_t * e) static void draw_main(lv_event_t * e) { lv_obj_t * obj = lv_event_get_target(e); + lv_switch_t * sw = (lv_switch_t *)obj; + const lv_area_t * clip_area = lv_event_get_param(e); lv_base_dir_t base_dir = lv_obj_get_style_base_dir(obj, LV_PART_MAIN); @@ -131,38 +157,47 @@ static void draw_main(lv_event_t * e) lv_coord_t bg_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN); lv_coord_t bg_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN); - bool chk = lv_obj_get_state(obj) & LV_STATE_CHECKED; - /*Draw the indicator in checked state*/ - if(chk) { - /*Respect the background's padding*/ - lv_area_t indic_area; - lv_area_copy(&indic_area, &obj->coords); - indic_area.x1 += bg_left; - indic_area.x2 -= bg_right; - indic_area.y1 += bg_top; - indic_area.y2 -= bg_bottom; + /*Draw the indicator*/ + /*Respect the background's padding*/ + lv_area_t indic_area; + lv_area_copy(&indic_area, &obj->coords); + indic_area.x1 += bg_left; + indic_area.x2 -= bg_right; + indic_area.y1 += bg_top; + indic_area.y2 -= bg_bottom; - lv_draw_rect_dsc_t draw_indic_dsc; - lv_draw_rect_dsc_init(&draw_indic_dsc); - lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &draw_indic_dsc); - lv_draw_rect(&indic_area, clip_area, &draw_indic_dsc); - } + lv_draw_rect_dsc_t draw_indic_dsc; + lv_draw_rect_dsc_init(&draw_indic_dsc); + lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &draw_indic_dsc); + lv_draw_rect(&indic_area, clip_area, &draw_indic_dsc); /*Draw the knob*/ lv_coord_t objh = lv_obj_get_height(obj); lv_coord_t knob_size = objh; lv_area_t knob_area; - /*Left*/ - if((base_dir != LV_BASE_DIR_RTL && !chk) || (base_dir == LV_BASE_DIR_RTL && chk)) { - knob_area.x1 = obj->coords.x1 + bg_left; - knob_area.x2 = knob_area.x1 + knob_size; + lv_coord_t anim_length = obj->coords.x2 - bg_right - obj->coords.x1 - bg_left - knob_size; + + lv_coord_t anim_value_x; + + bool chk = lv_obj_get_state(obj) & LV_STATE_CHECKED; + + if(LV_SWITCH_IS_ANIMATING(sw)) { + /* Use the animation's coordinate */ + anim_value_x = (anim_length * sw->anim_state) / LV_SWITCH_ANIM_STATE_END; } else { - knob_area.x2 = obj->coords.x2 - bg_right; - knob_area.x1 = knob_area.x2 - knob_size; + /* Use LV_STATE_CHECKED to decide the coordinate */ + anim_value_x = chk ? anim_length : 0; } + if(base_dir == LV_BASE_DIR_RTL) { + anim_value_x = anim_length - anim_value_x; + } + + knob_area.x1 = obj->coords.x1 + bg_left + anim_value_x; + knob_area.x2 = knob_area.x1 + knob_size; + knob_area.y1 = obj->coords.y1 + bg_top; knob_area.y2 = obj->coords.y2 - bg_bottom; @@ -182,7 +217,66 @@ static void draw_main(lv_event_t * e) lv_obj_init_draw_rect_dsc(obj, LV_PART_KNOB, &knob_rect_dsc); lv_draw_rect(&knob_area, clip_area, &knob_rect_dsc); - } +static void lv_switch_anim_exec_cb(void * var, int32_t value) +{ + lv_switch_t * sw = var; + sw->anim_state = value; + lv_obj_invalidate((lv_obj_t *)sw); +} + +/** + * Resets the switch's animation state to "no animation in progress". + */ +static void lv_switch_anim_ready(lv_anim_t * a) +{ + lv_switch_t * sw = a->var; + sw->anim_state = LV_SWITCH_ANIM_STATE_INV; + lv_obj_invalidate((lv_obj_t *)sw); +} + +/** + * Starts an animation for the switch knob. if the anim_time style property is greater than 0 + * @param obj the switch to animate + */ +static void lv_switch_trigger_anim(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_switch_t * sw = (lv_switch_t *)obj; + + uint32_t anim_dur_full = lv_obj_get_style_anim_time(obj, LV_PART_MAIN); + + if(anim_dur_full>0) { + bool chk = lv_obj_get_state(obj) & LV_STATE_CHECKED; + int32_t anim_start; + int32_t anim_end; + /*No animation in progress -> simply set the values*/ + if(sw->anim_state == LV_SWITCH_ANIM_STATE_INV) { + anim_start = chk ? LV_SWITCH_ANIM_STATE_START : LV_SWITCH_ANIM_STATE_END; + anim_end = chk ? LV_SWITCH_ANIM_STATE_END : LV_SWITCH_ANIM_STATE_START; + } + /*Animation in progress. Start from the animation end value*/ + else { + anim_start = sw->anim_state; + anim_end = chk ? LV_SWITCH_ANIM_STATE_END : LV_SWITCH_ANIM_STATE_START; + } + /*Calculate actual animation duration*/ + uint32_t anim_dur = (anim_dur_full * LV_ABS(anim_start - anim_end))/LV_SWITCH_ANIM_STATE_END; + + /*Stop the previous animation if it exists*/ + lv_anim_del(sw, NULL); + + lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_var(&a, sw); + lv_anim_set_exec_cb(&a, lv_switch_anim_exec_cb); + lv_anim_set_values(&a, anim_start, anim_end); + lv_anim_set_ready_cb(&a, lv_switch_anim_ready); + lv_anim_set_time(&a, anim_dur); + lv_anim_start(&a); + } +} + + #endif diff --git a/src/widgets/lv_switch.h b/src/widgets/lv_switch.h index 8ff92afb9..cb7328b6d 100644 --- a/src/widgets/lv_switch.h +++ b/src/widgets/lv_switch.h @@ -17,11 +17,6 @@ extern "C" { #if LV_USE_SWITCH != 0 -/*Testing of dependencies*/ -#if LV_USE_SLIDER == 0 -#error "lv_switch: lv_slider is required. Enable it in lv_conf.h (LV_USE_SLIDER 1)" -#endif - #include "../core/lv_obj.h" /********************* @@ -34,6 +29,7 @@ extern "C" { typedef struct { lv_obj_t obj; + int32_t anim_state; }lv_switch_t; extern const lv_obj_class_t lv_switch_class;