mirror of
https://github.com/lvgl/lvgl.git
synced 2025-01-28 07:03:00 +08:00
feat(widgets): add menu widget (#2603)
* add menu widget * Update lv_example_widgets.h * fix errors * Update lv_menu.c * try to fix errors * micropython * Fix colons * Simplify and optimise * Refactor * Update lv_example_menu_3.c * Update lv_example_menu_3.c * Add simple micropython examples * Improvements * Automatically set clickable flags * Custom header example * Include example * Refactor again * Fix error * Fix error * Add back micropython example * Hide back btn by default * Add config * Fix spacing * Fix spacing * Docs * Update lv_theme_default.c * Remove shaded text * Improve clarity * Create index.rst * Update custom header example * Change lv_menu_set_mode_sidebar to lv_menu_set_sidebar_page * Fix unused variable * Added ability to set title to page * Flex * Simplify sidebar check * Rename mode and update header btn * Run lv_conf_internal_gen.py * Run code-format.sh * Add contributors * Micropython example 3 * Micropython example 4 * Improve docs
This commit is contained in:
parent
1c64b78866
commit
ad947d3085
3
Kconfig
3
Kconfig
@ -775,6 +775,9 @@ menu "LVGL configuration"
|
||||
config LV_USE_LIST
|
||||
bool "List."
|
||||
default y if !LV_CONF_MINIMAL
|
||||
config LV_USE_MENU
|
||||
bool "Menu."
|
||||
default y if !LV_CONF_MINIMAL
|
||||
config LV_USE_METER
|
||||
bool "Meter."
|
||||
default y if !LV_CONF_MINIMAL
|
||||
|
@ -51,6 +51,7 @@ widgets = {
|
||||
"led":"LED",
|
||||
"line":"Line",
|
||||
"list":"List",
|
||||
"menu":"Menu",
|
||||
"meter":"Meter",
|
||||
"msgbox":"Message box",
|
||||
"roller":"Roller",
|
||||
|
@ -17,6 +17,7 @@
|
||||
keyboard
|
||||
led
|
||||
list
|
||||
menu
|
||||
meter
|
||||
msgbox
|
||||
span
|
||||
|
94
docs/widgets/extra/menu.md
Normal file
94
docs/widgets/extra/menu.md
Normal file
@ -0,0 +1,94 @@
|
||||
```eval_rst
|
||||
.. include:: /header.rst
|
||||
:github_url: |github_link_base|/widgets/extra/menu.md
|
||||
```
|
||||
# Menu (lv_menu)
|
||||
|
||||
## Overview
|
||||
The menu widget can be used to easily create multi-level menus. It handles the traversal between pages automatically.
|
||||
|
||||
## Parts and Styles
|
||||
The menu widget is built from the following objects:
|
||||
- Main container: lv_menu_main_cont
|
||||
- Main header: lv_menu_main_header_cont
|
||||
- Back btn: [lv_btn](/widgets/core/btn)
|
||||
- Back btn icon: [lv_img](/widgets/core/img)
|
||||
- Main page: lv_menu_page
|
||||
- Sidebar container: lv_menu_sidebar_cont
|
||||
- Sidebar header: lv_menu_sidebar_header_cont
|
||||
- Back btn: [lv_btn](/widgets/core/btn)
|
||||
- Back btn icon: [lv_img](/widgets/core/img)
|
||||
- Sidebar page: lv_menu_page
|
||||
|
||||
## Usage
|
||||
|
||||
### Create a menu
|
||||
`lv_menu_create(parent)` creates a new empty menu.
|
||||
|
||||
### Header mode
|
||||
The following header modes exist:
|
||||
- `LV_MENU_HEADER_TOP_FIXED` Header is positioned at the top.
|
||||
- `LV_MENU_HEADER_TOP_UNFIXED` Header is positioned at the top and can be scrolled out of view.
|
||||
- `LV_MENU_HEADER_BOTTOM_FIXED` Header is positioned at the bottom.
|
||||
|
||||
You can set header modes with `lv_menu_set_mode_header(menu, LV_MENU_HEADER...)`.
|
||||
|
||||
### Root back button mode
|
||||
The following root back button modes exist:
|
||||
- `LV_MENU_ROOT_BACK_BTN_DISABLED`
|
||||
- `LV_MENU_ROOT_BACK_BTN_ENABLED`
|
||||
|
||||
You can set root back button modes with `lv_menu_set_mode_root_back_btn(menu, LV_MENU_ROOT_BACK_BTN...)`.
|
||||
|
||||
### Create a menu page
|
||||
`lv_menu_page_create(menu, title)` creates a new empty menu page.
|
||||
You can add any widgets to the page.
|
||||
|
||||
### Set a menu page in the main area
|
||||
Once a menu page has been created, you can set it to the main area with `lv_menu_set_page(menu, page)`. NULL to clear main and clear menu history.
|
||||
|
||||
### Set a menu page in the sidebar
|
||||
Once a menu page has been created, you can set it to the sidebar with `lv_menu_set_sidebar_page(menu, page)`. NULL to clear sidebar.
|
||||
|
||||
### Linking between menu pages
|
||||
For instance, you have created a btn obj in the main page. When you click the btn obj, you want it to open up a new page, use `lv_menu_set_load_page_event(menu, obj, new page)`.
|
||||
|
||||
### Create a menu container, section, separator
|
||||
The following objects can be created so that it is easier to style the menu:
|
||||
|
||||
`lv_menu_cont_create(parent page)` creates a new empty container.
|
||||
|
||||
`lv_menu_section_create(parent page)` creates a new empty section.
|
||||
|
||||
`lv_menu_separator_create(parent page)` creates a separator.
|
||||
|
||||
## Events
|
||||
- `LV_EVENT_VALUE_CHANGED` Sent when a page is shown.
|
||||
- `lv_menu_get_cur_main_page(menu)` returns a pointer to menu page that is currently displayed in main.
|
||||
- `lv_menu_get_cur_sidebar_page(menu)` returns a pointer to menu page that is currently displayed in sidebar.
|
||||
- `LV_EVENT_CLICKED` Sent when a back btn in a header from either main or sidebar is clicked. `LV_OBJ_FLAG_EVENT_BUBBLE` is enabled on the buttons so you can add events to the menu itself.
|
||||
- `lv_menu_back_btn_is_root(menu, btn)` to check if btn is root back btn
|
||||
|
||||
See the events of the [Base object](/widgets/obj) too.
|
||||
|
||||
Learn more about [Events](/overview/event).
|
||||
|
||||
## Keys
|
||||
No keys are handled by the menu widget.
|
||||
|
||||
Learn more about [Keys](/overview/indev).
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
```eval_rst
|
||||
.. include:: ../../../examples/widgets/menu/index.rst
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
```eval_rst
|
||||
.. doxygenfile:: lv_menu.h
|
||||
:project: lvgl
|
||||
|
||||
```
|
@ -90,6 +90,12 @@ void lv_example_line_1(void);
|
||||
void lv_example_list_1(void);
|
||||
void lv_example_list_2(void);
|
||||
|
||||
void lv_example_menu_1(void);
|
||||
void lv_example_menu_2(void);
|
||||
void lv_example_menu_3(void);
|
||||
void lv_example_menu_4(void);
|
||||
void lv_example_menu_5(void);
|
||||
|
||||
void lv_example_meter_1(void);
|
||||
void lv_example_meter_2(void);
|
||||
void lv_example_meter_3(void);
|
||||
|
31
examples/widgets/menu/index.rst
Normal file
31
examples/widgets/menu/index.rst
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
Simple Menu
|
||||
""""""""""""""""
|
||||
|
||||
.. lv_example:: widgets/list/lv_example_menu_1
|
||||
:language: c
|
||||
|
||||
Simple Menu with root btn
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
.. lv_example:: widgets/list/lv_example_menu_2
|
||||
:language: c
|
||||
|
||||
Simple Menu with custom header
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
.. lv_example:: widgets/list/lv_example_menu_3
|
||||
:language: c
|
||||
|
||||
Simple Menu with floating btn to add new menu
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
.. lv_example:: widgets/list/lv_example_menu_4
|
||||
:language: c
|
||||
|
||||
Complex Menu
|
||||
""""""""""""""""""""""""""""
|
||||
|
||||
.. lv_example:: widgets/list/lv_example_menu_5
|
||||
:language: c
|
||||
|
40
examples/widgets/menu/lv_example_menu_1.c
Normal file
40
examples/widgets/menu/lv_example_menu_1.c
Normal file
@ -0,0 +1,40 @@
|
||||
#include "../../lv_examples.h"
|
||||
#if LV_USE_MENU && LV_BUILD_EXAMPLES
|
||||
|
||||
void lv_example_menu_1(void)
|
||||
{
|
||||
/*Create a menu object*/
|
||||
lv_obj_t * menu = lv_menu_create(lv_scr_act());
|
||||
lv_obj_set_size(menu, lv_disp_get_hor_res(NULL), lv_disp_get_ver_res(NULL));
|
||||
lv_obj_center(menu);
|
||||
|
||||
lv_obj_t * cont;
|
||||
lv_obj_t * label;
|
||||
|
||||
/*Create a sub page*/
|
||||
lv_obj_t * sub_page = lv_menu_page_create(menu, NULL);
|
||||
|
||||
cont = lv_menu_cont_create(sub_page);
|
||||
label = lv_label_create(cont);
|
||||
lv_label_set_text(label, "Hello, I am hiding here");
|
||||
|
||||
/*Create a main page*/
|
||||
lv_obj_t * main_page = lv_menu_page_create(menu, NULL);
|
||||
|
||||
cont = lv_menu_cont_create(main_page);
|
||||
label = lv_label_create(cont);
|
||||
lv_label_set_text(label, "Item 1");
|
||||
|
||||
cont = lv_menu_cont_create(main_page);
|
||||
label = lv_label_create(cont);
|
||||
lv_label_set_text(label, "Item 2");
|
||||
|
||||
cont = lv_menu_cont_create(main_page);
|
||||
label = lv_label_create(cont);
|
||||
lv_label_set_text(label, "Item 3 (Click me!)");
|
||||
lv_menu_set_load_page_event(menu, cont, sub_page);
|
||||
|
||||
lv_menu_set_page(menu, main_page);
|
||||
}
|
||||
|
||||
#endif
|
28
examples/widgets/menu/lv_example_menu_1.py
Normal file
28
examples/widgets/menu/lv_example_menu_1.py
Normal file
@ -0,0 +1,28 @@
|
||||
# Create a menu object
|
||||
menu = lv.menu(lv.scr_act())
|
||||
menu.set_size(320, 240)
|
||||
menu.center()
|
||||
|
||||
# Create a sub page
|
||||
sub_page = lv.menu_page(menu, None)
|
||||
cont = lv.menu_cont(sub_page)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Hello, I am hiding here")
|
||||
|
||||
# Create a main page
|
||||
main_page = lv.menu_page(menu, None)
|
||||
|
||||
cont = lv.menu_cont(main_page)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Item 1")
|
||||
|
||||
cont = lv.menu_cont(main_page)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Item 2")
|
||||
|
||||
cont = lv.menu_cont(main_page)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Item 3 (Click me!)")
|
||||
menu.set_load_page_event(cont, sub_page)
|
||||
|
||||
menu.set_page(main_page)
|
52
examples/widgets/menu/lv_example_menu_2.c
Normal file
52
examples/widgets/menu/lv_example_menu_2.c
Normal file
@ -0,0 +1,52 @@
|
||||
#include "../../lv_examples.h"
|
||||
#if LV_USE_MENU && LV_USE_MSGBOX && LV_BUILD_EXAMPLES
|
||||
|
||||
static void back_event_handler(lv_event_t * e)
|
||||
{
|
||||
lv_obj_t * obj = lv_event_get_target(e);
|
||||
lv_obj_t * menu = lv_event_get_user_data(e);
|
||||
|
||||
if(lv_menu_back_btn_is_root(menu, obj)) {
|
||||
lv_obj_t * mbox1 = lv_msgbox_create(NULL, "Hello", "Root back btn click.", NULL, true);
|
||||
lv_obj_center(mbox1);
|
||||
}
|
||||
}
|
||||
|
||||
void lv_example_menu_2(void)
|
||||
{
|
||||
lv_obj_t * menu = lv_menu_create(lv_scr_act());
|
||||
lv_menu_set_mode_root_back_btn(menu, LV_MENU_ROOT_BACK_BTN_ENABLED);
|
||||
lv_obj_add_event_cb(menu, back_event_handler, LV_EVENT_CLICKED, menu);
|
||||
lv_obj_set_size(menu, lv_disp_get_hor_res(NULL), lv_disp_get_ver_res(NULL));
|
||||
lv_obj_center(menu);
|
||||
|
||||
lv_obj_t * cont;
|
||||
lv_obj_t * label;
|
||||
|
||||
/*Create a sub page*/
|
||||
lv_obj_t * sub_page = lv_menu_page_create(menu, NULL);
|
||||
|
||||
cont = lv_menu_cont_create(sub_page);
|
||||
label = lv_label_create(cont);
|
||||
lv_label_set_text(label, "Hello, I am hiding here");
|
||||
|
||||
/*Create a main page*/
|
||||
lv_obj_t * main_page = lv_menu_page_create(menu, NULL);
|
||||
|
||||
cont = lv_menu_cont_create(main_page);
|
||||
label = lv_label_create(cont);
|
||||
lv_label_set_text(label, "Item 1");
|
||||
|
||||
cont = lv_menu_cont_create(main_page);
|
||||
label = lv_label_create(cont);
|
||||
lv_label_set_text(label, "Item 2");
|
||||
|
||||
cont = lv_menu_cont_create(main_page);
|
||||
label = lv_label_create(cont);
|
||||
lv_label_set_text(label, "Item 3 (Click me!)");
|
||||
lv_menu_set_load_page_event(menu, cont, sub_page);
|
||||
|
||||
lv_menu_set_page(menu, main_page);
|
||||
}
|
||||
|
||||
#endif
|
36
examples/widgets/menu/lv_example_menu_2.py
Normal file
36
examples/widgets/menu/lv_example_menu_2.py
Normal file
@ -0,0 +1,36 @@
|
||||
def back_event_handler(e):
|
||||
obj = e.get_target()
|
||||
if menu.back_btn_is_root(obj):
|
||||
mbox1 = lv.msgbox(lv.scr_act(), "Hello", "Root back btn click.", None, True)
|
||||
mbox1.center()
|
||||
|
||||
# Create a menu object
|
||||
menu = lv.menu(lv.scr_act())
|
||||
menu.set_mode_root_back_btn(lv.menu.ROOT_BACK_BTN.ENABLED)
|
||||
menu.add_event_cb(back_event_handler, lv.EVENT.CLICKED, None)
|
||||
menu.set_size(320, 240)
|
||||
menu.center()
|
||||
|
||||
# Create a sub page
|
||||
sub_page = lv.menu_page(menu, None)
|
||||
cont = lv.menu_cont(sub_page)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Hello, I am hiding here")
|
||||
|
||||
# Create a main page
|
||||
main_page = lv.menu_page(menu, None)
|
||||
|
||||
cont = lv.menu_cont(main_page)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Item 1")
|
||||
|
||||
cont = lv.menu_cont(main_page)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Item 2")
|
||||
|
||||
cont = lv.menu_cont(main_page)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Item 3 (Click me!)")
|
||||
menu.set_load_page_event(cont, sub_page)
|
||||
|
||||
menu.set_page(main_page)
|
59
examples/widgets/menu/lv_example_menu_3.c
Normal file
59
examples/widgets/menu/lv_example_menu_3.c
Normal file
@ -0,0 +1,59 @@
|
||||
#include "../../lv_examples.h"
|
||||
#if LV_USE_MENU && LV_USE_USER_DATA && LV_BUILD_EXAMPLES
|
||||
|
||||
void lv_example_menu_3(void)
|
||||
{
|
||||
/*Create a menu object*/
|
||||
lv_obj_t * menu = lv_menu_create(lv_scr_act());
|
||||
lv_obj_set_size(menu, lv_disp_get_hor_res(NULL), lv_disp_get_ver_res(NULL));
|
||||
lv_obj_center(menu);
|
||||
|
||||
/*Modify the header*/
|
||||
lv_obj_t * back_btn = lv_menu_get_main_header_back_btn(menu);
|
||||
lv_obj_t * back_btn_label = lv_label_create(back_btn);
|
||||
lv_label_set_text(back_btn_label, "Back");
|
||||
|
||||
lv_obj_t * cont;
|
||||
lv_obj_t * label;
|
||||
|
||||
/*Create sub pages*/
|
||||
lv_obj_t * sub_1_page = lv_menu_page_create(menu, "Page 1");
|
||||
|
||||
cont = lv_menu_cont_create(sub_1_page);
|
||||
label = lv_label_create(cont);
|
||||
lv_label_set_text(label, "Hello, I am hiding here");
|
||||
|
||||
lv_obj_t * sub_2_page = lv_menu_page_create(menu, "Page 2");
|
||||
|
||||
cont = lv_menu_cont_create(sub_2_page);
|
||||
label = lv_label_create(cont);
|
||||
lv_label_set_text(label, "Hello, I am hiding here");
|
||||
|
||||
lv_obj_t * sub_3_page = lv_menu_page_create(menu, "Page 3");
|
||||
|
||||
cont = lv_menu_cont_create(sub_3_page);
|
||||
label = lv_label_create(cont);
|
||||
lv_label_set_text(label, "Hello, I am hiding here");
|
||||
|
||||
/*Create a main page*/
|
||||
lv_obj_t * main_page = lv_menu_page_create(menu, NULL);
|
||||
|
||||
cont = lv_menu_cont_create(main_page);
|
||||
label = lv_label_create(cont);
|
||||
lv_label_set_text(label, "Item 1 (Click me!)");
|
||||
lv_menu_set_load_page_event(menu, cont, sub_1_page);
|
||||
|
||||
cont = lv_menu_cont_create(main_page);
|
||||
label = lv_label_create(cont);
|
||||
lv_label_set_text(label, "Item 2 (Click me!)");
|
||||
lv_menu_set_load_page_event(menu, cont, sub_2_page);
|
||||
|
||||
cont = lv_menu_cont_create(main_page);
|
||||
label = lv_label_create(cont);
|
||||
lv_label_set_text(label, "Item 3 (Click me!)");
|
||||
lv_menu_set_load_page_event(menu, cont, sub_3_page);
|
||||
|
||||
lv_menu_set_page(menu, main_page);
|
||||
}
|
||||
|
||||
#endif
|
43
examples/widgets/menu/lv_example_menu_3.py
Normal file
43
examples/widgets/menu/lv_example_menu_3.py
Normal file
@ -0,0 +1,43 @@
|
||||
# Create a menu object
|
||||
menu = lv.menu(lv.scr_act())
|
||||
menu.set_size(320, 240)
|
||||
menu.center()
|
||||
|
||||
# Create sub pages
|
||||
sub_page_1 = lv.menu_page(menu, "Page 1")
|
||||
|
||||
cont = lv.menu_cont(sub_page_1)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Hello, I am hiding here")
|
||||
|
||||
sub_page_2 = lv.menu_page(menu, "Page 2")
|
||||
|
||||
cont = lv.menu_cont(sub_page_2)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Hello, I am hiding here")
|
||||
|
||||
sub_page_3 = lv.menu_page(menu, "Page 3")
|
||||
|
||||
cont = lv.menu_cont(sub_page_3)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Hello, I am hiding here")
|
||||
|
||||
# Create a main page
|
||||
main_page = lv.menu_page(menu, None)
|
||||
|
||||
cont = lv.menu_cont(main_page)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Item 1 (Click me!)")
|
||||
menu.set_load_page_event(cont, sub_page_1)
|
||||
|
||||
cont = lv.menu_cont(main_page)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Item 2 (Click me!)")
|
||||
menu.set_load_page_event(cont, sub_page_2)
|
||||
|
||||
cont = lv.menu_cont(main_page)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Item 3 (Click me!)")
|
||||
menu.set_load_page_event(cont, sub_page_3)
|
||||
|
||||
menu.set_page(main_page)
|
70
examples/widgets/menu/lv_example_menu_4.c
Normal file
70
examples/widgets/menu/lv_example_menu_4.c
Normal file
@ -0,0 +1,70 @@
|
||||
#include "../../lv_examples.h"
|
||||
#if LV_USE_MENU && LV_BUILD_EXAMPLES
|
||||
|
||||
static uint32_t btn_cnt = 1;
|
||||
static lv_obj_t * main_page;
|
||||
static lv_obj_t * menu;
|
||||
|
||||
static void float_btn_event_cb(lv_event_t * e)
|
||||
{
|
||||
LV_UNUSED(e);
|
||||
|
||||
btn_cnt++;
|
||||
|
||||
lv_obj_t * cont;
|
||||
lv_obj_t * label;
|
||||
|
||||
lv_obj_t * sub_page = lv_menu_page_create(menu, NULL);
|
||||
|
||||
cont = lv_menu_cont_create(sub_page);
|
||||
label= lv_label_create(cont);
|
||||
lv_label_set_text_fmt(label, "Hello, I am hiding inside %i", btn_cnt);
|
||||
|
||||
cont = lv_menu_cont_create(main_page);
|
||||
label= lv_label_create(cont);
|
||||
lv_label_set_text_fmt(label, "Item %i", btn_cnt);
|
||||
lv_menu_set_load_page_event(menu, cont, sub_page);
|
||||
|
||||
lv_obj_scroll_to_view_recursive(cont, LV_ANIM_ON);
|
||||
}
|
||||
|
||||
void lv_example_menu_4(void)
|
||||
{
|
||||
/*Create a menu object*/
|
||||
menu = lv_menu_create(lv_scr_act());
|
||||
lv_obj_set_size(menu, lv_disp_get_hor_res(NULL), lv_disp_get_ver_res(NULL));
|
||||
lv_obj_center(menu);
|
||||
|
||||
lv_obj_t * cont;
|
||||
lv_obj_t * label;
|
||||
|
||||
/*Create a sub page*/
|
||||
lv_obj_t * sub_page = lv_menu_page_create(menu, NULL);
|
||||
|
||||
cont = lv_menu_cont_create(sub_page);
|
||||
label = lv_label_create(cont);
|
||||
lv_label_set_text(label, "Hello, I am hiding inside the first item");
|
||||
|
||||
/*Create a main page*/
|
||||
main_page = lv_menu_page_create(menu, NULL);
|
||||
|
||||
cont = lv_menu_cont_create(main_page);
|
||||
label = lv_label_create(cont);
|
||||
lv_label_set_text(label, "Item 1");
|
||||
lv_menu_set_load_page_event(menu, cont, sub_page);
|
||||
|
||||
lv_menu_set_page(menu, main_page);
|
||||
|
||||
/*Create floating btn*/
|
||||
lv_obj_t * float_btn = lv_btn_create(lv_scr_act());
|
||||
lv_obj_set_size(float_btn, 50, 50);
|
||||
lv_obj_add_flag(float_btn, LV_OBJ_FLAG_FLOATING);
|
||||
lv_obj_align(float_btn, LV_ALIGN_BOTTOM_RIGHT, -10, -10);
|
||||
lv_obj_add_event_cb(float_btn, float_btn_event_cb, LV_EVENT_CLICKED, menu);
|
||||
lv_obj_set_style_radius(float_btn, LV_RADIUS_CIRCLE, 0);
|
||||
lv_obj_set_style_bg_img_src(float_btn, LV_SYMBOL_PLUS, 0);
|
||||
lv_obj_set_style_text_font(float_btn, lv_theme_get_font_large(float_btn), 0);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
47
examples/widgets/menu/lv_example_menu_4.py
Normal file
47
examples/widgets/menu/lv_example_menu_4.py
Normal file
@ -0,0 +1,47 @@
|
||||
btn_cnt = 1
|
||||
|
||||
def float_btn_event_cb(e):
|
||||
global btn_cnt
|
||||
btn_cnt += 1
|
||||
|
||||
sub_page = lv.menu_page(menu, None)
|
||||
|
||||
cont = lv.menu_cont(sub_page)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Hello, I am hiding inside {:d}".format(btn_cnt))
|
||||
|
||||
cont = lv.menu_cont(main_page)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Item {:d}".format(btn_cnt))
|
||||
menu.set_load_page_event(cont, sub_page);
|
||||
|
||||
# Create a menu object
|
||||
menu = lv.menu(lv.scr_act())
|
||||
menu.set_size(320, 240)
|
||||
menu.center()
|
||||
|
||||
# Create a sub page
|
||||
sub_page = lv.menu_page(menu, None)
|
||||
|
||||
cont = lv.menu_cont(sub_page)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Hello, I am hiding inside the first item")
|
||||
|
||||
# Create a main page
|
||||
main_page = lv.menu_page(menu, None)
|
||||
|
||||
cont = lv.menu_cont(main_page)
|
||||
label = lv.label(cont)
|
||||
label.set_text("Item 1")
|
||||
menu.set_load_page_event(cont, sub_page)
|
||||
|
||||
menu.set_page(main_page)
|
||||
|
||||
float_btn = lv.btn(lv.scr_act())
|
||||
float_btn.set_size(50, 50)
|
||||
float_btn.add_flag(lv.obj.FLAG.FLOATING)
|
||||
float_btn.align(lv.ALIGN.BOTTOM_RIGHT, -10, -10)
|
||||
float_btn.add_event_cb(float_btn_event_cb, lv.EVENT.CLICKED, None)
|
||||
float_btn.set_style_radius(lv.RADIUS.CIRCLE, 0)
|
||||
float_btn.set_style_bg_img_src(lv.SYMBOL.PLUS, 0)
|
||||
float_btn.set_style_text_font(lv.theme_get_font_large(float_btn), 0)
|
194
examples/widgets/menu/lv_example_menu_5.c
Normal file
194
examples/widgets/menu/lv_example_menu_5.c
Normal file
@ -0,0 +1,194 @@
|
||||
#include "../../lv_examples.h"
|
||||
#if LV_USE_MENU && LV_USE_MSGBOX && LV_BUILD_EXAMPLES
|
||||
|
||||
enum {
|
||||
LV_MENU_ITEM_BUILDER_VARIANT_1,
|
||||
LV_MENU_ITEM_BUILDER_VARIANT_2
|
||||
};
|
||||
typedef uint8_t lv_menu_builder_variant_t;
|
||||
|
||||
static void back_event_handler(lv_event_t * e);
|
||||
static void switch_handler(lv_event_t * e);
|
||||
lv_obj_t * root_page;
|
||||
static lv_obj_t * create_text(lv_obj_t * parent, const char * icon, const char * txt,
|
||||
lv_menu_builder_variant_t builder_variant);
|
||||
static lv_obj_t * create_slider(lv_obj_t * parent,
|
||||
const char * icon, const char * txt, int32_t min, int32_t max, int32_t val);
|
||||
static lv_obj_t * create_switch(lv_obj_t * parent,
|
||||
const char * icon, const char * txt, bool chk);
|
||||
|
||||
void lv_example_menu_5(void)
|
||||
{
|
||||
lv_obj_t * menu = lv_menu_create(lv_scr_act());
|
||||
|
||||
lv_color_t bg_color = lv_obj_get_style_bg_color(menu, 0);
|
||||
if(lv_color_brightness(bg_color) > 127) {
|
||||
lv_obj_set_style_bg_color(menu, lv_color_darken(lv_obj_get_style_bg_color(menu, 0), 10), 0);
|
||||
}else{
|
||||
lv_obj_set_style_bg_color(menu, lv_color_darken(lv_obj_get_style_bg_color(menu, 0), 50), 0);
|
||||
}
|
||||
lv_menu_set_mode_root_back_btn(menu, LV_MENU_ROOT_BACK_BTN_ENABLED);
|
||||
lv_obj_add_event_cb(menu, back_event_handler, LV_EVENT_CLICKED, menu);
|
||||
lv_obj_set_size(menu, lv_disp_get_hor_res(NULL), lv_disp_get_ver_res(NULL));
|
||||
lv_obj_center(menu);
|
||||
|
||||
lv_obj_t * cont;
|
||||
lv_obj_t * section;
|
||||
|
||||
/*Create sub pages*/
|
||||
lv_obj_t * sub_mechanics_page = lv_menu_page_create(menu, NULL);
|
||||
lv_obj_set_style_pad_hor(sub_mechanics_page, lv_obj_get_style_pad_left(lv_menu_get_main_header(menu), 0), 0);
|
||||
lv_menu_separator_create(sub_mechanics_page);
|
||||
section = lv_menu_section_create(sub_mechanics_page);
|
||||
create_slider(section, LV_SYMBOL_SETTINGS, "Velocity", 0, 150, 120);
|
||||
create_slider(section, LV_SYMBOL_SETTINGS, "Acceleration", 0, 150, 50);
|
||||
create_slider(section, LV_SYMBOL_SETTINGS, "Weight limit", 0, 150, 80);
|
||||
|
||||
lv_obj_t * sub_sound_page = lv_menu_page_create(menu, NULL);
|
||||
lv_obj_set_style_pad_hor(sub_sound_page, lv_obj_get_style_pad_left(lv_menu_get_main_header(menu), 0), 0);
|
||||
lv_menu_separator_create(sub_sound_page);
|
||||
section = lv_menu_section_create(sub_sound_page);
|
||||
create_switch(section, LV_SYMBOL_AUDIO, "Sound", false);
|
||||
|
||||
lv_obj_t * sub_display_page = lv_menu_page_create(menu, NULL);
|
||||
lv_obj_set_style_pad_hor(sub_display_page, lv_obj_get_style_pad_left(lv_menu_get_main_header(menu), 0), 0);
|
||||
lv_menu_separator_create(sub_display_page);
|
||||
section = lv_menu_section_create(sub_display_page);
|
||||
create_slider(section, LV_SYMBOL_SETTINGS, "Brightness", 0, 150, 100);
|
||||
|
||||
lv_obj_t * sub_software_info_page = lv_menu_page_create(menu, NULL);
|
||||
lv_obj_set_style_pad_hor(sub_software_info_page, lv_obj_get_style_pad_left(lv_menu_get_main_header(menu), 0), 0);
|
||||
section = lv_menu_section_create(sub_software_info_page);
|
||||
create_text(section, NULL, "Version 1.0", LV_MENU_ITEM_BUILDER_VARIANT_1);
|
||||
|
||||
lv_obj_t * sub_legal_info_page = lv_menu_page_create(menu, NULL);
|
||||
lv_obj_set_style_pad_hor(sub_legal_info_page, lv_obj_get_style_pad_left(lv_menu_get_main_header(menu), 0), 0);
|
||||
section = lv_menu_section_create(sub_legal_info_page);
|
||||
for(uint32_t i=0; i<15; i++){
|
||||
create_text(section, NULL, "This is a long long long long long long long long long text, if it is long enough it may scroll.", LV_MENU_ITEM_BUILDER_VARIANT_1);
|
||||
}
|
||||
|
||||
lv_obj_t * sub_about_page = lv_menu_page_create(menu, NULL);
|
||||
lv_obj_set_style_pad_hor(sub_about_page, lv_obj_get_style_pad_left(lv_menu_get_main_header(menu), 0), 0);
|
||||
lv_menu_separator_create(sub_about_page);
|
||||
section = lv_menu_section_create(sub_about_page);
|
||||
cont = create_text(section, NULL, "Software information", LV_MENU_ITEM_BUILDER_VARIANT_1);
|
||||
lv_menu_set_load_page_event(menu, cont, sub_software_info_page);
|
||||
cont = create_text(section, NULL, "Legal information", LV_MENU_ITEM_BUILDER_VARIANT_1);
|
||||
lv_menu_set_load_page_event(menu, cont, sub_legal_info_page);
|
||||
|
||||
lv_obj_t * sub_menu_mode_page = lv_menu_page_create(menu, NULL);
|
||||
lv_obj_set_style_pad_hor(sub_menu_mode_page, lv_obj_get_style_pad_left(lv_menu_get_main_header(menu), 0), 0);
|
||||
lv_menu_separator_create(sub_menu_mode_page);
|
||||
section = lv_menu_section_create(sub_menu_mode_page);
|
||||
cont = create_switch(section, LV_SYMBOL_AUDIO, "Sidebar enable", true);
|
||||
lv_obj_add_event_cb(lv_obj_get_child(cont, 2), switch_handler, LV_EVENT_VALUE_CHANGED, menu);
|
||||
|
||||
/*Create a root page*/
|
||||
root_page = lv_menu_page_create(menu, "Settings");
|
||||
lv_obj_set_style_pad_hor(root_page, lv_obj_get_style_pad_left(lv_menu_get_main_header(menu), 0), 0);
|
||||
section = lv_menu_section_create(root_page);
|
||||
cont = create_text(section, LV_SYMBOL_SETTINGS, "Mechanics", LV_MENU_ITEM_BUILDER_VARIANT_1);
|
||||
lv_menu_set_load_page_event(menu, cont, sub_mechanics_page);
|
||||
cont = create_text(section, LV_SYMBOL_AUDIO, "Sound", LV_MENU_ITEM_BUILDER_VARIANT_1);
|
||||
lv_menu_set_load_page_event(menu, cont, sub_sound_page);
|
||||
cont = create_text(section, LV_SYMBOL_SETTINGS, "Display", LV_MENU_ITEM_BUILDER_VARIANT_1);
|
||||
lv_menu_set_load_page_event(menu, cont, sub_display_page);
|
||||
|
||||
create_text(root_page, NULL, "Others", LV_MENU_ITEM_BUILDER_VARIANT_1);
|
||||
section = lv_menu_section_create(root_page);
|
||||
cont = create_text(section, NULL, "About", LV_MENU_ITEM_BUILDER_VARIANT_1);
|
||||
lv_menu_set_load_page_event(menu, cont, sub_about_page);
|
||||
cont = create_text(section, LV_SYMBOL_SETTINGS, "Menu mode", LV_MENU_ITEM_BUILDER_VARIANT_1);
|
||||
lv_menu_set_load_page_event(menu, cont, sub_menu_mode_page);
|
||||
|
||||
lv_menu_set_sidebar_page(menu, root_page);
|
||||
|
||||
lv_event_send(lv_obj_get_child(lv_obj_get_child(lv_menu_get_cur_sidebar_page(menu), 0), 0), LV_EVENT_CLICKED, NULL);
|
||||
}
|
||||
|
||||
static void back_event_handler(lv_event_t * e)
|
||||
{
|
||||
lv_obj_t * obj = lv_event_get_target(e);
|
||||
lv_obj_t * menu = lv_event_get_user_data(e);
|
||||
|
||||
if(lv_menu_back_btn_is_root(menu, obj)) {
|
||||
lv_obj_t * mbox1 = lv_msgbox_create(NULL, "Hello", "Root back btn click.", NULL, true);
|
||||
lv_obj_center(mbox1);
|
||||
}
|
||||
}
|
||||
|
||||
static void switch_handler(lv_event_t * e)
|
||||
{
|
||||
lv_event_code_t code = lv_event_get_code(e);
|
||||
lv_obj_t * menu = lv_event_get_user_data(e);
|
||||
lv_obj_t * obj = lv_event_get_target(e);
|
||||
if(code == LV_EVENT_VALUE_CHANGED) {
|
||||
if(lv_obj_has_state(obj, LV_STATE_CHECKED)) {
|
||||
lv_menu_set_page(menu, NULL);
|
||||
lv_menu_set_sidebar_page(menu, root_page);
|
||||
lv_event_send(lv_obj_get_child(lv_obj_get_child(lv_menu_get_cur_sidebar_page(menu), 0), 0), LV_EVENT_CLICKED, NULL);
|
||||
}else {
|
||||
lv_menu_set_sidebar_page(menu, NULL);
|
||||
lv_menu_clear_history(menu); /* Clear history because we will be showing the root page later */
|
||||
lv_menu_set_page(menu, root_page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static lv_obj_t * create_text(lv_obj_t * parent, const char * icon, const char * txt,
|
||||
lv_menu_builder_variant_t builder_variant)
|
||||
{
|
||||
lv_obj_t * obj = lv_menu_cont_create(parent);
|
||||
|
||||
lv_obj_t * img = NULL;
|
||||
lv_obj_t * label = NULL;
|
||||
|
||||
if(icon) {
|
||||
img = lv_img_create(obj);
|
||||
lv_img_set_src(img, icon);
|
||||
}
|
||||
|
||||
if(txt) {
|
||||
label = lv_label_create(obj);
|
||||
lv_label_set_text(label, txt);
|
||||
lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR);
|
||||
lv_obj_set_flex_grow(label, 1);
|
||||
}
|
||||
|
||||
if(builder_variant == LV_MENU_ITEM_BUILDER_VARIANT_2 && icon && txt) {
|
||||
lv_obj_add_flag(img, LV_OBJ_FLAG_FLEX_IN_NEW_TRACK);
|
||||
lv_obj_swap(img, label);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
static lv_obj_t * create_slider(lv_obj_t * parent, const char * icon, const char * txt, int32_t min, int32_t max, int32_t val)
|
||||
{
|
||||
lv_obj_t * obj = create_text(parent, icon, txt, LV_MENU_ITEM_BUILDER_VARIANT_2);
|
||||
|
||||
lv_obj_t * slider = lv_slider_create(obj);
|
||||
lv_obj_set_flex_grow(slider, 1);
|
||||
lv_slider_set_range(slider, min, max);
|
||||
lv_slider_set_value(slider, val, LV_ANIM_OFF);
|
||||
|
||||
if(icon == NULL) {
|
||||
lv_obj_add_flag(slider, LV_OBJ_FLAG_FLEX_IN_NEW_TRACK);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
static lv_obj_t * create_switch(lv_obj_t * parent, const char * icon, const char * txt, bool chk)
|
||||
{
|
||||
lv_obj_t * obj = create_text(parent, icon, txt, LV_MENU_ITEM_BUILDER_VARIANT_1);
|
||||
|
||||
lv_obj_t * sw = lv_switch_create(obj);
|
||||
lv_obj_add_state(sw, chk ? LV_STATE_CHECKED : 0);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -484,6 +484,8 @@
|
||||
|
||||
#define LV_USE_LIST 1
|
||||
|
||||
#define LV_USE_MENU 1
|
||||
|
||||
#define LV_USE_METER 1
|
||||
|
||||
#define LV_USE_MSGBOX 1
|
||||
|
@ -28,3 +28,4 @@ Here some ideas as inspiration feel free to contribute with ideas too.
|
||||
## Contributors
|
||||
- lv_animimg: @ZhaoQiang-b45475
|
||||
- lv_span: @guoweilkd
|
||||
- lv_menu: @HX2003
|
@ -125,6 +125,11 @@ typedef struct {
|
||||
lv_style_t colorwheel_main;
|
||||
#endif
|
||||
|
||||
#if LV_USE_MENU
|
||||
lv_style_t menu_bg, menu_cont, menu_sidebar_cont, menu_main_cont, menu_page, menu_header_cont, menu_header_btn,
|
||||
menu_section, menu_pressed, menu_separator;
|
||||
#endif
|
||||
|
||||
#if LV_USE_MSGBOX
|
||||
lv_style_t msgbox_bg, msgbox_btn_bg, msgbox_backdrop_bg;
|
||||
#endif
|
||||
@ -454,6 +459,67 @@ static void style_init(void)
|
||||
lv_style_set_text_color(&styles->chart_ticks, lv_palette_main(LV_PALETTE_GREY));
|
||||
#endif
|
||||
|
||||
#if LV_USE_MENU
|
||||
style_init_reset(&styles->menu_bg);
|
||||
lv_style_set_pad_all(&styles->menu_bg, 0);
|
||||
lv_style_set_pad_gap(&styles->menu_bg, 0);
|
||||
lv_style_set_radius(&styles->menu_bg, 0);
|
||||
lv_style_set_clip_corner(&styles->menu_bg, true);
|
||||
lv_style_set_border_side(&styles->menu_bg, LV_BORDER_SIDE_NONE);
|
||||
|
||||
style_init_reset(&styles->menu_section);
|
||||
lv_style_set_radius(&styles->menu_section, RADIUS_DEFAULT);
|
||||
lv_style_set_clip_corner(&styles->menu_section, true);
|
||||
lv_style_set_bg_opa(&styles->menu_section, LV_OPA_COVER);
|
||||
lv_style_set_bg_color(&styles->menu_section, color_card);
|
||||
lv_style_set_text_color(&styles->menu_section, color_text);
|
||||
|
||||
style_init_reset(&styles->menu_cont);
|
||||
lv_style_set_pad_hor(&styles->menu_cont, PAD_SMALL);
|
||||
lv_style_set_pad_ver(&styles->menu_cont, PAD_SMALL);
|
||||
lv_style_set_pad_gap(&styles->menu_cont, PAD_SMALL);
|
||||
lv_style_set_border_width(&styles->menu_cont, lv_disp_dpx(theme.disp, 1));
|
||||
lv_style_set_border_opa(&styles->menu_cont, LV_OPA_10);
|
||||
lv_style_set_border_color(&styles->menu_cont, color_text);
|
||||
lv_style_set_border_side(&styles->menu_cont, LV_BORDER_SIDE_NONE);
|
||||
|
||||
style_init_reset(&styles->menu_sidebar_cont);
|
||||
lv_style_set_pad_all(&styles->menu_sidebar_cont, 0);
|
||||
lv_style_set_pad_gap(&styles->menu_sidebar_cont, 0);
|
||||
lv_style_set_border_width(&styles->menu_sidebar_cont, lv_disp_dpx(theme.disp, 1));
|
||||
lv_style_set_border_opa(&styles->menu_sidebar_cont, LV_OPA_10);
|
||||
lv_style_set_border_color(&styles->menu_sidebar_cont, color_text);
|
||||
lv_style_set_border_side(&styles->menu_sidebar_cont, LV_BORDER_SIDE_RIGHT);
|
||||
|
||||
style_init_reset(&styles->menu_main_cont);
|
||||
lv_style_set_pad_all(&styles->menu_main_cont, 0);
|
||||
lv_style_set_pad_gap(&styles->menu_main_cont, 0);
|
||||
|
||||
style_init_reset(&styles->menu_header_cont);
|
||||
lv_style_set_pad_hor(&styles->menu_header_cont, PAD_SMALL);
|
||||
lv_style_set_pad_ver(&styles->menu_header_cont, PAD_TINY);
|
||||
lv_style_set_pad_gap(&styles->menu_header_cont, PAD_SMALL);
|
||||
|
||||
style_init_reset(&styles->menu_header_btn);
|
||||
lv_style_set_pad_hor(&styles->menu_header_btn, PAD_TINY);
|
||||
lv_style_set_pad_ver(&styles->menu_header_btn, PAD_TINY);
|
||||
lv_style_set_shadow_opa(&styles->menu_header_btn, LV_OPA_TRANSP);
|
||||
lv_style_set_bg_opa(&styles->menu_header_btn, LV_OPA_TRANSP);
|
||||
lv_style_set_text_color(&styles->menu_header_btn, color_text);
|
||||
|
||||
style_init_reset(&styles->menu_page);
|
||||
lv_style_set_pad_hor(&styles->menu_page, 0);
|
||||
lv_style_set_pad_gap(&styles->menu_page, 0);
|
||||
|
||||
style_init_reset(&styles->menu_pressed);
|
||||
lv_style_set_bg_opa(&styles->menu_pressed, LV_OPA_20);
|
||||
lv_style_set_bg_color(&styles->menu_pressed, lv_palette_main(LV_PALETTE_GREY));
|
||||
|
||||
style_init_reset(&styles->menu_separator);
|
||||
lv_style_set_bg_opa(&styles->menu_separator, LV_OPA_TRANSP);
|
||||
lv_style_set_pad_ver(&styles->menu_separator, PAD_TINY);
|
||||
#endif
|
||||
|
||||
#if LV_USE_METER
|
||||
style_init_reset(&styles->meter_marker);
|
||||
lv_style_set_line_width(&styles->meter_marker, lv_disp_dpx(theme.disp, 5));
|
||||
@ -690,6 +756,14 @@ static void theme_apply(lv_theme_t * th, lv_obj_t * obj)
|
||||
#endif
|
||||
lv_obj_add_style(obj, &styles->bg_color_secondary, LV_STATE_CHECKED);
|
||||
lv_obj_add_style(obj, &styles->disabled, LV_STATE_DISABLED);
|
||||
|
||||
#if LV_USE_MENU
|
||||
if(lv_obj_check_type(lv_obj_get_parent(obj), &lv_menu_sidebar_header_cont_class) ||
|
||||
lv_obj_check_type(lv_obj_get_parent(obj), &lv_menu_main_header_cont_class)) {
|
||||
lv_obj_add_style(obj, &styles->menu_header_btn, 0);
|
||||
lv_obj_add_style(obj, &styles->menu_pressed, LV_STATE_PRESSED);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -983,6 +1057,44 @@ static void theme_apply(lv_theme_t * th, lv_obj_t * obj)
|
||||
|
||||
}
|
||||
#endif
|
||||
#if LV_USE_MENU
|
||||
else if(lv_obj_check_type(obj, &lv_menu_class)) {
|
||||
lv_obj_add_style(obj, &styles->card, 0);
|
||||
lv_obj_add_style(obj, &styles->menu_bg, 0);
|
||||
}
|
||||
else if(lv_obj_check_type(obj, &lv_menu_sidebar_cont_class)) {
|
||||
lv_obj_add_style(obj, &styles->menu_sidebar_cont, 0);
|
||||
lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR);
|
||||
lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED);
|
||||
}
|
||||
else if(lv_obj_check_type(obj, &lv_menu_main_cont_class)) {
|
||||
lv_obj_add_style(obj, &styles->menu_main_cont, 0);
|
||||
lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR);
|
||||
lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED);
|
||||
}
|
||||
else if(lv_obj_check_type(obj, &lv_menu_cont_class)) {
|
||||
lv_obj_add_style(obj, &styles->menu_cont, 0);
|
||||
lv_obj_add_style(obj, &styles->menu_pressed, LV_STATE_PRESSED);
|
||||
lv_obj_add_style(obj, &styles->bg_color_primary_muted, LV_STATE_PRESSED | LV_STATE_CHECKED);
|
||||
lv_obj_add_style(obj, &styles->bg_color_primary_muted, LV_STATE_CHECKED);
|
||||
}
|
||||
else if(lv_obj_check_type(obj, &lv_menu_sidebar_header_cont_class) ||
|
||||
lv_obj_check_type(obj, &lv_menu_main_header_cont_class)) {
|
||||
lv_obj_add_style(obj, &styles->menu_header_cont, 0);
|
||||
lv_obj_add_style(obj, &styles->menu_pressed, LV_STATE_PRESSED);
|
||||
}
|
||||
else if(lv_obj_check_type(obj, &lv_menu_page_class)) {
|
||||
lv_obj_add_style(obj, &styles->menu_page, 0);
|
||||
lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR);
|
||||
lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED);
|
||||
}
|
||||
else if(lv_obj_check_type(obj, &lv_menu_section_class)) {
|
||||
lv_obj_add_style(obj, &styles->menu_section, 0);
|
||||
}
|
||||
else if(lv_obj_check_type(obj, &lv_menu_separator_class)) {
|
||||
lv_obj_add_style(obj, &styles->menu_separator, 0);
|
||||
}
|
||||
#endif
|
||||
#if LV_USE_MSGBOX
|
||||
else if(lv_obj_check_type(obj, &lv_msgbox_class)) {
|
||||
lv_obj_add_style(obj, &styles->card, 0);
|
||||
|
@ -20,6 +20,7 @@ extern "C" {
|
||||
#include "chart/lv_chart.h"
|
||||
#include "keyboard/lv_keyboard.h"
|
||||
#include "list/lv_list.h"
|
||||
#include "menu/lv_menu.h"
|
||||
#include "msgbox/lv_msgbox.h"
|
||||
#include "meter/lv_meter.h"
|
||||
#include "spinbox/lv_spinbox.h"
|
||||
|
752
src/extra/widgets/menu/lv_menu.c
Normal file
752
src/extra/widgets/menu/lv_menu.c
Normal file
@ -0,0 +1,752 @@
|
||||
/**
|
||||
* @file lv_menu.c
|
||||
*
|
||||
*/
|
||||
|
||||
/*********************
|
||||
* INCLUDES
|
||||
*********************/
|
||||
#include "lv_menu.h"
|
||||
|
||||
#if LV_USE_MENU
|
||||
|
||||
/*********************
|
||||
* DEFINES
|
||||
*********************/
|
||||
#define MY_CLASS &lv_menu_class
|
||||
|
||||
#include "../../../core/lv_obj.h"
|
||||
#include "../../layouts/flex/lv_flex.h"
|
||||
#include "../../../widgets/lv_label.h"
|
||||
#include "../../../widgets/lv_btn.h"
|
||||
#include "../../../widgets/lv_img.h"
|
||||
|
||||
/**********************
|
||||
* TYPEDEFS
|
||||
**********************/
|
||||
|
||||
/**********************
|
||||
* STATIC PROTOTYPES
|
||||
**********************/
|
||||
static void lv_menu_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
|
||||
static void lv_menu_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
|
||||
static void lv_menu_page_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
|
||||
static void lv_menu_page_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
|
||||
static void lv_menu_cont_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
|
||||
static void lv_menu_section_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
|
||||
|
||||
const lv_obj_class_t lv_menu_class = {
|
||||
.constructor_cb = lv_menu_constructor,
|
||||
.destructor_cb = lv_menu_destructor,
|
||||
.base_class = &lv_obj_class,
|
||||
.width_def = (LV_DPI_DEF * 3) / 2,
|
||||
.height_def = LV_DPI_DEF * 2,
|
||||
.instance_size = sizeof(lv_menu_t)
|
||||
};
|
||||
const lv_obj_class_t lv_menu_page_class = {
|
||||
.constructor_cb = lv_menu_page_constructor,
|
||||
.destructor_cb = lv_menu_page_destructor,
|
||||
.base_class = &lv_obj_class,
|
||||
.width_def = LV_PCT(100),
|
||||
.height_def = LV_SIZE_CONTENT,
|
||||
.instance_size = sizeof(lv_menu_page_t)
|
||||
};
|
||||
|
||||
const lv_obj_class_t lv_menu_cont_class = {
|
||||
.constructor_cb = lv_menu_cont_constructor,
|
||||
.base_class = &lv_obj_class,
|
||||
.width_def = LV_PCT(100),
|
||||
.height_def = LV_SIZE_CONTENT
|
||||
};
|
||||
|
||||
const lv_obj_class_t lv_menu_section_class = {
|
||||
.constructor_cb = lv_menu_section_constructor,
|
||||
.base_class = &lv_obj_class,
|
||||
.width_def = LV_PCT(100),
|
||||
.height_def = LV_SIZE_CONTENT
|
||||
};
|
||||
|
||||
const lv_obj_class_t lv_menu_separator_class = {
|
||||
.base_class = &lv_obj_class,
|
||||
.width_def = LV_SIZE_CONTENT,
|
||||
.height_def = LV_SIZE_CONTENT
|
||||
};
|
||||
|
||||
const lv_obj_class_t lv_menu_sidebar_cont_class = {
|
||||
.base_class = &lv_obj_class
|
||||
};
|
||||
|
||||
const lv_obj_class_t lv_menu_main_cont_class = {
|
||||
.base_class = &lv_obj_class
|
||||
};
|
||||
|
||||
const lv_obj_class_t lv_menu_main_header_cont_class = {
|
||||
.base_class = &lv_obj_class
|
||||
};
|
||||
|
||||
const lv_obj_class_t lv_menu_sidebar_header_cont_class = {
|
||||
.base_class = &lv_obj_class
|
||||
};
|
||||
|
||||
static void lv_menu_refr(lv_obj_t * obj);
|
||||
static void lv_menu_refr_sidebar_header_mode(lv_obj_t * obj);
|
||||
static void lv_menu_refr_main_header_mode(lv_obj_t * obj);
|
||||
static void lv_menu_load_page_event_cb(lv_event_t * e);
|
||||
static void lv_menu_obj_del_event_cb(lv_event_t * e);
|
||||
static void lv_menu_back_event_cb(lv_event_t * e);
|
||||
static void lv_menu_value_changed_event_cb(lv_event_t * e);
|
||||
/**********************
|
||||
* STATIC VARIABLES
|
||||
**********************/
|
||||
|
||||
/**********************
|
||||
* MACROS
|
||||
**********************/
|
||||
|
||||
/**********************
|
||||
* GLOBAL FUNCTIONS
|
||||
**********************/
|
||||
bool lv_menu_item_back_btn_is_root(lv_obj_t * menu, lv_obj_t * obj);
|
||||
void lv_menu_clear_history(lv_obj_t * obj);
|
||||
|
||||
lv_obj_t * lv_menu_create(lv_obj_t * parent)
|
||||
{
|
||||
LV_LOG_INFO("begin");
|
||||
lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
|
||||
lv_obj_class_init_obj(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
lv_obj_t * lv_menu_page_create(lv_obj_t * parent, char * title)
|
||||
{
|
||||
LV_LOG_INFO("begin");
|
||||
lv_obj_t * obj = lv_obj_class_create_obj(&lv_menu_page_class, parent);
|
||||
lv_obj_class_init_obj(obj);
|
||||
|
||||
lv_menu_page_t * page = (lv_menu_page_t *)obj;
|
||||
page->title = title;
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
lv_obj_t * lv_menu_cont_create(lv_obj_t * parent)
|
||||
{
|
||||
LV_LOG_INFO("begin");
|
||||
lv_obj_t * obj = lv_obj_class_create_obj(&lv_menu_cont_class, parent);
|
||||
lv_obj_class_init_obj(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
lv_obj_t * lv_menu_section_create(lv_obj_t * parent)
|
||||
{
|
||||
LV_LOG_INFO("begin");
|
||||
lv_obj_t * obj = lv_obj_class_create_obj(&lv_menu_section_class, parent);
|
||||
lv_obj_class_init_obj(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
lv_obj_t * lv_menu_separator_create(lv_obj_t * parent)
|
||||
{
|
||||
LV_LOG_INFO("begin");
|
||||
lv_obj_t * obj = lv_obj_class_create_obj(&lv_menu_separator_class, parent);
|
||||
lv_obj_class_init_obj(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
void lv_menu_refr(lv_obj_t * obj)
|
||||
{
|
||||
LV_ASSERT_OBJ(obj, MY_CLASS);
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
lv_ll_t * history_ll = &(menu->history_ll);
|
||||
|
||||
/* The current menu */
|
||||
lv_menu_history_t * act_hist = _lv_ll_get_head(history_ll);
|
||||
|
||||
lv_obj_t * page = NULL;
|
||||
|
||||
if(act_hist != NULL) {
|
||||
page = act_hist->page;
|
||||
/* Delete the current item from the history */
|
||||
_lv_ll_remove(history_ll, act_hist);
|
||||
lv_mem_free(act_hist);
|
||||
menu->cur_depth--;
|
||||
}
|
||||
|
||||
/* Set it */
|
||||
lv_menu_set_page(obj, page);
|
||||
}
|
||||
|
||||
/*=====================
|
||||
* Setter functions
|
||||
*====================*/
|
||||
|
||||
void lv_menu_set_page(lv_obj_t * obj, lv_obj_t * page)
|
||||
{
|
||||
LV_ASSERT_OBJ(obj, MY_CLASS);
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
|
||||
/* Hide previous page */
|
||||
if(menu->main_page != NULL) {
|
||||
lv_obj_set_parent(menu->main_page, menu->storage);
|
||||
}
|
||||
|
||||
if(page != NULL) {
|
||||
/* Add a new node */
|
||||
lv_ll_t * history_ll = &(menu->history_ll);
|
||||
lv_menu_history_t * new_node = _lv_ll_ins_head(history_ll);
|
||||
new_node->page = page;
|
||||
menu->cur_depth++;
|
||||
|
||||
/* Place page in main */
|
||||
lv_obj_set_parent(page, menu->main);
|
||||
}
|
||||
else {
|
||||
/* Empty page, clear history */
|
||||
lv_menu_clear_history(obj);
|
||||
}
|
||||
|
||||
menu->main_page = page;
|
||||
|
||||
/* If there is a selected tab, update checked state */
|
||||
if(menu->selected_tab != NULL) {
|
||||
if(menu->sidebar_page != NULL) {
|
||||
lv_obj_add_state(menu->selected_tab, LV_STATE_CHECKED);
|
||||
}
|
||||
else {
|
||||
lv_obj_clear_state(menu->selected_tab, LV_STATE_CHECKED);
|
||||
}
|
||||
}
|
||||
|
||||
/* Back btn management */
|
||||
if(menu->sidebar_page != NULL) {
|
||||
/* With sidebar enabled */
|
||||
if(menu->sidebar_generated) {
|
||||
if(menu->mode_root_back_btn == LV_MENU_ROOT_BACK_BTN_ENABLED) {
|
||||
/* Root back btn is always shown if enabled*/
|
||||
lv_obj_clear_flag(menu->sidebar_header_back_btn, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(menu->sidebar_header_back_btn, LV_OBJ_FLAG_CLICKABLE);
|
||||
}
|
||||
else {
|
||||
lv_obj_add_flag(menu->sidebar_header_back_btn, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_clear_flag(menu->sidebar_header_back_btn, LV_OBJ_FLAG_CLICKABLE);
|
||||
}
|
||||
}
|
||||
|
||||
if(menu->cur_depth >= 2) {
|
||||
lv_obj_clear_flag(menu->main_header_back_btn, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(menu->main_header_back_btn, LV_OBJ_FLAG_CLICKABLE);
|
||||
}
|
||||
else {
|
||||
lv_obj_add_flag(menu->main_header_back_btn, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_clear_flag(menu->main_header_back_btn, LV_OBJ_FLAG_CLICKABLE);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* With sidebar disabled */
|
||||
if(menu->cur_depth >= 2 || menu->mode_root_back_btn == LV_MENU_ROOT_BACK_BTN_ENABLED) {
|
||||
lv_obj_clear_flag(menu->main_header_back_btn, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_add_flag(menu->main_header_back_btn, LV_OBJ_FLAG_CLICKABLE);
|
||||
}
|
||||
else {
|
||||
lv_obj_add_flag(menu->main_header_back_btn, LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_clear_flag(menu->main_header_back_btn, LV_OBJ_FLAG_CLICKABLE);
|
||||
}
|
||||
}
|
||||
|
||||
lv_event_send((lv_obj_t *)menu, LV_EVENT_VALUE_CHANGED, NULL);
|
||||
|
||||
lv_menu_refr_main_header_mode(obj);
|
||||
}
|
||||
|
||||
void lv_menu_set_sidebar_page(lv_obj_t * obj, lv_obj_t * page)
|
||||
{
|
||||
LV_ASSERT_OBJ(obj, MY_CLASS);
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
|
||||
/* Sidebar management*/
|
||||
if(page != NULL) {
|
||||
/* Sidebar should be enabled */
|
||||
if(!menu->sidebar_generated) {
|
||||
/* Create sidebar */
|
||||
lv_obj_t * sidebar_cont = lv_obj_class_create_obj(&lv_menu_sidebar_cont_class, obj);
|
||||
lv_obj_class_init_obj(sidebar_cont);
|
||||
lv_obj_move_to_index(sidebar_cont, 1);
|
||||
lv_obj_set_size(sidebar_cont, LV_PCT(30), LV_PCT(100));
|
||||
lv_obj_set_flex_flow(sidebar_cont, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_add_flag(sidebar_cont, LV_OBJ_FLAG_EVENT_BUBBLE);
|
||||
lv_obj_clear_flag(sidebar_cont, LV_OBJ_FLAG_CLICKABLE);
|
||||
menu->sidebar = sidebar_cont;
|
||||
|
||||
lv_obj_t * sidebar_header = lv_obj_class_create_obj(&lv_menu_sidebar_header_cont_class, sidebar_cont);
|
||||
lv_obj_class_init_obj(sidebar_header);
|
||||
lv_obj_set_size(sidebar_header, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_flex_flow(sidebar_header, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(sidebar_header, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_clear_flag(sidebar_header, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_flag(sidebar_header, LV_OBJ_FLAG_EVENT_BUBBLE);
|
||||
menu->sidebar_header = sidebar_header;
|
||||
|
||||
lv_obj_t * sidebar_header_back_btn = lv_btn_create(menu->sidebar_header);
|
||||
lv_obj_add_event_cb(sidebar_header_back_btn, lv_menu_back_event_cb, LV_EVENT_CLICKED, menu);
|
||||
lv_obj_add_flag(sidebar_header_back_btn, LV_OBJ_FLAG_EVENT_BUBBLE);
|
||||
lv_obj_set_flex_flow(sidebar_header_back_btn, LV_FLEX_FLOW_ROW);
|
||||
menu->sidebar_header_back_btn = sidebar_header_back_btn;
|
||||
|
||||
lv_obj_t * sidebar_header_back_icon = lv_img_create(menu->sidebar_header_back_btn);
|
||||
lv_img_set_src(sidebar_header_back_icon, LV_SYMBOL_LEFT);
|
||||
|
||||
lv_obj_t * sidebar_header_title = lv_label_create(menu->sidebar_header);
|
||||
lv_obj_add_flag(sidebar_header_title, LV_OBJ_FLAG_HIDDEN);
|
||||
menu->sidebar_header_title = sidebar_header_title;
|
||||
|
||||
menu->sidebar_generated = true;
|
||||
}
|
||||
|
||||
lv_obj_set_parent(page, menu->sidebar);
|
||||
|
||||
lv_menu_refr_sidebar_header_mode(obj);
|
||||
}
|
||||
else {
|
||||
/* Sidebar should be disabled */
|
||||
if(menu->sidebar_generated) {
|
||||
lv_obj_set_parent(menu->sidebar_page, menu->storage);
|
||||
lv_obj_del(menu->sidebar);
|
||||
|
||||
menu->sidebar_generated = false;
|
||||
}
|
||||
}
|
||||
|
||||
menu->sidebar_page = page;
|
||||
lv_menu_refr(obj);
|
||||
}
|
||||
|
||||
void lv_menu_set_mode_header(lv_obj_t * obj, lv_menu_mode_header_t mode_header)
|
||||
{
|
||||
LV_ASSERT_OBJ(obj, MY_CLASS);
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
|
||||
if(menu->mode_header != mode_header) {
|
||||
menu->mode_header = mode_header;
|
||||
lv_menu_refr_main_header_mode(obj);
|
||||
if(menu->sidebar_generated) lv_menu_refr_sidebar_header_mode(obj);
|
||||
}
|
||||
}
|
||||
|
||||
void lv_menu_set_mode_root_back_btn(lv_obj_t * obj, lv_menu_mode_root_back_btn_t mode_root_back_btn)
|
||||
{
|
||||
LV_ASSERT_OBJ(obj, MY_CLASS);
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
|
||||
if(menu->mode_root_back_btn != mode_root_back_btn) {
|
||||
menu->mode_root_back_btn = mode_root_back_btn;
|
||||
lv_menu_refr(obj);
|
||||
}
|
||||
}
|
||||
|
||||
void lv_menu_set_load_page_event(lv_obj_t * menu, lv_obj_t * obj, lv_obj_t * page)
|
||||
{
|
||||
LV_ASSERT_OBJ(menu, MY_CLASS);
|
||||
|
||||
/* Make the object clickable */
|
||||
lv_obj_add_flag(obj, LV_OBJ_FLAG_CLICKABLE);
|
||||
|
||||
/* Remove old event */
|
||||
if(lv_obj_remove_event_cb(obj, lv_menu_load_page_event_cb)) {
|
||||
lv_event_send(obj, LV_EVENT_DELETE, NULL);
|
||||
lv_obj_remove_event_cb(obj, lv_menu_obj_del_event_cb);
|
||||
}
|
||||
|
||||
lv_menu_load_page_event_data_t * event_data = lv_mem_alloc(sizeof(lv_menu_load_page_event_data_t));
|
||||
event_data->menu = menu;
|
||||
event_data->page = page;
|
||||
|
||||
lv_obj_add_event_cb(obj, lv_menu_load_page_event_cb, LV_EVENT_CLICKED, event_data);
|
||||
lv_obj_add_event_cb(obj, lv_menu_obj_del_event_cb, LV_EVENT_DELETE, event_data);
|
||||
}
|
||||
|
||||
/*=====================
|
||||
* Getter functions
|
||||
*====================*/
|
||||
lv_obj_t * lv_menu_get_cur_main_page(lv_obj_t * obj)
|
||||
{
|
||||
LV_ASSERT_OBJ(obj, MY_CLASS);
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
return menu->main_page;
|
||||
}
|
||||
|
||||
lv_obj_t * lv_menu_get_cur_sidebar_page(lv_obj_t * obj)
|
||||
{
|
||||
LV_ASSERT_OBJ(obj, MY_CLASS);
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
return menu->sidebar_page;
|
||||
}
|
||||
|
||||
lv_obj_t * lv_menu_get_main_header(lv_obj_t * obj)
|
||||
{
|
||||
LV_ASSERT_OBJ(obj, MY_CLASS);
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
return menu->main_header;
|
||||
}
|
||||
|
||||
lv_obj_t * lv_menu_get_main_header_back_btn(lv_obj_t * obj)
|
||||
{
|
||||
LV_ASSERT_OBJ(obj, MY_CLASS);
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
return menu->main_header_back_btn;
|
||||
}
|
||||
|
||||
lv_obj_t * lv_menu_get_sidebar_header(lv_obj_t * obj)
|
||||
{
|
||||
LV_ASSERT_OBJ(obj, MY_CLASS);
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
return menu->sidebar_header;
|
||||
}
|
||||
|
||||
lv_obj_t * lv_menu_get_sidebar_header_back_btn(lv_obj_t * obj)
|
||||
{
|
||||
LV_ASSERT_OBJ(obj, MY_CLASS);
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
return menu->sidebar_header_back_btn;
|
||||
}
|
||||
|
||||
bool lv_menu_back_btn_is_root(lv_obj_t * menu, lv_obj_t * obj)
|
||||
{
|
||||
LV_ASSERT_OBJ(menu, MY_CLASS);
|
||||
|
||||
if(obj == ((lv_menu_t *)menu)->sidebar_header_back_btn) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(obj == ((lv_menu_t *)menu)->main_header_back_btn && ((lv_menu_t *)menu)->prev_depth <= 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void lv_menu_clear_history(lv_obj_t * obj)
|
||||
{
|
||||
LV_ASSERT_OBJ(obj, MY_CLASS);
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
lv_ll_t * history_ll = &(menu->history_ll);
|
||||
|
||||
_lv_ll_clear(history_ll);
|
||||
|
||||
menu->cur_depth = 0;
|
||||
}
|
||||
|
||||
/**********************
|
||||
* STATIC FUNCTIONS
|
||||
**********************/
|
||||
|
||||
static void lv_menu_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
|
||||
{
|
||||
LV_UNUSED(class_p);
|
||||
LV_TRACE_OBJ_CREATE("begin");
|
||||
|
||||
lv_obj_set_layout(obj, LV_LAYOUT_FLEX);
|
||||
lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW);
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
|
||||
menu->mode_header = LV_MENU_HEADER_TOP_FIXED;
|
||||
menu->mode_root_back_btn = LV_MENU_ROOT_BACK_BTN_DISABLED;
|
||||
menu->cur_depth = 0;
|
||||
menu->prev_depth = 0;
|
||||
menu->sidebar_generated = false;
|
||||
|
||||
_lv_ll_init(&(menu->history_ll), sizeof(lv_menu_history_t));
|
||||
|
||||
menu->storage = lv_obj_create(obj);
|
||||
lv_obj_add_flag(menu->storage, LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
menu->sidebar = NULL;
|
||||
menu->sidebar_header = NULL;
|
||||
menu->sidebar_header_back_btn = NULL;
|
||||
menu->sidebar_header_title = NULL;
|
||||
menu->sidebar_page = NULL;
|
||||
|
||||
lv_obj_t * main_cont = lv_obj_class_create_obj(&lv_menu_main_cont_class, obj);
|
||||
lv_obj_class_init_obj(main_cont);
|
||||
lv_obj_set_height(main_cont, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(main_cont, 1);
|
||||
lv_obj_set_flex_flow(main_cont, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_add_flag(main_cont, LV_OBJ_FLAG_EVENT_BUBBLE);
|
||||
lv_obj_clear_flag(main_cont, LV_OBJ_FLAG_CLICKABLE);
|
||||
menu->main = main_cont;
|
||||
|
||||
lv_obj_t * main_header = lv_obj_class_create_obj(&lv_menu_main_header_cont_class, main_cont);
|
||||
lv_obj_class_init_obj(main_header);
|
||||
lv_obj_set_size(main_header, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_flex_flow(main_header, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(main_header, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_clear_flag(main_header, LV_OBJ_FLAG_CLICKABLE);
|
||||
lv_obj_add_flag(main_header, LV_OBJ_FLAG_EVENT_BUBBLE);
|
||||
menu->main_header = main_header;
|
||||
|
||||
/* Create the default simple back btn and title */
|
||||
lv_obj_t * main_header_back_btn = lv_btn_create(menu->main_header);
|
||||
lv_obj_add_event_cb(main_header_back_btn, lv_menu_back_event_cb, LV_EVENT_CLICKED, menu);
|
||||
lv_obj_add_flag(main_header_back_btn, LV_OBJ_FLAG_EVENT_BUBBLE);
|
||||
lv_obj_set_flex_flow(main_header_back_btn, LV_FLEX_FLOW_ROW);
|
||||
menu->main_header_back_btn = main_header_back_btn;
|
||||
|
||||
lv_obj_t * main_header_back_icon = lv_img_create(menu->main_header_back_btn);
|
||||
lv_img_set_src(main_header_back_icon, LV_SYMBOL_LEFT);
|
||||
|
||||
lv_obj_t * main_header_title = lv_label_create(menu->main_header);
|
||||
lv_obj_add_flag(main_header_title, LV_OBJ_FLAG_HIDDEN);
|
||||
menu->main_header_title = main_header_title;
|
||||
|
||||
menu->main_page = NULL;
|
||||
menu->selected_tab = NULL;
|
||||
|
||||
lv_obj_add_event_cb(obj, lv_menu_value_changed_event_cb, LV_EVENT_VALUE_CHANGED, menu);
|
||||
|
||||
LV_TRACE_OBJ_CREATE("finished");
|
||||
}
|
||||
|
||||
static void lv_menu_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
|
||||
{
|
||||
LV_UNUSED(class_p);
|
||||
LV_TRACE_OBJ_CREATE("begin");
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
lv_ll_t * history_ll = &(menu->history_ll);
|
||||
|
||||
_lv_ll_clear(history_ll);
|
||||
|
||||
LV_TRACE_OBJ_CREATE("finished");
|
||||
}
|
||||
|
||||
static void lv_menu_page_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
|
||||
{
|
||||
LV_UNUSED(class_p);
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)lv_obj_get_parent(obj);
|
||||
|
||||
lv_obj_set_parent(obj, ((lv_menu_t *)menu)->storage);
|
||||
lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_add_flag(obj, LV_OBJ_FLAG_EVENT_BUBBLE);
|
||||
}
|
||||
|
||||
static void lv_menu_page_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
|
||||
{
|
||||
LV_UNUSED(class_p);
|
||||
|
||||
lv_menu_page_t * page = (lv_menu_page_t *)obj;
|
||||
|
||||
if(page->title != NULL) {
|
||||
lv_mem_free(page->title);
|
||||
page->title = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void lv_menu_cont_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
|
||||
{
|
||||
LV_UNUSED(class_p);
|
||||
lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICKABLE);
|
||||
}
|
||||
|
||||
static void lv_menu_section_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
|
||||
{
|
||||
LV_UNUSED(class_p);
|
||||
lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICKABLE);
|
||||
}
|
||||
|
||||
static void lv_menu_refr_sidebar_header_mode(lv_obj_t * obj)
|
||||
{
|
||||
LV_ASSERT_OBJ(obj, MY_CLASS);
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
|
||||
if(menu->sidebar_header == NULL || menu->sidebar_page == NULL) return;
|
||||
|
||||
switch(menu->mode_header) {
|
||||
case LV_MENU_HEADER_TOP_FIXED:
|
||||
/* Content should fill the remaining space */
|
||||
lv_obj_move_to_index(menu->sidebar_header, 0);
|
||||
lv_obj_set_flex_grow(menu->sidebar_page, 1);
|
||||
break;
|
||||
case LV_MENU_HEADER_TOP_UNFIXED:
|
||||
lv_obj_move_to_index(menu->sidebar_header, 0);
|
||||
lv_obj_set_flex_grow(menu->sidebar_page, 0);
|
||||
break;
|
||||
case LV_MENU_HEADER_BOTTOM_FIXED:
|
||||
lv_obj_move_to_index(menu->sidebar_header, 1);
|
||||
lv_obj_set_flex_grow(menu->sidebar_page, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
lv_obj_refr_size(menu->sidebar_header);
|
||||
lv_obj_refr_size(menu->sidebar_page);
|
||||
|
||||
if(lv_obj_get_content_height(menu->sidebar_header) == 0) {
|
||||
lv_obj_add_flag(menu->sidebar_header, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
else {
|
||||
lv_obj_clear_flag(menu->sidebar_header, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
static void lv_menu_refr_main_header_mode(lv_obj_t * obj)
|
||||
{
|
||||
LV_ASSERT_OBJ(obj, MY_CLASS);
|
||||
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
|
||||
if(menu->main_header == NULL || menu->main_page == NULL) return;
|
||||
|
||||
switch(menu->mode_header) {
|
||||
case LV_MENU_HEADER_TOP_FIXED:
|
||||
/* Content should fill the remaining space */
|
||||
lv_obj_move_to_index(menu->main_header, 0);
|
||||
lv_obj_set_flex_grow(menu->main_page, 1);
|
||||
break;
|
||||
case LV_MENU_HEADER_TOP_UNFIXED:
|
||||
lv_obj_move_to_index(menu->main_header, 0);
|
||||
lv_obj_set_flex_grow(menu->main_page, 0);
|
||||
break;
|
||||
case LV_MENU_HEADER_BOTTOM_FIXED:
|
||||
lv_obj_move_to_index(menu->main_header, 1);
|
||||
lv_obj_set_flex_grow(menu->main_page, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
lv_obj_refr_size(menu->main_header);
|
||||
lv_obj_refr_size(menu->main_page);
|
||||
lv_obj_update_layout(menu->main_header);
|
||||
|
||||
if(lv_obj_get_content_height(menu->main_header) == 0) {
|
||||
lv_obj_add_flag(menu->main_header, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
else {
|
||||
lv_obj_clear_flag(menu->main_header, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
static void lv_menu_load_page_event_cb(lv_event_t * e)
|
||||
{
|
||||
lv_obj_t * obj = lv_event_get_target(e);
|
||||
lv_menu_load_page_event_data_t * event_data = lv_event_get_user_data(e);
|
||||
lv_menu_t * menu = (lv_menu_t *)(event_data->menu);
|
||||
lv_obj_t * page = event_data->page;
|
||||
|
||||
if(menu->sidebar_page != NULL) {
|
||||
/* Check if clicked obj is in the sidebar */
|
||||
bool sidebar = false;
|
||||
lv_obj_t * parent = obj;
|
||||
|
||||
while(parent) {
|
||||
if(parent == (lv_obj_t *)menu) break;
|
||||
if(parent == menu->sidebar) {
|
||||
sidebar = true;
|
||||
break;
|
||||
}
|
||||
parent = lv_obj_get_parent(parent);
|
||||
}
|
||||
|
||||
if(sidebar) {
|
||||
/* Clear checked state of previous obj */
|
||||
if(menu->selected_tab != obj && menu->selected_tab != NULL) {
|
||||
lv_obj_clear_state(menu->selected_tab, LV_STATE_CHECKED);
|
||||
}
|
||||
|
||||
lv_menu_clear_history((lv_obj_t *)menu);
|
||||
|
||||
menu->selected_tab = obj;
|
||||
}
|
||||
}
|
||||
|
||||
lv_menu_set_page((lv_obj_t *)menu, page);
|
||||
}
|
||||
|
||||
static void lv_menu_obj_del_event_cb(lv_event_t * e)
|
||||
{
|
||||
lv_menu_load_page_event_data_t * event_data = lv_event_get_user_data(e);
|
||||
lv_mem_free(event_data);
|
||||
}
|
||||
|
||||
static void lv_menu_back_event_cb(lv_event_t * e)
|
||||
{
|
||||
lv_event_code_t code = lv_event_get_code(e);
|
||||
/* LV_EVENT_CLICKED */
|
||||
if(code == LV_EVENT_CLICKED) {
|
||||
lv_obj_t * obj = lv_event_get_target(e);
|
||||
lv_menu_t * menu = (lv_menu_t *)lv_event_get_user_data(e);
|
||||
|
||||
if(!(obj == menu->main_header_back_btn || obj == menu->sidebar_header_back_btn)) return;
|
||||
|
||||
menu->prev_depth = menu->cur_depth; /* Save the previous value for user event handler */
|
||||
|
||||
if(lv_menu_back_btn_is_root((lv_obj_t *)menu, obj)) return;
|
||||
|
||||
lv_ll_t * history_ll = &(menu->history_ll);
|
||||
|
||||
/* The current menu */
|
||||
lv_menu_history_t * act_hist = _lv_ll_get_head(history_ll);
|
||||
|
||||
/* The previous menu */
|
||||
lv_menu_history_t * prev_hist = _lv_ll_get_next(history_ll, act_hist);
|
||||
|
||||
if(prev_hist != NULL) {
|
||||
/* Previous menu exists */
|
||||
/* Delete the current item from the history */
|
||||
_lv_ll_remove(history_ll, act_hist);
|
||||
lv_mem_free(act_hist);
|
||||
menu->cur_depth--;
|
||||
/* Create the previous menu.
|
||||
* Remove it from the history because `lv_menu_set_page` will add it again */
|
||||
_lv_ll_remove(history_ll, prev_hist);
|
||||
menu->cur_depth--;
|
||||
lv_menu_set_page(&(menu->obj), prev_hist->page);
|
||||
|
||||
lv_mem_free(prev_hist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void lv_menu_value_changed_event_cb(lv_event_t * e)
|
||||
{
|
||||
lv_obj_t * obj = lv_event_get_user_data(e);
|
||||
lv_menu_t * menu = (lv_menu_t *)obj;
|
||||
|
||||
lv_menu_page_t * main_page = (lv_menu_page_t *)lv_menu_get_cur_main_page(obj);
|
||||
if(main_page != NULL && menu->main_header_title != NULL) {
|
||||
if(main_page->title != NULL) {
|
||||
lv_label_set_text(menu->main_header_title, main_page->title);
|
||||
lv_obj_clear_flag(menu->main_header_title, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
else {
|
||||
lv_obj_add_flag(menu->main_header_title, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
lv_menu_page_t * sidebar_page = (lv_menu_page_t *)lv_menu_get_cur_sidebar_page(obj);
|
||||
if(sidebar_page != NULL && menu->sidebar_header_title != NULL) {
|
||||
if(sidebar_page->title != NULL) {
|
||||
lv_label_set_text(menu->sidebar_header_title, sidebar_page->title);
|
||||
lv_obj_clear_flag(menu->sidebar_header_title, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
else {
|
||||
lv_obj_add_flag(menu->sidebar_header_title, LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif /*LV_USE_MENU*/
|
233
src/extra/widgets/menu/lv_menu.h
Normal file
233
src/extra/widgets/menu/lv_menu.h
Normal file
@ -0,0 +1,233 @@
|
||||
/**
|
||||
* @file lv_menu.h
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LV_MENU_H
|
||||
#define LV_MENU_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*********************
|
||||
* INCLUDES
|
||||
*********************/
|
||||
#include "../../../core/lv_obj.h"
|
||||
|
||||
#if LV_USE_MENU
|
||||
|
||||
/*********************
|
||||
* DEFINES
|
||||
*********************/
|
||||
|
||||
/**********************
|
||||
* TYPEDEFS
|
||||
**********************/
|
||||
|
||||
enum {
|
||||
LV_MENU_HEADER_TOP_FIXED, /* Header is positioned at the top */
|
||||
LV_MENU_HEADER_TOP_UNFIXED, /* Header is positioned at the top and can be scrolled out of view*/
|
||||
LV_MENU_HEADER_BOTTOM_FIXED /* Header is positioned at the bottom */
|
||||
};
|
||||
typedef uint8_t lv_menu_mode_header_t;
|
||||
|
||||
enum {
|
||||
LV_MENU_ROOT_BACK_BTN_DISABLED,
|
||||
LV_MENU_ROOT_BACK_BTN_ENABLED
|
||||
};
|
||||
typedef uint8_t lv_menu_mode_root_back_btn_t;
|
||||
|
||||
typedef struct lv_menu_load_page_event_data_t {
|
||||
lv_obj_t * menu;
|
||||
lv_obj_t * page;
|
||||
} lv_menu_load_page_event_data_t;
|
||||
|
||||
typedef struct {
|
||||
lv_obj_t * page;
|
||||
} lv_menu_history_t;
|
||||
|
||||
typedef struct {
|
||||
lv_obj_t obj;
|
||||
lv_obj_t * storage; /* a pointer to obj that is the parent of all pages not displayed */
|
||||
lv_obj_t * main;
|
||||
lv_obj_t * main_page;
|
||||
lv_obj_t * main_header;
|
||||
lv_obj_t *
|
||||
main_header_back_btn; /* a pointer to obj that on click triggers back btn event handler, can be same as 'main_header' */
|
||||
lv_obj_t * main_header_title;
|
||||
lv_obj_t * sidebar;
|
||||
lv_obj_t * sidebar_page;
|
||||
lv_obj_t * sidebar_header;
|
||||
lv_obj_t *
|
||||
sidebar_header_back_btn; /* a pointer to obj that on click triggers back btn event handler, can be same as 'sidebar_header' */
|
||||
lv_obj_t * sidebar_header_title;
|
||||
lv_obj_t * selected_tab;
|
||||
lv_ll_t history_ll;
|
||||
uint8_t cur_depth;
|
||||
uint8_t prev_depth;
|
||||
uint8_t sidebar_generated : 1;
|
||||
lv_menu_mode_header_t mode_header : 2;
|
||||
lv_menu_mode_root_back_btn_t mode_root_back_btn : 1;
|
||||
} lv_menu_t;
|
||||
|
||||
typedef struct {
|
||||
lv_obj_t obj;
|
||||
char * title;
|
||||
} lv_menu_page_t;
|
||||
|
||||
extern const lv_obj_class_t lv_menu_class;
|
||||
extern const lv_obj_class_t lv_menu_page_class;
|
||||
extern const lv_obj_class_t lv_menu_cont_class;
|
||||
extern const lv_obj_class_t lv_menu_section_class;
|
||||
extern const lv_obj_class_t lv_menu_separator_class;
|
||||
extern const lv_obj_class_t lv_menu_sidebar_cont_class;
|
||||
extern const lv_obj_class_t lv_menu_main_cont_class;
|
||||
extern const lv_obj_class_t lv_menu_sidebar_header_cont_class;
|
||||
extern const lv_obj_class_t lv_menu_main_header_cont_class;
|
||||
/**********************
|
||||
* GLOBAL PROTOTYPES
|
||||
**********************/
|
||||
|
||||
/**
|
||||
* Create a menu object
|
||||
* @param parent pointer to an object, it will be the parent of the new menu
|
||||
* @return pointer to the created menu
|
||||
*/
|
||||
lv_obj_t * lv_menu_create(lv_obj_t * parent);
|
||||
|
||||
/**
|
||||
* Create a menu page object
|
||||
* @param parent pointer to menu object
|
||||
* @param title pointer to text for title in header (NULL to not display title)
|
||||
* @return pointer to the created menu page
|
||||
*/
|
||||
lv_obj_t * lv_menu_page_create(lv_obj_t * parent, char * title);
|
||||
|
||||
/**
|
||||
* Create a menu cont object
|
||||
* @param parent pointer to an object, it will be the parent of the new menu cont object
|
||||
* @return pointer to the created menu cont
|
||||
*/
|
||||
lv_obj_t * lv_menu_cont_create(lv_obj_t * parent);
|
||||
|
||||
/**
|
||||
* Create a menu section object
|
||||
* @param parent pointer to an object, it will be the parent of the new menu section object
|
||||
* @return pointer to the created menu section
|
||||
*/
|
||||
lv_obj_t * lv_menu_section_create(lv_obj_t * parent);
|
||||
|
||||
/**
|
||||
* Create a menu separator object
|
||||
* @param parent pointer to an object, it will be the parent of the new menu separator object
|
||||
* @return pointer to the created menu separator
|
||||
*/
|
||||
lv_obj_t * lv_menu_separator_create(lv_obj_t * parent);
|
||||
/*=====================
|
||||
* Setter functions
|
||||
*====================*/
|
||||
/**
|
||||
* Set menu page to display in main
|
||||
* @param obj pointer to the menu
|
||||
* @param page pointer to the menu page to set (NULL to clear main and clear menu history)
|
||||
*/
|
||||
void lv_menu_set_page(lv_obj_t * obj, lv_obj_t * page);
|
||||
|
||||
/**
|
||||
* Set menu page to display in sidebar
|
||||
* @param obj pointer to the menu
|
||||
* @param page pointer to the menu page to set (NULL to clear sidebar)
|
||||
*/
|
||||
void lv_menu_set_sidebar_page(lv_obj_t * obj, lv_obj_t * page);
|
||||
|
||||
/**
|
||||
* Set the how the header should behave and its position
|
||||
* @param obj pointer to a menu
|
||||
* @param mode_header
|
||||
*/
|
||||
void lv_menu_set_mode_header(lv_obj_t * obj, lv_menu_mode_header_t mode_header);
|
||||
|
||||
/**
|
||||
* Set whether back button should appear at root
|
||||
* @param obj pointer to a menu
|
||||
* @param mode_root_back_btn
|
||||
*/
|
||||
void lv_menu_set_mode_root_back_btn(lv_obj_t * obj, lv_menu_mode_root_back_btn_t mode_root_back_btn);
|
||||
|
||||
/**
|
||||
* Add menu to the menu item
|
||||
* @param menu pointer to the menu
|
||||
* @param obj pointer to the obj
|
||||
* @param page pointer to the page to load when obj is clicked
|
||||
*/
|
||||
void lv_menu_set_load_page_event(lv_obj_t * menu, lv_obj_t * obj, lv_obj_t * page);
|
||||
|
||||
/*=====================
|
||||
* Getter functions
|
||||
*====================*/
|
||||
/**
|
||||
* Get a pointer to menu page that is currently displayed in main
|
||||
* @param obj pointer to the menu
|
||||
* @return pointer to current page
|
||||
*/
|
||||
lv_obj_t * lv_menu_get_cur_main_page(lv_obj_t * obj);
|
||||
|
||||
/**
|
||||
* Get a pointer to menu page that is currently displayed in sidebar
|
||||
* @param obj pointer to the menu
|
||||
* @return pointer to current page
|
||||
*/
|
||||
lv_obj_t * lv_menu_get_cur_sidebar_page(lv_obj_t * obj);
|
||||
|
||||
/**
|
||||
* Get a pointer to main header obj
|
||||
* @param obj pointer to the menu
|
||||
* @return pointer to main header obj
|
||||
*/
|
||||
lv_obj_t * lv_menu_get_main_header(lv_obj_t * obj);
|
||||
|
||||
/**
|
||||
* Get a pointer to main header back btn obj
|
||||
* @param obj pointer to the menu
|
||||
* @return pointer to main header back btn obj
|
||||
*/
|
||||
lv_obj_t * lv_menu_get_main_header_back_btn(lv_obj_t * obj);
|
||||
|
||||
/**
|
||||
* Get a pointer to sidebar header obj
|
||||
* @param obj pointer to the menu
|
||||
* @return pointer to sidebar header obj
|
||||
*/
|
||||
lv_obj_t * lv_menu_get_sidebar_header(lv_obj_t * obj);
|
||||
|
||||
/**
|
||||
* Get a pointer to sidebar header obj
|
||||
* @param obj pointer to the menu
|
||||
* @return pointer to sidebar header back btn obj
|
||||
*/
|
||||
lv_obj_t * lv_menu_get_sidebar_header_back_btn(lv_obj_t * obj);
|
||||
|
||||
/**
|
||||
* Check if a obj is a root back btn
|
||||
* @param menu pointer to the menu
|
||||
* @return true if it is a root back btn
|
||||
*/
|
||||
bool lv_menu_back_btn_is_root(lv_obj_t * menu, lv_obj_t * obj);
|
||||
|
||||
/**
|
||||
* Clear menu history
|
||||
* @param obj pointer to the menu
|
||||
*/
|
||||
void lv_menu_clear_history(lv_obj_t * obj);
|
||||
/**********************
|
||||
* MACROS
|
||||
**********************/
|
||||
|
||||
#endif /*LV_USE_MENU*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /*extern "C"*/
|
||||
#endif
|
||||
|
||||
#endif /*LV_MENU_H*/
|
@ -1551,6 +1551,18 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef LV_USE_MENU
|
||||
#ifdef _LV_KCONFIG_PRESENT
|
||||
#ifdef CONFIG_LV_USE_MENU
|
||||
#define LV_USE_MENU CONFIG_LV_USE_MENU
|
||||
#else
|
||||
#define LV_USE_MENU 0
|
||||
#endif
|
||||
#else
|
||||
#define LV_USE_MENU 1
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef LV_USE_METER
|
||||
#ifdef _LV_KCONFIG_PRESENT
|
||||
#ifdef CONFIG_LV_USE_METER
|
||||
|
Loading…
x
Reference in New Issue
Block a user