1 /**
2 * @file lv_btnm.c
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9 #include "lv_btnmatrix.h"
10 #if LV_USE_BTNMATRIX != 0
11
12 #include "../lv_misc/lv_debug.h"
13 #include "../lv_core/lv_group.h"
14 #include "../lv_draw/lv_draw.h"
15 #include "../lv_core/lv_refr.h"
16 #include "../lv_themes/lv_theme.h"
17 #include "../lv_misc/lv_txt.h"
18
19 /*********************
20 * DEFINES
21 *********************/
22 #define LV_OBJX_NAME "lv_btnmatrix"
23 #define BTN_EXTRA_CLICK_AREA_MAX (LV_DPI / 4)
24
25 /**********************
26 * TYPEDEFS
27 **********************/
28
29 /**********************
30 * STATIC PROTOTYPES
31 **********************/
32 static lv_res_t lv_btnmatrix_signal(lv_obj_t * btnm, lv_signal_t sign, void * param);
33 static lv_design_res_t lv_btnmatrix_design(lv_obj_t * btnm, const lv_area_t * clip_area, lv_design_mode_t mode);
34 static lv_style_list_t * lv_btnmatrix_get_style(lv_obj_t * btnm, uint8_t part);
35
36 static uint8_t get_button_width(lv_btnmatrix_ctrl_t ctrl_bits);
37 static bool button_is_hidden(lv_btnmatrix_ctrl_t ctrl_bits);
38 static bool button_is_repeat_disabled(lv_btnmatrix_ctrl_t ctrl_bits);
39 static bool button_is_inactive(lv_btnmatrix_ctrl_t ctrl_bits);
40 static bool button_is_click_trig(lv_btnmatrix_ctrl_t ctrl_bits);
41 static bool button_is_tgl_enabled(lv_btnmatrix_ctrl_t ctrl_bits);
42 static bool button_get_tgl_state(lv_btnmatrix_ctrl_t ctrl_bits);
43 static uint16_t get_button_from_point(lv_obj_t * btnm, lv_point_t * p);
44 static void allocate_btn_areas_and_controls(const lv_obj_t * btnm, const char ** map);
45 static void invalidate_button_area(const lv_obj_t * btnm, uint16_t btn_idx);
46 static bool maps_are_identical(const char ** map1, const char ** map2);
47 static void make_one_button_toggled(lv_obj_t * btnm, uint16_t btn_idx);
48
49 /**********************
50 * STATIC VARIABLES
51 **********************/
52 static const char * lv_btnmatrix_def_map[] = {"Btn1", "Btn2", "Btn3", "\n", "Btn4", "Btn5", ""};
53
54 static lv_design_cb_t ancestor_design_f;
55 static lv_signal_cb_t ancestor_signal;
56
57 /**********************
58 * MACROS
59 **********************/
60
61 /**********************
62 * GLOBAL FUNCTIONS
63 **********************/
64
65 /**
66 * Create a button matrix objects
67 * @param par pointer to an object, it will be the parent of the new button matrix
68 * @param copy pointer to a button matrix object, if not NULL then the new object will be copied
69 * from it
70 * @return pointer to the created button matrix
71 */
lv_btnmatrix_create(lv_obj_t * par,const lv_obj_t * copy)72 lv_obj_t * lv_btnmatrix_create(lv_obj_t * par, const lv_obj_t * copy)
73 {
74 LV_LOG_TRACE("button matrix create started");
75
76 /*Create the ancestor object*/
77 lv_obj_t * btnm = lv_obj_create(par, copy);
78 LV_ASSERT_MEM(btnm);
79 if(btnm == NULL) return NULL;
80
81 if(ancestor_signal == NULL) ancestor_signal = lv_obj_get_signal_cb(btnm);
82
83 /*Allocate the object type specific extended data*/
84 lv_btnmatrix_ext_t * ext = lv_obj_allocate_ext_attr(btnm, sizeof(lv_btnmatrix_ext_t));
85 LV_ASSERT_MEM(ext);
86 if(ext == NULL) {
87 lv_obj_del(btnm);
88 return NULL;
89 }
90
91 ext->btn_cnt = 0;
92 ext->btn_id_pr = LV_BTNMATRIX_BTN_NONE;
93 ext->btn_id_focused = LV_BTNMATRIX_BTN_NONE;
94 ext->btn_id_act = LV_BTNMATRIX_BTN_NONE;
95 ext->button_areas = NULL;
96 ext->ctrl_bits = NULL;
97 ext->map_p = NULL;
98 ext->recolor = 0;
99 ext->one_check = 0;
100 lv_style_list_init(&ext->style_btn);
101 ext->style_btn.ignore_trans = 1;
102
103 if(ancestor_design_f == NULL) ancestor_design_f = lv_obj_get_design_cb(btnm);
104
105 lv_obj_set_signal_cb(btnm, lv_btnmatrix_signal);
106 lv_obj_set_design_cb(btnm, lv_btnmatrix_design);
107
108 /*Init the new button matrix object*/
109 if(copy == NULL) {
110 lv_btnmatrix_set_map(btnm, lv_btnmatrix_def_map);
111 lv_obj_set_size(btnm, LV_DPI * 2, LV_DPI * 1);
112 lv_theme_apply(btnm, LV_THEME_BTNMATRIX);
113 }
114 /*Copy an existing object*/
115 else {
116 lv_btnmatrix_ext_t * copy_ext = lv_obj_get_ext_attr(copy);
117 lv_btnmatrix_set_map(btnm, copy_ext->map_p);
118 lv_btnmatrix_set_ctrl_map(btnm, copy_ext->ctrl_bits);
119 lv_style_list_copy(&ext->style_btn, ©_ext->style_btn);
120 }
121
122 LV_LOG_INFO("button matrix created");
123
124 return btnm;
125 }
126
127 /*=====================
128 * Setter functions
129 *====================*/
130
131 /**
132 * Set a new map. Buttons will be created/deleted according to the map. The
133 * button matrix keeps a reference to the map and so the string array must not
134 * be deallocated during the life of the matrix.
135 * @param btnm pointer to a button matrix object
136 * @param map pointer a string array. The last string has to be: "". Use "\n" to make a line break.
137 */
lv_btnmatrix_set_map(lv_obj_t * btnm,const char * map[])138 void lv_btnmatrix_set_map(lv_obj_t * btnm, const char * map[])
139 {
140 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
141 LV_ASSERT_NULL(map);
142
143 /*
144 * lv_btnmatrix_set_map is called on receipt of signals such as
145 * LV_SIGNAL_CORD_CHG regardless of whether the map has changed (e.g.
146 * calling lv_obj_align on the map will trigger this).
147 *
148 * We check if the map has changed here to avoid overwriting changes made
149 * to hidden/longpress/disabled states after the map was originally set.
150 *
151 * TODO: separate all map set/allocation from layout code below and skip
152 * set/allocation when map hasn't changed.
153 */
154 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
155 if(!maps_are_identical(ext->map_p, map)) {
156
157 /*Analyze the map and create the required number of buttons*/
158 allocate_btn_areas_and_controls(btnm, map);
159 }
160 ext->map_p = map;
161
162 /*Set size and positions of the buttons*/
163 lv_style_int_t left = lv_obj_get_style_pad_left(btnm, LV_BTNMATRIX_PART_BG);
164 lv_style_int_t right = lv_obj_get_style_pad_right(btnm, LV_BTNMATRIX_PART_BG);
165 lv_style_int_t top = lv_obj_get_style_pad_top(btnm, LV_BTNMATRIX_PART_BG);
166 lv_style_int_t bottom = lv_obj_get_style_pad_bottom(btnm, LV_BTNMATRIX_PART_BG);
167 lv_style_int_t inner = lv_obj_get_style_pad_inner(btnm, LV_BTNMATRIX_PART_BG);
168
169 lv_coord_t max_w = lv_obj_get_width(btnm) - left - right;
170 lv_coord_t max_h = lv_obj_get_height(btnm) - top - bottom;
171 lv_coord_t act_y = top;
172
173 /*Count the lines to calculate button height*/
174 uint8_t line_cnt = 1;
175 uint8_t li;
176 for(li = 0; strlen(map[li]) != 0; li++) {
177 if(strcmp(map[li], "\n") == 0) line_cnt++;
178 }
179
180 lv_coord_t btn_h = max_h - ((line_cnt - 1) * inner);
181 btn_h = (btn_h + line_cnt / 2) / line_cnt;
182 btn_h--; /*-1 because e.g. height = 100 means 101 pixels (0..100)*/
183
184 /* Count the units and the buttons in a line
185 * (A button can be 1,2,3... unit wide)*/
186 uint16_t unit_act_cnt; /*Number of units currently put in a row*/
187 uint16_t i_tot = 0; /*Act. index in the str map*/
188 uint16_t btn_i = 0; /*Act. index of button areas*/
189 const char ** map_p_tmp = map;
190
191 /*Count the units and the buttons in a line*/
192 while(1) {
193 uint16_t unit_cnt = 0; /*Number of units in a row*/
194 uint16_t btn_cnt = 0; /*Number of buttons in a row*/
195 /*Count the buttons in a line*/
196 while(strcmp(map_p_tmp[btn_cnt], "\n") != 0 && strlen(map_p_tmp[btn_cnt]) != 0) { /*Check a line*/
197 unit_cnt += get_button_width(ext->ctrl_bits[btn_i + btn_cnt]);
198 btn_cnt++;
199 }
200
201 /*Make sure the last row is at the bottom of 'btnm'*/
202 if(map_p_tmp[btn_cnt][0] == '\0') { /*Last row?*/
203 btn_h = lv_obj_get_height(btnm) - act_y - bottom - 1;
204 }
205
206 lv_bidi_dir_t base_dir = lv_obj_get_base_dir(btnm);
207
208 /*Only deal with the non empty lines*/
209 if(btn_cnt != 0) {
210 /*Calculate the width of all units*/
211 lv_coord_t all_unit_w = max_w - ((unit_cnt - 1) * inner);
212
213 /*Set the button size and positions and set the texts*/
214 uint16_t i;
215 lv_coord_t act_x;
216
217 unit_act_cnt = 0;
218 for(i = 0; i < btn_cnt; i++) {
219 uint8_t btn_unit_w = get_button_width(ext->ctrl_bits[btn_i]);
220 /* one_unit_w = all_unit_w / unit_cnt
221 * act_unit_w = one_unit_w * button_width
222 * do this two operations but the multiply first to divide a greater number */
223 lv_coord_t act_unit_w = (all_unit_w * btn_unit_w) / unit_cnt + inner * (btn_unit_w - 1);
224 act_unit_w--; /*-1 because e.g. width = 100 means 101 pixels (0..100)*/
225
226 /*Always recalculate act_x because of rounding errors */
227 if(base_dir == LV_BIDI_DIR_RTL) {
228 act_x = (unit_act_cnt * all_unit_w) / unit_cnt + unit_act_cnt * inner;
229 act_x = lv_obj_get_width(btnm) - right - act_x - act_unit_w - 1;
230 }
231 else {
232 act_x = (unit_act_cnt * all_unit_w) / unit_cnt + unit_act_cnt * inner +
233 left;
234 }
235 /* Set the button's area.
236 * If inner padding is zero then use the prev. button x2 as x1 to avoid rounding
237 * errors*/
238 if(btn_i != 0 && inner == 0 && ((act_x != left && base_dir != LV_BIDI_DIR_RTL) ||
239 (act_x + act_unit_w == max_w - right && base_dir == LV_BIDI_DIR_RTL))) {
240 lv_area_set(&ext->button_areas[btn_i], ext->button_areas[btn_i - 1].x2, act_y, act_x + act_unit_w,
241 act_y + btn_h);
242 }
243 else {
244 lv_area_set(&ext->button_areas[btn_i], act_x, act_y, act_x + act_unit_w, act_y + btn_h);
245 }
246
247 unit_act_cnt += btn_unit_w;
248
249 i_tot++;
250 btn_i++;
251 }
252 }
253 act_y += btn_h + inner + 1;
254
255 if(strlen(map_p_tmp[btn_cnt]) == 0) break; /*Break on end of map*/
256 map_p_tmp = &map_p_tmp[btn_cnt + 1]; /*Set the map to the next line*/
257 i_tot++; /*Skip the '\n'*/
258 }
259
260 lv_obj_invalidate(btnm);
261 }
262
263 /**
264 * Set the button control map (hidden, disabled etc.) for a button matrix. The
265 * control map array will be copied and so may be deallocated after this
266 * function returns.
267 * @param btnm pointer to a button matrix object
268 * @param ctrl_map pointer to an array of `lv_btn_ctrl_t` control bytes. The
269 * length of the array and position of the elements must match
270 * the number and order of the individual buttons (i.e. excludes
271 * newline entries).
272 * An element of the map should look like e.g.:
273 * `ctrl_map[0] = width | LV_BTNMATRIX_CTRL_NO_REPEAT | LV_BTNMATRIX_CTRL_TGL_ENABLE`
274 */
lv_btnmatrix_set_ctrl_map(lv_obj_t * btnm,const lv_btnmatrix_ctrl_t ctrl_map[])275 void lv_btnmatrix_set_ctrl_map(lv_obj_t * btnm, const lv_btnmatrix_ctrl_t ctrl_map[])
276 {
277 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
278
279 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
280 _lv_memcpy(ext->ctrl_bits, ctrl_map, sizeof(lv_btnmatrix_ctrl_t) * ext->btn_cnt);
281
282 lv_btnmatrix_set_map(btnm, ext->map_p);
283 }
284
285 /**
286 * Set the focused button i.e. visually highlight it.
287 * @param btnm pointer to button matrix object
288 * @param id index of the button to focus(`LV_BTNMATRIX_BTN_NONE` to remove focus)
289 */
lv_btnmatrix_set_focused_btn(lv_obj_t * btnm,uint16_t id)290 void lv_btnmatrix_set_focused_btn(lv_obj_t * btnm, uint16_t id)
291 {
292 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
293
294 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
295
296 if(id >= ext->btn_cnt && id != LV_BTNMATRIX_BTN_NONE) return;
297
298 if(id == ext->btn_id_focused) return;
299
300 ext->btn_id_focused = id;
301 lv_obj_invalidate(btnm);
302 }
303
304
305 /**
306 * Enable recoloring of button's texts
307 * @param btnm pointer to button matrix object
308 * @param en true: enable recoloring; false: disable
309 */
lv_btnmatrix_set_recolor(const lv_obj_t * btnm,bool en)310 void lv_btnmatrix_set_recolor(const lv_obj_t * btnm, bool en)
311 {
312 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
313
314 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
315
316 ext->recolor = en;
317 lv_obj_invalidate(btnm);
318 }
319
320 /**
321 * Set the attributes of a button of the button matrix
322 * @param btnm pointer to button matrix object
323 * @param btn_id 0 based index of the button to modify. (Not counting new lines)
324 */
lv_btnmatrix_set_btn_ctrl(const lv_obj_t * btnm,uint16_t btn_id,lv_btnmatrix_ctrl_t ctrl)325 void lv_btnmatrix_set_btn_ctrl(const lv_obj_t * btnm, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)
326 {
327 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
328
329 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
330
331 if(btn_id >= ext->btn_cnt) return;
332
333 ext->ctrl_bits[btn_id] |= ctrl;
334 invalidate_button_area(btnm, btn_id);
335 }
336
337 /**
338 * Clear the attributes of a button of the button matrix
339 * @param btnm pointer to button matrix object
340 * @param btn_id 0 based index of the button to modify. (Not counting new lines)
341 */
lv_btnmatrix_clear_btn_ctrl(const lv_obj_t * btnm,uint16_t btn_id,lv_btnmatrix_ctrl_t ctrl)342 void lv_btnmatrix_clear_btn_ctrl(const lv_obj_t * btnm, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)
343 {
344 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
345
346 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
347
348 if(btn_id >= ext->btn_cnt) return;
349
350 ext->ctrl_bits[btn_id] &= (~ctrl);
351 invalidate_button_area(btnm, btn_id);
352 }
353
354 /**
355 * Set the attributes of all buttons of a button matrix
356 * @param btnm pointer to a button matrix object
357 * @param ctrl attribute(s) to set from `lv_btnmatrix_ctrl_t`. Values can be ORed.
358 */
lv_btnmatrix_set_btn_ctrl_all(lv_obj_t * btnm,lv_btnmatrix_ctrl_t ctrl)359 void lv_btnmatrix_set_btn_ctrl_all(lv_obj_t * btnm, lv_btnmatrix_ctrl_t ctrl)
360 {
361 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
362
363 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
364 uint16_t i;
365 for(i = 0; i < ext->btn_cnt; i++) {
366 lv_btnmatrix_set_btn_ctrl(btnm, i, ctrl);
367 }
368 }
369
370 /**
371 * Clear the attributes of all buttons of a button matrix
372 * @param btnm pointer to a button matrix object
373 * @param ctrl attribute(s) to set from `lv_btnmatrix_ctrl_t`. Values can be ORed.
374 * @param en true: set the attributes; false: clear the attributes
375 */
lv_btnmatrix_clear_btn_ctrl_all(lv_obj_t * btnm,lv_btnmatrix_ctrl_t ctrl)376 void lv_btnmatrix_clear_btn_ctrl_all(lv_obj_t * btnm, lv_btnmatrix_ctrl_t ctrl)
377 {
378 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
379
380 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
381 uint16_t i;
382 for(i = 0; i < ext->btn_cnt; i++) {
383 lv_btnmatrix_clear_btn_ctrl(btnm, i, ctrl);
384 }
385 }
386
387 /**
388 * Set a single buttons relative width.
389 * This method will cause the matrix be regenerated and is a relatively
390 * expensive operation. It is recommended that initial width be specified using
391 * `lv_btnmatrix_set_ctrl_map` and this method only be used for dynamic changes.
392 * @param btnm pointer to button matrix object
393 * @param btn_id 0 based index of the button to modify.
394 * @param width Relative width compared to the buttons in the same row. [1..7]
395 */
lv_btnmatrix_set_btn_width(lv_obj_t * btnm,uint16_t btn_id,uint8_t width)396 void lv_btnmatrix_set_btn_width(lv_obj_t * btnm, uint16_t btn_id, uint8_t width)
397 {
398 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
399
400
401 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
402 if(btn_id >= ext->btn_cnt) return;
403 ext->ctrl_bits[btn_id] &= (~LV_BTNMATRIX_WIDTH_MASK);
404 ext->ctrl_bits[btn_id] |= (LV_BTNMATRIX_WIDTH_MASK & width);
405
406 lv_btnmatrix_set_map(btnm, ext->map_p);
407 }
408
409 /**
410 * Make the button matrix like a selector widget (only one button may be toggled at a time).
411 * `Checkable` must be enabled on the buttons you want to be selected with `lv_btnmatrix_set_ctrl` or
412 * `lv_btnmatrix_set_btn_ctrl_all`.
413 * @param btnm Button matrix object
414 * @param one_chk Whether "one check" mode is enabled
415 */
lv_btnmatrix_set_one_check(lv_obj_t * btnm,bool one_chk)416 void lv_btnmatrix_set_one_check(lv_obj_t * btnm, bool one_chk)
417 {
418 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
419
420 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
421 ext->one_check = one_chk;
422
423 /*If more than one button is toggled only the first one should be*/
424 make_one_button_toggled(btnm, 0);
425 }
426
427 /**
428 * Set the align of the map text (left, right or center)
429 * @param btnm pointer to a btnmatrix object
430 * @param align LV_LABEL_ALIGN_LEFT, LV_LABEL_ALIGN_RIGHT or LV_LABEL_ALIGN_CENTER
431 */
lv_btnmatrix_set_align(lv_obj_t * btnm,lv_label_align_t align)432 void lv_btnmatrix_set_align(lv_obj_t * btnm, lv_label_align_t align)
433 {
434 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
435
436 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
437 if(ext->align == align) return;
438
439 ext->align = align;
440
441 lv_obj_invalidate(btnm);
442 }
443
444 /*=====================
445 * Getter functions
446 *====================*/
447
448 /**
449 * Get the current map of a button matrix
450 * @param btnm pointer to a button matrix object
451 * @return the current map
452 */
lv_btnmatrix_get_map_array(const lv_obj_t * btnm)453 const char ** lv_btnmatrix_get_map_array(const lv_obj_t * btnm)
454 {
455 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
456
457 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
458 return ext->map_p;
459 }
460
461 /**
462 * Check whether the button's text can use recolor or not
463 * @param btnm pointer to button matrix object
464 * @return true: text recolor enable; false: disabled
465 */
lv_btnmatrix_get_recolor(const lv_obj_t * btnm)466 bool lv_btnmatrix_get_recolor(const lv_obj_t * btnm)
467 {
468 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
469
470 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
471
472 return ext->recolor;
473 }
474
475 /**
476 * Get the index of the lastly "activated" button by the user (pressed, released etc)
477 * Useful in the the `event_cb` to get the text of the button, check if hidden etc.
478 * @param btnm pointer to button matrix object
479 * @return index of the last released button (LV_BTNMATRIX_BTN_NONE: if unset)
480 */
lv_btnmatrix_get_active_btn(const lv_obj_t * btnm)481 uint16_t lv_btnmatrix_get_active_btn(const lv_obj_t * btnm)
482 {
483 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
484
485 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
486 return ext->btn_id_act;
487 }
488
489 /**
490 * Get the text of the lastly "activated" button by the user (pressed, released etc)
491 * Useful in the the `event_cb`
492 * @param btnm pointer to button matrix object
493 * @return text of the last released button (NULL: if unset)
494 */
lv_btnmatrix_get_active_btn_text(const lv_obj_t * btnm)495 const char * lv_btnmatrix_get_active_btn_text(const lv_obj_t * btnm)
496 {
497 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
498
499 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
500 if(ext->btn_id_act != LV_BTNMATRIX_BTN_NONE) {
501 return lv_btnmatrix_get_btn_text(btnm, ext->btn_id_act);
502 }
503 else {
504 return NULL;
505 }
506 }
507
508 /**
509 * Get the pressed button's index.
510 * The button be really pressed by the user or manually set to pressed with `lv_btnmatrix_set_pressed`
511 * @param btnm pointer to button matrix object
512 * @return index of the pressed button (LV_BTNMATRIX_BTN_NONE: if unset)
513 */
lv_btnmatrix_get_focused_btn(const lv_obj_t * btnm)514 uint16_t lv_btnmatrix_get_focused_btn(const lv_obj_t * btnm)
515 {
516 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
517
518 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
519 return ext->btn_id_focused;
520 }
521
522 /**
523 * Get the button's text
524 * @param btnm pointer to button matrix object
525 * @param btn_id the index a button not counting new line characters. (The return value of
526 * lv_btnmatrix_get_pressed/released)
527 * @return text of btn_index` button
528 */
lv_btnmatrix_get_btn_text(const lv_obj_t * btnm,uint16_t btn_id)529 const char * lv_btnmatrix_get_btn_text(const lv_obj_t * btnm, uint16_t btn_id)
530 {
531 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
532
533 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
534 if(btn_id > ext->btn_cnt) return NULL;
535
536 uint16_t txt_i = 0;
537 uint16_t btn_i = 0;
538
539 /* Search the text of ext->btn_pr the buttons text in the map
540 * Skip "\n"-s*/
541 while(btn_i != btn_id) {
542 btn_i++;
543 txt_i++;
544 if(strcmp(ext->map_p[txt_i], "\n") == 0) txt_i++;
545 }
546
547 if(btn_i == ext->btn_cnt) return NULL;
548
549 return ext->map_p[txt_i];
550 }
551
552 /**
553 * Get the whether a control value is enabled or disabled for button of a button matrix
554 * @param btnm pointer to a button matrix object
555 * @param btn_id the index a button not counting new line characters. (E.g. the return value of
556 * lv_btnmatrix_get_pressed/released)
557 * @param ctrl control values to check (ORed value can be used)
558 * @return true: long press repeat is disabled; false: long press repeat enabled
559 */
lv_btnmatrix_get_btn_ctrl(lv_obj_t * btnm,uint16_t btn_id,lv_btnmatrix_ctrl_t ctrl)560 bool lv_btnmatrix_get_btn_ctrl(lv_obj_t * btnm, uint16_t btn_id, lv_btnmatrix_ctrl_t ctrl)
561 {
562 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
563
564 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
565 if(btn_id >= ext->btn_cnt) return false;
566
567 return (ext->ctrl_bits[btn_id] & ctrl) ? true : false;
568 }
569
570
571 /**
572 * Find whether "one check" mode is enabled.
573 * @param btnm Button matrix object
574 * @return whether "one check" mode is enabled
575 */
lv_btnmatrix_get_one_check(const lv_obj_t * btnm)576 bool lv_btnmatrix_get_one_check(const lv_obj_t * btnm)
577 {
578 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
579
580 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
581
582 return ext->one_check;
583 }
584
585 /**
586 * Get the align attribute
587 * @param btnm pointer to a btnmatrix object
588 * @return LV_LABEL_ALIGN_LEFT, LV_LABEL_ALIGN_RIGHT or LV_LABEL_ALIGN_CENTER
589 */
lv_btnmatrix_get_align(const lv_obj_t * btnm)590 lv_label_align_t lv_btnmatrix_get_align(const lv_obj_t * btnm)
591 {
592 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
593
594 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
595
596 lv_label_align_t align = ext->align;
597
598 if(align == LV_LABEL_ALIGN_AUTO) {
599 #if LV_USE_BIDI
600 lv_bidi_dir_t base_dir = lv_obj_get_base_dir(btnm);
601 if(base_dir == LV_BIDI_DIR_RTL) align = LV_LABEL_ALIGN_RIGHT;
602 else align = LV_LABEL_ALIGN_LEFT;
603 #else
604 align = LV_LABEL_ALIGN_LEFT;
605 #endif
606 }
607
608 return align;
609 }
610
611 /**********************
612 * STATIC FUNCTIONS
613 **********************/
614
615 /**
616 * Handle the drawing related tasks of the button matrix
617 * @param btnm pointer to a button matrix object
618 * @param clip_area the object will be drawn only in this area
619 * @param mode LV_DESIGN_COVER_CHK: only check if the object fully covers the 'mask_p' area
620 * (return 'true' if yes)
621 * LV_DESIGN_DRAW: draw the object (always return 'true')
622 * LV_DESIGN_DRAW_POST: drawing after every children are drawn
623 * @param return an element of `lv_design_res_t`
624 */
lv_btnmatrix_design(lv_obj_t * btnm,const lv_area_t * clip_area,lv_design_mode_t mode)625 static lv_design_res_t lv_btnmatrix_design(lv_obj_t * btnm, const lv_area_t * clip_area, lv_design_mode_t mode)
626 {
627 if(mode == LV_DESIGN_COVER_CHK) {
628 return ancestor_design_f(btnm, clip_area, mode);
629 }
630 /*Draw the object*/
631 else if(mode == LV_DESIGN_DRAW_MAIN) {
632 ancestor_design_f(btnm, clip_area, mode);
633
634 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
635 if(ext->btn_cnt == 0) return LV_DESIGN_RES_OK;
636 lv_area_t area_btnm;
637 lv_obj_get_coords(btnm, &area_btnm);
638
639 lv_area_t area_tmp;
640 lv_coord_t btn_w;
641 lv_coord_t btn_h;
642
643 uint16_t btn_i = 0;
644 uint16_t txt_i = 0;
645 lv_txt_flag_t txt_flag = LV_TXT_FLAG_NONE;
646 if(ext->recolor) txt_flag |= LV_TXT_FLAG_RECOLOR;
647 lv_label_align_t align = lv_btnmatrix_get_align(btnm);
648 if(align == LV_LABEL_ALIGN_CENTER) txt_flag |= LV_TXT_FLAG_CENTER;
649 if(align == LV_LABEL_ALIGN_RIGHT) txt_flag |= LV_TXT_FLAG_RIGHT;
650
651 lv_draw_rect_dsc_t draw_rect_rel_dsc;
652 lv_draw_label_dsc_t draw_label_rel_dsc;
653
654 lv_draw_rect_dsc_t draw_rect_chk_dsc;
655 lv_draw_label_dsc_t draw_label_chk_dsc;
656
657 lv_draw_rect_dsc_t draw_rect_ina_dsc;
658 lv_draw_label_dsc_t draw_label_ina_dsc;
659
660 lv_draw_rect_dsc_t draw_rect_tmp_dsc;
661 lv_draw_label_dsc_t draw_label_tmp_dsc;
662
663 lv_state_t state_ori = btnm->state;
664 _lv_obj_disable_style_caching(btnm, true);
665 btnm->state = LV_STATE_DEFAULT;
666 lv_draw_rect_dsc_init(&draw_rect_rel_dsc);
667 lv_draw_label_dsc_init(&draw_label_rel_dsc);
668 lv_obj_init_draw_rect_dsc(btnm, LV_BTNMATRIX_PART_BTN, &draw_rect_rel_dsc);
669 lv_obj_init_draw_label_dsc(btnm, LV_BTNMATRIX_PART_BTN, &draw_label_rel_dsc);
670 draw_label_rel_dsc.flag = txt_flag;
671 btnm->state = state_ori;
672 _lv_obj_disable_style_caching(btnm, false);
673
674 bool chk_inited = false;
675 bool disabled_inited = false;
676
677 lv_style_int_t padding_top = lv_obj_get_style_pad_top(btnm, LV_BTNMATRIX_PART_BG);
678 lv_style_int_t padding_bottom = lv_obj_get_style_pad_bottom(btnm, LV_BTNMATRIX_PART_BG);
679
680 for(btn_i = 0; btn_i < ext->btn_cnt; btn_i++, txt_i++) {
681 /*Search the next valid text in the map*/
682 while(strcmp(ext->map_p[txt_i], "\n") == 0) {
683 txt_i++;
684 }
685
686 /*Skip hidden buttons*/
687 if(button_is_hidden(ext->ctrl_bits[btn_i])) continue;
688
689 lv_area_copy(&area_tmp, &ext->button_areas[btn_i]);
690 area_tmp.x1 += area_btnm.x1;
691 area_tmp.y1 += area_btnm.y1;
692 area_tmp.x2 += area_btnm.x1;
693 area_tmp.y2 += area_btnm.y1;
694
695 btn_w = lv_area_get_width(&area_tmp);
696 btn_h = lv_area_get_height(&area_tmp);
697
698 /*Choose the style*/
699 lv_draw_rect_dsc_t * draw_rect_dsc_act;
700 lv_draw_label_dsc_t * draw_label_dsc_act;
701 lv_state_t btn_state = LV_STATE_DEFAULT;
702 if(button_get_tgl_state(ext->ctrl_bits[btn_i])) btn_state |= LV_STATE_CHECKED;
703 if(button_is_inactive(ext->ctrl_bits[btn_i])) btn_state |= LV_STATE_DISABLED;
704 if(btn_i == ext->btn_id_pr) btn_state |= LV_STATE_PRESSED;
705 if(btn_i == ext->btn_id_focused) {
706 btn_state |= LV_STATE_FOCUSED;
707 if(state_ori & LV_STATE_EDITED) btn_state |= LV_STATE_EDITED;
708 }
709
710 if(btn_state == LV_STATE_DEFAULT) {
711 draw_rect_dsc_act = &draw_rect_rel_dsc;
712 draw_label_dsc_act = &draw_label_rel_dsc;
713 }
714 else if(btn_state == LV_STATE_CHECKED) {
715 if(!chk_inited) {
716 btnm->state = LV_STATE_CHECKED;
717 _lv_obj_disable_style_caching(btnm, true);
718 lv_draw_rect_dsc_init(&draw_rect_chk_dsc);
719 lv_draw_label_dsc_init(&draw_label_chk_dsc);
720 lv_obj_init_draw_rect_dsc(btnm, LV_BTNMATRIX_PART_BTN, &draw_rect_chk_dsc);
721 lv_obj_init_draw_label_dsc(btnm, LV_BTNMATRIX_PART_BTN, &draw_label_chk_dsc);
722 draw_label_chk_dsc.flag = txt_flag;
723 btnm->state = state_ori;
724 _lv_obj_disable_style_caching(btnm, false);
725 chk_inited = true;
726 }
727 draw_rect_dsc_act = &draw_rect_chk_dsc;
728 draw_label_dsc_act = &draw_label_chk_dsc;
729 }
730 else if(btn_state == LV_STATE_DISABLED) {
731 if(!disabled_inited) {
732 btnm->state = LV_STATE_DISABLED;
733 _lv_obj_disable_style_caching(btnm, true);
734 lv_draw_rect_dsc_init(&draw_rect_ina_dsc);
735 lv_draw_label_dsc_init(&draw_label_ina_dsc);
736 lv_obj_init_draw_rect_dsc(btnm, LV_BTNMATRIX_PART_BTN, &draw_rect_ina_dsc);
737 lv_obj_init_draw_label_dsc(btnm, LV_BTNMATRIX_PART_BTN, &draw_label_ina_dsc);
738 draw_label_ina_dsc.flag = txt_flag;
739 btnm->state = state_ori;
740 _lv_obj_disable_style_caching(btnm, false);
741 disabled_inited = true;
742 }
743 draw_rect_dsc_act = &draw_rect_ina_dsc;
744 draw_label_dsc_act = &draw_label_ina_dsc;
745 }
746 /*In other cases get the styles directly without caching them*/
747 else {
748 btnm->state = btn_state;
749 _lv_obj_disable_style_caching(btnm, true);
750 lv_draw_rect_dsc_init(&draw_rect_tmp_dsc);
751 lv_draw_label_dsc_init(&draw_label_tmp_dsc);
752 lv_obj_init_draw_rect_dsc(btnm, LV_BTNMATRIX_PART_BTN, &draw_rect_tmp_dsc);
753 lv_obj_init_draw_label_dsc(btnm, LV_BTNMATRIX_PART_BTN, &draw_label_tmp_dsc);
754 draw_label_tmp_dsc.flag = txt_flag;
755 draw_rect_dsc_act = &draw_rect_tmp_dsc;
756 draw_label_dsc_act = &draw_label_tmp_dsc;
757 btnm->state = state_ori;
758 _lv_obj_disable_style_caching(btnm, false);
759 }
760
761 lv_style_int_t border_part_ori = draw_rect_dsc_act->border_side;
762
763 /*Remove borders on the edges if `LV_BORDER_INTERNAL`*/
764 if(border_part_ori & LV_BORDER_SIDE_INTERNAL) {
765 /*Top/Bottom lines*/
766 if(area_tmp.y1 == btnm->coords.y1 + padding_top) {
767 draw_rect_dsc_act->border_side &= ~LV_BORDER_SIDE_TOP;
768 }
769 if(area_tmp.y2 == btnm->coords.y2 - padding_bottom) {
770 draw_rect_dsc_act->border_side &= ~LV_BORDER_SIDE_BOTTOM;
771 }
772
773 /*Left/right columns*/
774 if(txt_i == 0) { /*First button*/
775 draw_rect_dsc_act->border_side &= ~LV_BORDER_SIDE_LEFT;
776 }
777 else if(strcmp(ext->map_p[txt_i - 1], "\n") == 0) {
778 draw_rect_dsc_act->border_side &= ~LV_BORDER_SIDE_LEFT;
779 }
780
781 if(ext->map_p[txt_i + 1][0] == '\0' || strcmp(ext->map_p[txt_i + 1], "\n") == 0) {
782 draw_rect_dsc_act->border_side &= ~LV_BORDER_SIDE_RIGHT;
783 }
784 }
785
786 lv_draw_rect(&area_tmp, clip_area, draw_rect_dsc_act);
787
788 draw_rect_dsc_act->border_side = border_part_ori;
789
790 /*Calculate the size of the text*/
791 const lv_font_t * font = draw_label_dsc_act->font;
792 lv_style_int_t letter_space = draw_label_dsc_act->letter_space;
793 lv_style_int_t line_space = draw_label_dsc_act->line_space;
794 const char * txt = ext->map_p[txt_i];
795 lv_point_t txt_size;
796 _lv_txt_get_size(&txt_size, txt, font, letter_space,
797 line_space, lv_area_get_width(&area_btnm), txt_flag);
798
799 area_tmp.x1 += (btn_w - txt_size.x) / 2;
800 area_tmp.y1 += (btn_h - txt_size.y) / 2;
801 area_tmp.x2 = area_tmp.x1 + txt_size.x;
802 area_tmp.y2 = area_tmp.y1 + txt_size.y;
803
804 lv_draw_label(&area_tmp, clip_area, draw_label_dsc_act, txt, NULL);
805 }
806 }
807 else if(mode == LV_DESIGN_DRAW_POST) {
808 ancestor_design_f(btnm, clip_area, mode);
809 }
810 return LV_DESIGN_RES_OK;
811 }
812
813 /**
814 * Signal function of the button matrix
815 * @param btnm pointer to a button matrix object
816 * @param sign a signal type from lv_signal_t enum
817 * @param param pointer to a signal specific variable
818 * @return LV_RES_OK: the object is not deleted in the function; LV_RES_INV: the object is deleted
819 */
lv_btnmatrix_signal(lv_obj_t * btnm,lv_signal_t sign,void * param)820 static lv_res_t lv_btnmatrix_signal(lv_obj_t * btnm, lv_signal_t sign, void * param)
821 {
822 lv_res_t res;
823 if(sign == LV_SIGNAL_GET_STYLE) {
824 lv_get_style_info_t * info = param;
825 info->result = lv_btnmatrix_get_style(btnm, info->part);
826 if(info->result != NULL) return LV_RES_OK;
827 else return ancestor_signal(btnm, sign, param);
828 }
829
830 /* Include the ancient signal function */
831 res = ancestor_signal(btnm, sign, param);
832 if(res != LV_RES_OK) return res;
833 if(sign == LV_SIGNAL_GET_TYPE) return lv_obj_handle_get_type_signal(param, LV_OBJX_NAME);
834
835 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
836 lv_point_t p;
837 if(sign == LV_SIGNAL_CLEANUP) {
838 lv_obj_clean_style_list(btnm, LV_BTNMATRIX_PART_BTN);
839 lv_mem_free(ext->button_areas);
840 lv_mem_free(ext->ctrl_bits);
841 }
842 else if(sign == LV_SIGNAL_STYLE_CHG) {
843 lv_btnmatrix_set_map(btnm, ext->map_p);
844 }
845 else if(sign == LV_SIGNAL_COORD_CHG) {
846 if(lv_obj_get_width(btnm) != lv_area_get_width(param) || lv_obj_get_height(btnm) != lv_area_get_height(param)) {
847 lv_btnmatrix_set_map(btnm, ext->map_p);
848 }
849 }
850 else if(sign == LV_SIGNAL_PRESSED) {
851 invalidate_button_area(btnm, ext->btn_id_pr);
852
853 lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
854 if(indev_type == LV_INDEV_TYPE_POINTER || indev_type == LV_INDEV_TYPE_BUTTON) {
855 uint16_t btn_pr;
856 /*Search the pressed area*/
857 lv_indev_get_point(param, &p);
858 btn_pr = get_button_from_point(btnm, &p);
859 /*Handle the case where there is no button there*/
860 if(btn_pr != LV_BTNMATRIX_BTN_NONE) {
861 if(button_is_inactive(ext->ctrl_bits[btn_pr]) == false &&
862 button_is_hidden(ext->ctrl_bits[btn_pr]) == false) {
863 invalidate_button_area(btnm, ext->btn_id_pr) /*Invalidate the old area*/;
864 ext->btn_id_pr = btn_pr;
865 ext->btn_id_act = btn_pr;
866 invalidate_button_area(btnm, ext->btn_id_pr); /*Invalidate the new area*/
867 }
868 }
869 }
870 #if LV_USE_GROUP
871 else if(indev_type == LV_INDEV_TYPE_KEYPAD || (indev_type == LV_INDEV_TYPE_ENCODER &&
872 lv_group_get_editing(lv_obj_get_group(btnm)))) {
873 ext->btn_id_pr = ext->btn_id_focused;
874 invalidate_button_area(btnm, ext->btn_id_focused);
875 }
876 #endif
877
878 if(ext->btn_id_pr != LV_BTNMATRIX_BTN_NONE) {
879 if(button_is_click_trig(ext->ctrl_bits[ext->btn_id_pr]) == false &&
880 button_is_inactive(ext->ctrl_bits[ext->btn_id_pr]) == false &&
881 button_is_hidden(ext->ctrl_bits[ext->btn_id_pr]) == false) {
882 uint32_t b = ext->btn_id_pr;
883 res = lv_event_send(btnm, LV_EVENT_VALUE_CHANGED, &b);
884 }
885 }
886 }
887 else if(sign == LV_SIGNAL_PRESSING) {
888 uint16_t btn_pr = LV_BTNMATRIX_BTN_NONE;
889 /*Search the pressed area*/
890 lv_indev_t * indev = lv_indev_get_act();
891 lv_indev_type_t indev_type = lv_indev_get_type(indev);
892 if(indev_type == LV_INDEV_TYPE_ENCODER || indev_type == LV_INDEV_TYPE_KEYPAD) return LV_RES_OK;
893
894 lv_indev_get_point(indev, &p);
895 btn_pr = get_button_from_point(btnm, &p);
896 /*Invalidate to old and the new areas*/
897 if(btn_pr != ext->btn_id_pr) {
898 if(ext->btn_id_pr != LV_BTNMATRIX_BTN_NONE) {
899 invalidate_button_area(btnm, ext->btn_id_pr);
900 }
901
902 ext->btn_id_pr = btn_pr;
903 ext->btn_id_act = btn_pr;
904
905 lv_indev_reset_long_press(param); /*Start the log press time again on the new button*/
906 if(btn_pr != LV_BTNMATRIX_BTN_NONE &&
907 button_is_inactive(ext->ctrl_bits[btn_pr]) == false &&
908 button_is_hidden(ext->ctrl_bits[btn_pr]) == false) {
909 invalidate_button_area(btnm, btn_pr);
910 /* Send VALUE_CHANGED for the newly pressed button */
911 if(button_is_click_trig(ext->ctrl_bits[btn_pr]) == false) {
912 uint32_t b = btn_pr;
913 lv_event_send(btnm, LV_EVENT_VALUE_CHANGED, &b);
914 }
915 }
916 }
917 }
918 else if(sign == LV_SIGNAL_RELEASED) {
919 if(ext->btn_id_pr != LV_BTNMATRIX_BTN_NONE) {
920 /*Toggle the button if enabled*/
921 if(button_is_tgl_enabled(ext->ctrl_bits[ext->btn_id_pr]) &&
922 !button_is_inactive(ext->ctrl_bits[ext->btn_id_pr])) {
923 if(button_get_tgl_state(ext->ctrl_bits[ext->btn_id_pr]) && !ext->one_check) {
924 ext->ctrl_bits[ext->btn_id_pr] &= (~LV_BTNMATRIX_CTRL_CHECK_STATE);
925 }
926 else {
927 ext->ctrl_bits[ext->btn_id_pr] |= LV_BTNMATRIX_CTRL_CHECK_STATE;
928 }
929 if(ext->one_check) make_one_button_toggled(btnm, ext->btn_id_pr);
930 }
931
932 /*Invalidate to old pressed area*/;
933 invalidate_button_area(btnm, ext->btn_id_pr);
934 invalidate_button_area(btnm, ext->btn_id_focused);
935
936
937 lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act());
938 if(indev_type == LV_INDEV_TYPE_KEYPAD || indev_type == LV_INDEV_TYPE_ENCODER) {
939 ext->btn_id_focused = ext->btn_id_pr;
940 }
941
942 ext->btn_id_pr = LV_BTNMATRIX_BTN_NONE;
943
944 if(button_is_click_trig(ext->ctrl_bits[ext->btn_id_act]) == true &&
945 button_is_inactive(ext->ctrl_bits[ext->btn_id_act]) == false &&
946 button_is_hidden(ext->ctrl_bits[ext->btn_id_act]) == false) {
947 uint32_t b = ext->btn_id_act;
948 res = lv_event_send(btnm, LV_EVENT_VALUE_CHANGED, &b);
949 }
950 }
951 }
952 else if(sign == LV_SIGNAL_LONG_PRESS_REP) {
953 if(ext->btn_id_act != LV_BTNMATRIX_BTN_NONE) {
954 if(button_is_repeat_disabled(ext->ctrl_bits[ext->btn_id_act]) == false &&
955 button_is_inactive(ext->ctrl_bits[ext->btn_id_act]) == false &&
956 button_is_hidden(ext->ctrl_bits[ext->btn_id_act]) == false) {
957 uint32_t b = ext->btn_id_act;
958 res = lv_event_send(btnm, LV_EVENT_VALUE_CHANGED, &b);
959 }
960 }
961 }
962 else if(sign == LV_SIGNAL_PRESS_LOST) {
963 ext->btn_id_pr = LV_BTNMATRIX_BTN_NONE;
964 ext->btn_id_act = LV_BTNMATRIX_BTN_NONE;
965 lv_obj_invalidate(btnm);
966 }
967 else if(sign == LV_SIGNAL_FOCUS) {
968 #if LV_USE_GROUP
969 lv_indev_t * indev = lv_indev_get_act();
970 lv_indev_type_t indev_type = lv_indev_get_type(indev);
971
972 /*If not focused by an input device assume the last input device*/
973 if(indev == NULL) {
974 indev = lv_indev_get_next(NULL);
975 indev_type = lv_indev_get_type(indev);
976 }
977
978 if(indev_type == LV_INDEV_TYPE_ENCODER) {
979 /*In navigation mode don't select any button but in edit mode select the fist*/
980 if(lv_group_get_editing(lv_obj_get_group(btnm))) {
981 ext->btn_id_focused = 0;
982 ext->btn_id_act = ext->btn_id_focused;
983 }
984 else {
985 ext->btn_id_focused = LV_BTNMATRIX_BTN_NONE;
986 }
987 }
988 else if(indev_type == LV_INDEV_TYPE_KEYPAD) {
989 ext->btn_id_focused = 0;
990 ext->btn_id_act = ext->btn_id_focused;
991 }
992
993 #endif
994 }
995 else if(sign == LV_SIGNAL_DEFOCUS || sign == LV_SIGNAL_LEAVE) {
996 if(ext->btn_id_focused != LV_BTNMATRIX_BTN_NONE) invalidate_button_area(btnm, ext->btn_id_focused);
997 if(ext->btn_id_pr != LV_BTNMATRIX_BTN_NONE) invalidate_button_area(btnm, ext->btn_id_pr);
998 ext->btn_id_focused = LV_BTNMATRIX_BTN_NONE;
999 ext->btn_id_pr = LV_BTNMATRIX_BTN_NONE;
1000 ext->btn_id_act = LV_BTNMATRIX_BTN_NONE;
1001 }
1002 else if(sign == LV_SIGNAL_CONTROL) {
1003 #if LV_USE_GROUP
1004 char c = *((char *)param);
1005 if(c == LV_KEY_RIGHT) {
1006 if(ext->btn_id_focused == LV_BTNMATRIX_BTN_NONE)
1007 ext->btn_id_focused = 0;
1008 else
1009 ext->btn_id_focused++;
1010 if(ext->btn_id_focused >= ext->btn_cnt - 1) ext->btn_id_focused = ext->btn_cnt - 1;
1011 ext->btn_id_act = ext->btn_id_focused;
1012 lv_obj_invalidate(btnm);
1013 }
1014 else if(c == LV_KEY_LEFT) {
1015 if(ext->btn_id_focused == LV_BTNMATRIX_BTN_NONE) ext->btn_id_focused = 0;
1016 if(ext->btn_id_focused > 0) ext->btn_id_focused--;
1017 ext->btn_id_act = ext->btn_id_focused;
1018 lv_obj_invalidate(btnm);
1019 }
1020 else if(c == LV_KEY_DOWN) {
1021 lv_style_int_t pad_inner = lv_obj_get_style_pad_inner(btnm, LV_BTNMATRIX_PART_BG);
1022 /*Find the area below the the current*/
1023 if(ext->btn_id_focused == LV_BTNMATRIX_BTN_NONE) {
1024 ext->btn_id_focused = 0;
1025 }
1026 else {
1027 uint16_t area_below;
1028 lv_coord_t pr_center =
1029 ext->button_areas[ext->btn_id_focused].x1 + (lv_area_get_width(&ext->button_areas[ext->btn_id_focused]) >> 1);
1030
1031 for(area_below = ext->btn_id_focused; area_below < ext->btn_cnt; area_below++) {
1032 if(ext->button_areas[area_below].y1 > ext->button_areas[ext->btn_id_focused].y1 &&
1033 pr_center >= ext->button_areas[area_below].x1 &&
1034 pr_center <= ext->button_areas[area_below].x2 + pad_inner &&
1035 button_is_inactive(ext->ctrl_bits[area_below]) == false &&
1036 button_is_hidden(ext->ctrl_bits[area_below]) == false) {
1037 break;
1038 }
1039 }
1040
1041 if(area_below < ext->btn_cnt) ext->btn_id_focused = area_below;
1042 }
1043 ext->btn_id_act = ext->btn_id_focused;
1044 lv_obj_invalidate(btnm);
1045 }
1046 else if(c == LV_KEY_UP) {
1047 lv_style_int_t pad_inner = lv_obj_get_style_pad_inner(btnm, LV_BTNMATRIX_PART_BG);
1048 /*Find the area below the the current*/
1049 if(ext->btn_id_focused == LV_BTNMATRIX_BTN_NONE) {
1050 ext->btn_id_focused = 0;
1051 }
1052 else {
1053 int16_t area_above;
1054 lv_coord_t pr_center =
1055 ext->button_areas[ext->btn_id_focused].x1 + (lv_area_get_width(&ext->button_areas[ext->btn_id_focused]) >> 1);
1056
1057 for(area_above = ext->btn_id_focused; area_above >= 0; area_above--) {
1058 if(ext->button_areas[area_above].y1 < ext->button_areas[ext->btn_id_focused].y1 &&
1059 pr_center >= ext->button_areas[area_above].x1 - pad_inner &&
1060 pr_center <= ext->button_areas[area_above].x2 &&
1061 button_is_inactive(ext->ctrl_bits[area_above]) == false &&
1062 button_is_hidden(ext->ctrl_bits[area_above]) == false) {
1063 break;
1064 }
1065 }
1066 if(area_above >= 0) ext->btn_id_focused = area_above;
1067 }
1068 ext->btn_id_act = ext->btn_id_focused;
1069 lv_obj_invalidate(btnm);
1070 }
1071 #endif
1072 }
1073 else if(sign == LV_SIGNAL_GET_EDITABLE) {
1074 #if LV_USE_GROUP
1075 bool * editable = (bool *)param;
1076 *editable = true;
1077 #endif
1078 }
1079 return res;
1080 }
1081
1082 /**
1083 * Get the style descriptor of a part of the object
1084 * @param btnm pointer the object
1085 * @param part the part of the object. (LV_BTNMATRIX_PART_...)
1086 * @return pointer to the style descriptor of the specified part
1087 */
lv_btnmatrix_get_style(lv_obj_t * btnm,uint8_t part)1088 static lv_style_list_t * lv_btnmatrix_get_style(lv_obj_t * btnm, uint8_t part)
1089 {
1090 LV_ASSERT_OBJ(btnm, LV_OBJX_NAME);
1091
1092 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
1093
1094 lv_style_list_t * style_dsc_p;
1095
1096 switch(part) {
1097 case LV_BTNMATRIX_PART_BG:
1098 style_dsc_p = &btnm->style_list;
1099 break;
1100 case LV_BTNMATRIX_PART_BTN:
1101 style_dsc_p = &ext->style_btn;
1102 break;
1103 default:
1104 style_dsc_p = NULL;
1105 }
1106
1107 return style_dsc_p;
1108 }
1109
1110 /**
1111 * Create the required number of buttons and control bytes according to a map
1112 * @param btnm pointer to button matrix object
1113 * @param map_p pointer to a string array
1114 */
allocate_btn_areas_and_controls(const lv_obj_t * btnm,const char ** map)1115 static void allocate_btn_areas_and_controls(const lv_obj_t * btnm, const char ** map)
1116 {
1117 /*Count the buttons in the map*/
1118 uint16_t btn_cnt = 0;
1119 uint16_t i = 0;
1120 while(strlen(map[i]) != 0) {
1121 if(strcmp(map[i], "\n") != 0) { /*Do not count line breaks*/
1122 btn_cnt++;
1123 }
1124 i++;
1125 }
1126
1127 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
1128
1129 if(ext->button_areas != NULL) {
1130 lv_mem_free(ext->button_areas);
1131 ext->button_areas = NULL;
1132 }
1133 if(ext->ctrl_bits != NULL) {
1134 lv_mem_free(ext->ctrl_bits);
1135 ext->ctrl_bits = NULL;
1136 }
1137
1138 ext->button_areas = lv_mem_alloc(sizeof(lv_area_t) * btn_cnt);
1139 LV_ASSERT_MEM(ext->button_areas);
1140 ext->ctrl_bits = lv_mem_alloc(sizeof(lv_btnmatrix_ctrl_t) * btn_cnt);
1141 LV_ASSERT_MEM(ext->ctrl_bits);
1142 if(ext->button_areas == NULL || ext->ctrl_bits == NULL) btn_cnt = 0;
1143
1144 _lv_memset_00(ext->ctrl_bits, sizeof(lv_btnmatrix_ctrl_t) * btn_cnt);
1145
1146 ext->btn_cnt = btn_cnt;
1147 }
1148
1149 /**
1150 * Get the width of a button in units (default is 1).
1151 * @param ctrl_bits least significant 3 bits used (1..7 valid values)
1152 * @return the width of the button in units
1153 */
get_button_width(lv_btnmatrix_ctrl_t ctrl_bits)1154 static uint8_t get_button_width(lv_btnmatrix_ctrl_t ctrl_bits)
1155 {
1156 uint8_t w = ctrl_bits & LV_BTNMATRIX_WIDTH_MASK;
1157 return w != 0 ? w : 1;
1158 }
1159
button_is_hidden(lv_btnmatrix_ctrl_t ctrl_bits)1160 static bool button_is_hidden(lv_btnmatrix_ctrl_t ctrl_bits)
1161 {
1162 return (ctrl_bits & LV_BTNMATRIX_CTRL_HIDDEN) ? true : false;
1163 }
1164
button_is_repeat_disabled(lv_btnmatrix_ctrl_t ctrl_bits)1165 static bool button_is_repeat_disabled(lv_btnmatrix_ctrl_t ctrl_bits)
1166 {
1167 return (ctrl_bits & LV_BTNMATRIX_CTRL_NO_REPEAT) ? true : false;
1168 }
1169
button_is_inactive(lv_btnmatrix_ctrl_t ctrl_bits)1170 static bool button_is_inactive(lv_btnmatrix_ctrl_t ctrl_bits)
1171 {
1172 return (ctrl_bits & LV_BTNMATRIX_CTRL_DISABLED) ? true : false;
1173 }
1174
button_is_click_trig(lv_btnmatrix_ctrl_t ctrl_bits)1175 static bool button_is_click_trig(lv_btnmatrix_ctrl_t ctrl_bits)
1176 {
1177 return (ctrl_bits & LV_BTNMATRIX_CTRL_CLICK_TRIG) ? true : false;
1178 }
1179
button_is_tgl_enabled(lv_btnmatrix_ctrl_t ctrl_bits)1180 static bool button_is_tgl_enabled(lv_btnmatrix_ctrl_t ctrl_bits)
1181 {
1182 return (ctrl_bits & LV_BTNMATRIX_CTRL_CHECKABLE) ? true : false;
1183 }
1184
button_get_tgl_state(lv_btnmatrix_ctrl_t ctrl_bits)1185 static bool button_get_tgl_state(lv_btnmatrix_ctrl_t ctrl_bits)
1186 {
1187 return (ctrl_bits & LV_BTNMATRIX_CTRL_CHECK_STATE) ? true : false;
1188 }
1189
1190 /**
1191 * Gives the button id of a button under a given point
1192 * @param btnm pointer to a button matrix object
1193 * @param p a point with absolute coordinates
1194 * @return the id of the button or LV_BTNMATRIX_BTN_NONE.
1195 */
get_button_from_point(lv_obj_t * btnm,lv_point_t * p)1196 static uint16_t get_button_from_point(lv_obj_t * btnm, lv_point_t * p)
1197 {
1198 lv_area_t btnm_cords;
1199 lv_area_t btn_area;
1200 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
1201 uint16_t i;
1202 lv_obj_get_coords(btnm, &btnm_cords);
1203
1204 lv_coord_t w = lv_obj_get_width(btnm);
1205 lv_coord_t h = lv_obj_get_height(btnm);
1206 lv_style_int_t pleft = lv_obj_get_style_pad_left(btnm, LV_BTNMATRIX_PART_BG);
1207 lv_style_int_t pright = lv_obj_get_style_pad_right(btnm, LV_BTNMATRIX_PART_BG);
1208 lv_style_int_t ptop = lv_obj_get_style_pad_top(btnm, LV_BTNMATRIX_PART_BG);
1209 lv_style_int_t pbottom = lv_obj_get_style_pad_bottom(btnm, LV_BTNMATRIX_PART_BG);
1210 lv_style_int_t pinner = lv_obj_get_style_pad_inner(btnm, LV_BTNMATRIX_PART_BG);
1211
1212 /*Get the half inner padding. Button look larger with this value. (+1 for rounding error)*/
1213 pinner = (pinner / 2) + 1 + (pinner & 1);
1214
1215 pinner = LV_MATH_MIN(pinner, BTN_EXTRA_CLICK_AREA_MAX);
1216 pright = LV_MATH_MIN(pright, BTN_EXTRA_CLICK_AREA_MAX);
1217 ptop = LV_MATH_MIN(ptop, BTN_EXTRA_CLICK_AREA_MAX);
1218 pbottom = LV_MATH_MIN(pbottom, BTN_EXTRA_CLICK_AREA_MAX);
1219
1220 for(i = 0; i < ext->btn_cnt; i++) {
1221 lv_area_copy(&btn_area, &ext->button_areas[i]);
1222 if(btn_area.x1 <= pleft) btn_area.x1 += btnm_cords.x1 - LV_MATH_MIN(pleft, BTN_EXTRA_CLICK_AREA_MAX);
1223 else btn_area.x1 += btnm_cords.x1 - pinner;
1224
1225 if(btn_area.y1 <= ptop) btn_area.y1 += btnm_cords.y1 - LV_MATH_MIN(ptop, BTN_EXTRA_CLICK_AREA_MAX);
1226 else btn_area.y1 += btnm_cords.y1 - pinner;
1227
1228 if(btn_area.x2 >= w - pright - 2) btn_area.x2 += btnm_cords.x1 + LV_MATH_MIN(pright,
1229 BTN_EXTRA_CLICK_AREA_MAX); /*-2 for rounding error*/
1230 else btn_area.x2 += btnm_cords.x1 + pinner;
1231
1232 if(btn_area.y2 >= h - pbottom - 2) btn_area.y2 += btnm_cords.y1 + LV_MATH_MIN(pbottom,
1233 BTN_EXTRA_CLICK_AREA_MAX); /*-2 for rounding error*/
1234 else btn_area.y2 += btnm_cords.y1 + pinner;
1235
1236 if(_lv_area_is_point_on(&btn_area, p, 0) != false) {
1237 break;
1238 }
1239 }
1240
1241 if(i == ext->btn_cnt) i = LV_BTNMATRIX_BTN_NONE;
1242
1243 return i;
1244 }
1245
invalidate_button_area(const lv_obj_t * btnm,uint16_t btn_idx)1246 static void invalidate_button_area(const lv_obj_t * btnm, uint16_t btn_idx)
1247 {
1248 if(btn_idx == LV_BTNMATRIX_BTN_NONE) return;
1249
1250 lv_area_t btn_area;
1251 lv_area_t btnm_area;
1252
1253 lv_btnmatrix_ext_t * ext = lv_obj_get_ext_attr(btnm);
1254 lv_area_copy(&btn_area, &ext->button_areas[btn_idx]);
1255 lv_obj_get_coords(btnm, &btnm_area);
1256
1257 /* Convert relative coordinates to absolute */
1258 btn_area.x1 += btnm_area.x1;
1259 btn_area.y1 += btnm_area.y1;
1260 btn_area.x2 += btnm_area.x1;
1261 btn_area.y2 += btnm_area.y1;
1262
1263 lv_obj_invalidate_area(btnm, &btn_area);
1264 }
1265
1266 /**
1267 * Compares two button matrix maps for equality
1268 * @param map1 map to compare
1269 * @param map2 map to compare
1270 * @return true if maps are identical in length and content
1271 */
maps_are_identical(const char ** map1,const char ** map2)1272 static bool maps_are_identical(const char ** map1, const char ** map2)
1273 {
1274 if(map1 == map2) return true;
1275 if(map1 == NULL || map2 == NULL) return map1 == map2;
1276
1277 uint16_t i = 0;
1278 while(map1[i][0] != '\0' && map2[i][0] != '\0') {
1279 if(strcmp(map1[i], map2[i]) != 0) return false;
1280 i++;
1281 }
1282 return map1[i][0] == '\0' && map2[i][0] == '\0';
1283 }
1284
1285 /**
1286 * Enforces a single button being toggled on the button matrix.
1287 * It simply clears the toggle flag on other buttons.
1288 * @param btnm Button matrix object
1289 * @param btn_idx Button that should remain toggled
1290 */
make_one_button_toggled(lv_obj_t * btnm,uint16_t btn_idx)1291 static void make_one_button_toggled(lv_obj_t * btnm, uint16_t btn_idx)
1292 {
1293 /*Save whether the button was toggled*/
1294 bool was_toggled = lv_btnmatrix_get_btn_ctrl(btnm, btn_idx, LV_BTNMATRIX_CTRL_CHECK_STATE);
1295
1296 lv_btnmatrix_clear_btn_ctrl_all(btnm, LV_BTNMATRIX_CTRL_CHECK_STATE);
1297
1298 if(was_toggled) lv_btnmatrix_set_btn_ctrl(btnm, btn_idx, LV_BTNMATRIX_CTRL_CHECK_STATE);
1299 }
1300
1301 #endif
1302