1 /**
2  * @file lv_sw.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_switch_private.h"
10 #include "../../core/lv_obj_private.h"
11 #include "../../core/lv_obj_class_private.h"
12 
13 #if LV_USE_SWITCH != 0
14 
15 #include "../../misc/lv_assert.h"
16 #include "../../misc/lv_math.h"
17 #include "../../misc/lv_anim_private.h"
18 #include "../../indev/lv_indev.h"
19 #include "../../display/lv_display.h"
20 
21 /*********************
22  *      DEFINES
23  *********************/
24 #define MY_CLASS (&lv_switch_class)
25 
26 #define LV_SWITCH_IS_ANIMATING(sw) (((sw)->anim_state) != LV_SWITCH_ANIM_STATE_INV)
27 
28 /** Switch animation start value. (Not the real value of the switch just indicates process animation)*/
29 #define LV_SWITCH_ANIM_STATE_START 0
30 
31 /** Switch animation end value.  (Not the real value of the switch just indicates process animation)*/
32 #define LV_SWITCH_ANIM_STATE_END   256
33 
34 /** Mark no animation is in progress*/
35 #define LV_SWITCH_ANIM_STATE_INV   -1
36 
37 /**********************
38  *      TYPEDEFS
39  **********************/
40 
41 /**********************
42  *  STATIC PROTOTYPES
43  **********************/
44 static void lv_switch_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
45 static void lv_switch_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
46 static void lv_switch_event(const lv_obj_class_t * class_p, lv_event_t * e);
47 static void draw_main(lv_event_t * e);
48 
49 static void lv_switch_anim_exec_cb(void * sw, int32_t value);
50 static void lv_switch_trigger_anim(lv_obj_t * obj);
51 static void lv_switch_anim_completed(lv_anim_t * a);
52 
53 /**********************
54  *  STATIC VARIABLES
55  **********************/
56 const lv_obj_class_t lv_switch_class = {
57     .constructor_cb = lv_switch_constructor,
58     .destructor_cb = lv_switch_destructor,
59     .event_cb = lv_switch_event,
60     .width_def = (4 * LV_DPI_DEF) / 10,
61     .height_def = (4 * LV_DPI_DEF) / 17,
62     .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE,
63     .instance_size = sizeof(lv_switch_t),
64     .base_class = &lv_obj_class,
65     .name = "switch",
66 };
67 
68 /**********************
69  *      MACROS
70  **********************/
71 
72 /**********************
73  *   GLOBAL FUNCTIONS
74  **********************/
75 
lv_switch_create(lv_obj_t * parent)76 lv_obj_t * lv_switch_create(lv_obj_t * parent)
77 {
78     LV_LOG_INFO("begin");
79     lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
80     lv_obj_class_init_obj(obj);
81     return obj;
82 }
83 
84 /*=====================
85  * Setter functions
86  *====================*/
87 
lv_switch_set_orientation(lv_obj_t * obj,lv_switch_orientation_t orientation)88 void lv_switch_set_orientation(lv_obj_t * obj, lv_switch_orientation_t orientation)
89 {
90     LV_ASSERT_OBJ(obj, MY_CLASS);
91     lv_switch_t * sw = (lv_switch_t *)obj;
92 
93     sw->orientation = orientation;
94     lv_obj_invalidate(obj);
95 }
96 
97 /*=====================
98  * Getter functions
99  *====================*/
100 
lv_switch_get_orientation(lv_obj_t * obj)101 lv_switch_orientation_t lv_switch_get_orientation(lv_obj_t * obj)
102 {
103     LV_ASSERT_OBJ(obj, MY_CLASS);
104     lv_switch_t * sw = (lv_switch_t *)obj;
105 
106     return sw->orientation;
107 }
108 
109 /**********************
110  *   STATIC FUNCTIONS
111  **********************/
112 
lv_switch_constructor(const lv_obj_class_t * class_p,lv_obj_t * obj)113 static void lv_switch_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
114 {
115     LV_UNUSED(class_p);
116     LV_TRACE_OBJ_CREATE("begin");
117 
118     lv_switch_t * sw = (lv_switch_t *)obj;
119 
120     sw->anim_state = LV_SWITCH_ANIM_STATE_INV;
121     sw->orientation = LV_SWITCH_ORIENTATION_AUTO;
122 
123     lv_obj_remove_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
124     lv_obj_add_flag(obj, LV_OBJ_FLAG_CHECKABLE);
125     lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
126 
127     LV_TRACE_OBJ_CREATE("finished");
128 }
129 
lv_switch_destructor(const lv_obj_class_t * class_p,lv_obj_t * obj)130 static void lv_switch_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
131 {
132     LV_UNUSED(class_p);
133     lv_switch_t * sw = (lv_switch_t *)obj;
134 
135     lv_anim_delete(sw, NULL);
136 }
137 
lv_switch_event(const lv_obj_class_t * class_p,lv_event_t * e)138 static void lv_switch_event(const lv_obj_class_t * class_p, lv_event_t * e)
139 {
140     LV_UNUSED(class_p);
141 
142     lv_result_t res;
143 
144     /*Call the ancestor's event handler*/
145     res = lv_obj_event_base(MY_CLASS, e);
146     if(res != LV_RESULT_OK) return;
147 
148     lv_event_code_t code = lv_event_get_code(e);
149     lv_obj_t * obj = lv_event_get_current_target(e);
150 
151     if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) {
152         int32_t knob_left = lv_obj_get_style_pad_left(obj,   LV_PART_KNOB);
153         int32_t knob_right = lv_obj_get_style_pad_right(obj,  LV_PART_KNOB);
154         int32_t knob_top = lv_obj_get_style_pad_top(obj,    LV_PART_KNOB);
155         int32_t knob_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_KNOB);
156 
157         /*The smaller size is the knob diameter*/
158         int32_t knob_size = LV_MAX4(knob_left, knob_right, knob_bottom, knob_top);
159         knob_size += LV_SWITCH_KNOB_EXT_AREA_CORRECTION;
160         knob_size += lv_obj_calculate_ext_draw_size(obj, LV_PART_KNOB);
161 
162         int32_t * s = lv_event_get_param(e);
163         *s = LV_MAX(*s, knob_size);
164         *s = LV_MAX(*s, lv_obj_calculate_ext_draw_size(obj, LV_PART_INDICATOR));
165     }
166     else if(code == LV_EVENT_VALUE_CHANGED) {
167         lv_switch_trigger_anim(obj);
168         lv_obj_invalidate(obj);
169     }
170     else if(code == LV_EVENT_DRAW_MAIN) {
171         draw_main(e);
172     }
173 }
174 
draw_main(lv_event_t * e)175 static void draw_main(lv_event_t * e)
176 {
177     lv_obj_t * obj = lv_event_get_current_target(e);
178     lv_switch_t * sw = (lv_switch_t *)obj;
179 
180     lv_layer_t * layer = lv_event_get_layer(e);
181 
182     /*Draw the indicator*/
183     lv_area_t indic_area;
184     /*Exclude background's padding*/
185     lv_obj_get_content_coords(obj, &indic_area);
186 
187     lv_draw_rect_dsc_t draw_indic_dsc;
188     lv_draw_rect_dsc_init(&draw_indic_dsc);
189     draw_indic_dsc.base.layer = layer;
190     lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &draw_indic_dsc);
191     lv_draw_rect(layer, &draw_indic_dsc, &indic_area);
192 
193     /*Draw the knob*/
194     lv_area_t knob_area;
195     lv_area_copy(&knob_area, &obj->coords);
196 
197     int32_t switch_w = lv_area_get_width(&obj->coords);
198     int32_t switch_h = lv_area_get_height(&obj->coords);
199     bool hor = false;
200 
201     switch(sw->orientation) {
202         case LV_SWITCH_ORIENTATION_HORIZONTAL:
203             hor = true;
204             break;
205         case LV_SWITCH_ORIENTATION_VERTICAL:
206             hor = false;
207             break;
208         case LV_SWITCH_ORIENTATION_AUTO:
209         default:
210             hor = (switch_w >= switch_h);
211             break;
212     }
213 
214     if(hor) {
215         int32_t anim_value_x = 0;
216         int32_t knob_size = lv_obj_get_height(obj);
217         int32_t anim_length = lv_area_get_width(&obj->coords) - knob_size;
218         if(LV_SWITCH_IS_ANIMATING(sw)) {
219             /* Use the animation's coordinate */
220             anim_value_x = (anim_length * sw->anim_state) / LV_SWITCH_ANIM_STATE_END;
221         }
222         else {
223             /* Use LV_STATE_CHECKED to decide the coordinate */
224             bool chk = lv_obj_get_state(obj) & LV_STATE_CHECKED;
225             anim_value_x = chk ? anim_length : 0;
226         }
227 
228         if(LV_BASE_DIR_RTL == lv_obj_get_style_base_dir(obj, LV_PART_MAIN)) {
229             anim_value_x = anim_length - anim_value_x;
230         }
231         knob_area.x1 += anim_value_x;
232         knob_area.x2 = knob_area.x1 + (knob_size > 0 ? knob_size - 1 : 0);
233     }
234     else {
235         int32_t anim_value_y = 0;
236         int32_t knob_size = lv_obj_get_width(obj);
237         int32_t anim_length = lv_area_get_height(&obj->coords) - knob_size;
238         if(LV_SWITCH_IS_ANIMATING(sw)) {
239             /* Use the animation's coordinate */
240             anim_value_y = (anim_length * sw->anim_state) / LV_SWITCH_ANIM_STATE_END;
241         }
242         else {
243             /* Use LV_STATE_CHECKED to decide the coordinate */
244             bool chk = lv_obj_get_state(obj) & LV_STATE_CHECKED;
245             anim_value_y = chk ? anim_length : 0;
246         }
247 
248         if(LV_BASE_DIR_RTL == lv_obj_get_style_base_dir(obj, LV_PART_MAIN)) {
249             anim_value_y = anim_length - anim_value_y;
250         }
251 
252         knob_area.y2 -= anim_value_y;
253         knob_area.y1 = knob_area.y2 - (knob_size > 0 ? knob_size - 1 : 0);
254     }
255 
256     int32_t knob_left = lv_obj_get_style_pad_left(obj, LV_PART_KNOB);
257     int32_t knob_right = lv_obj_get_style_pad_right(obj, LV_PART_KNOB);
258     int32_t knob_top = lv_obj_get_style_pad_top(obj, LV_PART_KNOB);
259     int32_t knob_bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_KNOB);
260 
261     /*Apply the paddings on the knob area*/
262     knob_area.x1 -= knob_left;
263     knob_area.x2 += knob_right;
264     knob_area.y1 -= knob_top;
265     knob_area.y2 += knob_bottom;
266 
267     lv_draw_rect_dsc_t knob_rect_dsc;
268     lv_draw_rect_dsc_init(&knob_rect_dsc);
269     knob_rect_dsc.base.layer = layer;
270     lv_obj_init_draw_rect_dsc(obj, LV_PART_KNOB, &knob_rect_dsc);
271 
272     lv_draw_rect(layer, &knob_rect_dsc, &knob_area);
273 }
274 
lv_switch_anim_exec_cb(void * var,int32_t value)275 static void lv_switch_anim_exec_cb(void * var, int32_t value)
276 {
277     lv_switch_t * sw = var;
278     sw->anim_state = value;
279     lv_obj_invalidate((lv_obj_t *)sw);
280 }
281 
282 /**
283  * Resets the switch's animation state to "no animation in progress".
284  */
lv_switch_anim_completed(lv_anim_t * a)285 static void lv_switch_anim_completed(lv_anim_t * a)
286 {
287     lv_switch_t * sw = a->var;
288     sw->anim_state = LV_SWITCH_ANIM_STATE_INV;
289     lv_obj_invalidate((lv_obj_t *)sw);
290 }
291 
292 /**
293  * Starts an animation for the switch knob. if the anim_time style property is greater than 0
294  * @param obj the switch to animate
295  */
lv_switch_trigger_anim(lv_obj_t * obj)296 static void lv_switch_trigger_anim(lv_obj_t * obj)
297 {
298     LV_ASSERT_OBJ(obj, MY_CLASS);
299     lv_switch_t * sw = (lv_switch_t *)obj;
300 
301     uint32_t anim_dur_full = lv_obj_get_style_anim_duration(obj, LV_PART_MAIN);
302 
303     if(anim_dur_full > 0) {
304         bool chk = lv_obj_get_state(obj) & LV_STATE_CHECKED;
305         int32_t anim_start;
306         int32_t anim_end;
307         /*No animation in progress -> simply set the values*/
308         if(sw->anim_state == LV_SWITCH_ANIM_STATE_INV) {
309             anim_start = chk ? LV_SWITCH_ANIM_STATE_START : LV_SWITCH_ANIM_STATE_END;
310             anim_end   = chk ? LV_SWITCH_ANIM_STATE_END : LV_SWITCH_ANIM_STATE_START;
311         }
312         /*Animation in progress. Start from the animation end value*/
313         else {
314             anim_start = sw->anim_state;
315             anim_end   = chk ? LV_SWITCH_ANIM_STATE_END : LV_SWITCH_ANIM_STATE_START;
316         }
317         /*Calculate actual animation duration*/
318         uint32_t anim_dur = (anim_dur_full * LV_ABS(anim_start - anim_end)) / LV_SWITCH_ANIM_STATE_END;
319 
320         /*Stop the previous animation if it exists*/
321         lv_anim_delete(sw, NULL);
322 
323         lv_anim_t a;
324         lv_anim_init(&a);
325         lv_anim_set_var(&a, sw);
326         lv_anim_set_exec_cb(&a, lv_switch_anim_exec_cb);
327         lv_anim_set_values(&a, anim_start, anim_end);
328         lv_anim_set_completed_cb(&a, lv_switch_anim_completed);
329         lv_anim_set_duration(&a, anim_dur);
330         lv_anim_start(&a);
331     }
332 }
333 
334 #endif
335