mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 02:45:05 +00:00
Setup and Files app UX improved
This commit is contained in:
parent
bb79f13544
commit
625c8383f3
@ -1,6 +1,7 @@
|
||||
# Architecture: LVGL
|
||||
|
||||
User interfaces should scale well for everything between very large (e.g. 1280x720) and small (e.g. 135x240) displays. Vertical and horizontal layouts are supported.
|
||||
Margins and padding are set in density-independent pixels with `LV_DPX()`, not in raw pixels.
|
||||
|
||||
Two kernel modules cover LVGL:
|
||||
|
||||
|
||||
@ -7,7 +7,6 @@
|
||||
|
||||
## Higher Priority
|
||||
|
||||
- Improve Setup: Show progress of some kind (page_indicator?). Improve skip/next buttons UX? e.g. add outline button style and put buttons together? (update docs!)
|
||||
- BlueTooth as radio backend for MeshCore/Meshtastic?
|
||||
- ESP-NOW as radio backend. Use it for MeshCore/Meshtastic?
|
||||
- Find out why the first build of an app with a clean build cache folder always fails
|
||||
@ -19,7 +18,6 @@
|
||||
- Use GPS time to set/update the current time: when enabled, at boot, when wifi is not enabled, start the gps device and wait until a lock is acquired, then get the time and sync it. Then stop the GPS device.
|
||||
- lvgl-module's spinner relies on hard-coded spinner asset from Tactility main project, should be sized based on DPI
|
||||
- Cursor is hard-coded, needs size based on DPI
|
||||
- Files app should have a page indicator like Launcher.
|
||||
- Move Bluetooth code from kernel to Modules/bluetooth-module, and have includes in path bluetooth/ then make a bluetooth_command.cpp like wifi_command.cpp
|
||||
- Inspect code for memory improvements: replace `std::*` with `tt::std::*`
|
||||
- esp_lvgl_port settings has a large stack size (~9kB) to fix stackoverflow when LVGL events (e.g. button click) do actions like file operations do actions like file operations. Can we reduce the callstack?
|
||||
|
||||
20
Modules/lvgl-module/include/lvgl/widgets/text_button.h
Normal file
20
Modules/lvgl-module/include/lvgl/widgets/text_button.h
Normal file
@ -0,0 +1,20 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Creates a button without a container, for low-emphasis actions such as "Skip" or "Back".
|
||||
* It behaves like a regular button, but the theme styles it as a text button.
|
||||
* @param[in] parent the parent object for the new button
|
||||
* @return the created button
|
||||
*/
|
||||
lv_obj_t* lvgl_text_button_create(lv_obj_t* parent);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -24,6 +24,7 @@
|
||||
#include <lvgl/widgets/page_indicator.h>
|
||||
#include <lvgl/widgets/spinner.h>
|
||||
#include <lvgl/widgets/statusbar.h>
|
||||
#include <lvgl/widgets/text_button.h>
|
||||
#include <lvgl/widgets/toolbar.h>
|
||||
|
||||
#include <tactility/module.h>
|
||||
@ -136,6 +137,8 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
||||
// lvgl_icon_button
|
||||
DEFINE_MODULE_SYMBOL(lvgl_icon_button_create),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_icon_button_create_variant),
|
||||
// lvgl_text_button
|
||||
DEFINE_MODULE_SYMBOL(lvgl_text_button_create),
|
||||
// lvgl_list
|
||||
DEFINE_MODULE_SYMBOL(lvgl_list_create),
|
||||
DEFINE_MODULE_SYMBOL(lvgl_list_add_text),
|
||||
|
||||
@ -22,6 +22,7 @@ extern const lv_obj_class_t lvgl_page_indicator_dot_class;
|
||||
extern const lv_obj_class_t lvgl_list_class;
|
||||
extern const lv_obj_class_t lvgl_list_text_class;
|
||||
extern const lv_obj_class_t lvgl_list_button_class;
|
||||
extern const lv_obj_class_t lvgl_text_button_class;
|
||||
|
||||
#define NEUTRAL_4 lv_color_hex(0x0E0E0E)
|
||||
#define NEUTRAL_6 lv_color_hex(0x131313)
|
||||
@ -372,12 +373,9 @@ typedef struct {
|
||||
lv_style_t button_checked_pressed;
|
||||
#endif
|
||||
lv_style_t button_disabled;
|
||||
#if LV_USE_MSGBOX
|
||||
lv_style_t text_button;
|
||||
#endif
|
||||
#if LV_USE_MSGBOX
|
||||
lv_style_t text_button_pressed;
|
||||
#endif
|
||||
lv_style_t text_button_margin;
|
||||
lv_style_t icon_button;
|
||||
lv_style_t icon_button_selected;
|
||||
lv_style_t icon_button_filled;
|
||||
@ -857,7 +855,6 @@ static void style_init(my_theme_t * theme)
|
||||
lv_style_set_text_color(&theme->styles.button_disabled, BUTTON_DISABLED_LABEL_COLOR);
|
||||
lv_style_set_text_opa(&theme->styles.button_disabled, BUTTON_DISABLED_LABEL_OPACITY);
|
||||
|
||||
#if LV_USE_MSGBOX
|
||||
style_init_reset(&theme->styles.text_button);
|
||||
lv_style_set_bg_opa(&theme->styles.text_button, LV_OPA_TRANSP);
|
||||
lv_style_set_text_color(&theme->styles.text_button, TEXT_BUTTON_LABEL_COLOR);
|
||||
@ -865,13 +862,13 @@ static void style_init(my_theme_t * theme)
|
||||
lv_style_set_pad_hor(&theme->styles.text_button, TEXT_BUTTON_PAD_HOR);
|
||||
lv_style_set_pad_ver(&theme->styles.text_button, BUTTON_PAD_VER);
|
||||
lv_style_set_pad_gap(&theme->styles.text_button, BUTTON_GAP);
|
||||
#endif
|
||||
|
||||
#if LV_USE_MSGBOX
|
||||
style_init_reset(&theme->styles.text_button_pressed);
|
||||
lv_style_set_bg_color(&theme->styles.text_button_pressed, TEXT_BUTTON_PRESSED_CONTAINER_COLOR);
|
||||
lv_style_set_bg_opa(&theme->styles.text_button_pressed, OPACITY_STATE_PRESSED);
|
||||
#endif
|
||||
|
||||
style_init_reset(&theme->styles.text_button_margin);
|
||||
lv_style_set_margin_all(&theme->styles.text_button_margin, FOCUS_WIDTH + FOCUS_OFFSET);
|
||||
|
||||
style_init_reset(&theme->styles.icon_button);
|
||||
lv_style_set_bg_opa(&theme->styles.icon_button, LV_OPA_TRANSP);
|
||||
@ -1843,6 +1840,17 @@ static void theme_apply(lv_theme_t * th, lv_obj_t * obj)
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if(lv_obj_check_type(obj, &lvgl_text_button_class)) {
|
||||
/* text_button */
|
||||
lv_obj_add_style(obj, &theme->styles.text_button, 0);
|
||||
lv_obj_add_style(obj, &theme->styles.text_button_margin, 0);
|
||||
lv_obj_add_style(obj, &theme->styles.transition_exit, 0);
|
||||
lv_obj_add_style(obj, &theme->styles.text_button_pressed, LV_STATE_PRESSED);
|
||||
lv_obj_add_style(obj, &theme->styles.transition_enter, LV_STATE_PRESSED);
|
||||
lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY);
|
||||
lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED);
|
||||
return;
|
||||
}
|
||||
#if LV_USE_MSGBOX
|
||||
if(lv_obj_check_type(obj, &lv_msgbox_footer_button_class)) {
|
||||
/* msgbox_footer_button */
|
||||
|
||||
@ -22,6 +22,7 @@ extern const lv_obj_class_t lvgl_page_indicator_dot_class;
|
||||
extern const lv_obj_class_t lvgl_list_class;
|
||||
extern const lv_obj_class_t lvgl_list_text_class;
|
||||
extern const lv_obj_class_t lvgl_list_button_class;
|
||||
extern const lv_obj_class_t lvgl_text_button_class;
|
||||
|
||||
#define BLACK lv_color_black()
|
||||
#define WHITE lv_color_white()
|
||||
@ -237,6 +238,7 @@ typedef struct {
|
||||
#if LV_USE_BUTTON || LV_USE_MSGBOX
|
||||
lv_style_t button;
|
||||
#endif
|
||||
lv_style_t text_button;
|
||||
lv_style_t icon_button;
|
||||
lv_style_t icon_button_filled;
|
||||
lv_style_t icon_button_filled_pressed;
|
||||
@ -612,6 +614,16 @@ static void style_init(my_theme_t * theme)
|
||||
lv_style_set_margin_all(&theme->styles.button, FOCUS_WIDTH + FOCUS_OFFSET);
|
||||
#endif
|
||||
|
||||
style_init_reset(&theme->styles.text_button);
|
||||
lv_style_set_bg_opa(&theme->styles.text_button, LV_OPA_TRANSP);
|
||||
lv_style_set_text_color(&theme->styles.text_button, BUTTON_LABEL_COLOR);
|
||||
lv_style_set_border_width(&theme->styles.text_button, 0);
|
||||
lv_style_set_radius(&theme->styles.text_button, BUTTON_SHAPE);
|
||||
lv_style_set_pad_hor(&theme->styles.text_button, BUTTON_PAD_HOR);
|
||||
lv_style_set_pad_ver(&theme->styles.text_button, BUTTON_PAD_VER);
|
||||
lv_style_set_pad_gap(&theme->styles.text_button, BUTTON_GAP);
|
||||
lv_style_set_margin_all(&theme->styles.text_button, FOCUS_WIDTH + FOCUS_OFFSET);
|
||||
|
||||
style_init_reset(&theme->styles.icon_button);
|
||||
lv_style_set_bg_opa(&theme->styles.icon_button, LV_OPA_TRANSP);
|
||||
lv_style_set_text_color(&theme->styles.icon_button, COLOR_ON_SURFACE);
|
||||
@ -1415,6 +1427,14 @@ static void theme_apply(lv_theme_t * th, lv_obj_t * obj)
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if(lv_obj_check_type(obj, &lvgl_text_button_class)) {
|
||||
/* text_button */
|
||||
lv_obj_add_style(obj, &theme->styles.text_button, 0);
|
||||
lv_obj_add_style(obj, &theme->styles.inverted, LV_STATE_PRESSED);
|
||||
lv_obj_add_style(obj, &theme->styles.focus_ring, LV_STATE_FOCUS_KEY);
|
||||
lv_obj_add_style(obj, &theme->styles.disabled, LV_STATE_DISABLED);
|
||||
return;
|
||||
}
|
||||
#if LV_USE_MSGBOX
|
||||
if(lv_obj_check_type(obj, &lv_msgbox_footer_button_class)) {
|
||||
/* msgbox_footer_button */
|
||||
|
||||
26
Modules/lvgl-module/source/widgets/text_button.cpp
Normal file
26
Modules/lvgl-module/source/widgets/text_button.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#define LV_USE_PRIVATE_API 1 // For the lv_obj_class_t declaration
|
||||
|
||||
#include <lvgl/widgets/text_button.h>
|
||||
|
||||
// Not static: the theme styles text buttons by this class
|
||||
extern "C" const lv_obj_class_t lvgl_text_button_class = {
|
||||
.base_class = &lv_button_class,
|
||||
.constructor_cb = nullptr,
|
||||
.destructor_cb = nullptr,
|
||||
.event_cb = nullptr,
|
||||
.user_data = nullptr,
|
||||
.name = "text_button",
|
||||
.width_def = 0,
|
||||
.height_def = 0,
|
||||
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
|
||||
.group_def = LV_OBJ_CLASS_GROUP_DEF_INHERIT,
|
||||
.instance_size = 0,
|
||||
.theme_inheritable = LV_OBJ_CLASS_THEME_INHERITABLE_FALSE
|
||||
};
|
||||
|
||||
lv_obj_t* lvgl_text_button_create(lv_obj_t* parent) {
|
||||
lv_obj_t* obj = lv_obj_class_create_obj(&lvgl_text_button_class, parent);
|
||||
lv_obj_class_init_obj(obj);
|
||||
return obj;
|
||||
}
|
||||
@ -38,6 +38,7 @@
|
||||
@widget flex_list: lvgl_list_class;
|
||||
@widget flex_list_text: lvgl_list_text_class;
|
||||
@widget flex_list_button: lvgl_list_button_class;
|
||||
@widget text_button: lvgl_text_button_class;
|
||||
|
||||
// ============================================================
|
||||
// 1. Base tokens
|
||||
@ -686,6 +687,11 @@ text_button_pressed {
|
||||
bgOpa: @opacity-state-pressed;
|
||||
}
|
||||
|
||||
// Room for the focus ring of a text button, which the parent would clip
|
||||
text_button_margin {
|
||||
marginAll: @focus-width + @focus-offset;
|
||||
}
|
||||
|
||||
icon_button {
|
||||
bgOpa: transp;
|
||||
textColor: @icon-button-color;
|
||||
@ -1420,6 +1426,16 @@ apply(msgbox_header_button) {
|
||||
disabled: state.disabled;
|
||||
}
|
||||
|
||||
apply(text_button) {
|
||||
text_button: default;
|
||||
text_button_margin: default;
|
||||
transition_exit: default;
|
||||
text_button_pressed: state.pressed;
|
||||
transition_enter: state.pressed;
|
||||
focus_ring: state.focusKey;
|
||||
disabled: state.disabled;
|
||||
}
|
||||
|
||||
apply(msgbox_footer_button) {
|
||||
text_button: default;
|
||||
transition_exit: default;
|
||||
|
||||
@ -36,6 +36,7 @@
|
||||
@widget flex_list: lvgl_list_class;
|
||||
@widget flex_list_text: lvgl_list_text_class;
|
||||
@widget flex_list_button: lvgl_list_button_class;
|
||||
@widget text_button: lvgl_text_button_class;
|
||||
|
||||
// ============================================================
|
||||
// 1. Base tokens
|
||||
@ -465,6 +466,19 @@ button {
|
||||
marginAll: @focus-width + @focus-offset;
|
||||
}
|
||||
|
||||
// A button without a container, for low-emphasis actions
|
||||
text_button {
|
||||
bgOpa: transp;
|
||||
textColor: @button-label-color;
|
||||
borderWidth: 0;
|
||||
radius: @button-shape;
|
||||
padHor: @button-pad-hor;
|
||||
padVer: @button-pad-ver;
|
||||
padGap: @button-gap;
|
||||
// Room for the focus ring, which the parent would clip
|
||||
marginAll: @focus-width + @focus-offset;
|
||||
}
|
||||
|
||||
icon_button {
|
||||
bgOpa: transp;
|
||||
textColor: @color-on-surface;
|
||||
@ -1059,6 +1073,13 @@ apply(menu_sidebar_header_cont > button, menu_main_header_cont > button, win > o
|
||||
focus_ring: state.focusKey;
|
||||
}
|
||||
|
||||
apply(text_button) {
|
||||
text_button: default;
|
||||
inverted: state.pressed;
|
||||
focus_ring: state.focusKey;
|
||||
disabled: state.disabled;
|
||||
}
|
||||
|
||||
apply(button, msgbox_footer_button) {
|
||||
button: default;
|
||||
inverted: state.pressed;
|
||||
|
||||
@ -62,7 +62,8 @@ public:
|
||||
void setSwipeNavigation(bool enabled) { swipeNavigation = enabled; }
|
||||
|
||||
/**
|
||||
* Creates the grid and populates it. Call setPageButtons() right after it to add page buttons.
|
||||
* Creates the grid and populates it, with a page indicator below it that is hidden when there's only one page.
|
||||
* Call setPageButtons() right after it to add page buttons.
|
||||
* @param[in] parent the container for the grid, with a flex column layout
|
||||
*/
|
||||
void createWidgets(lv_obj_t* parent);
|
||||
|
||||
@ -446,6 +446,11 @@ void TileGrid::createWidgets(lv_obj_t* parent) {
|
||||
barSideContainer = nullptr;
|
||||
barButtons.clear();
|
||||
createGrid(parent);
|
||||
// Centered below the grid
|
||||
pageIndicator = lvgl_page_indicator_create(parent);
|
||||
lv_obj_set_width(pageIndicator, LV_PCT(100));
|
||||
// Keeps the dots off the screen's bottom edge
|
||||
lv_obj_set_style_margin_bottom(pageIndicator, lvgl_theme_is_compact() ? LV_DPX(2) : LV_DPX(8), LV_STATE_DEFAULT);
|
||||
finishCreate(parent);
|
||||
}
|
||||
|
||||
|
||||
@ -27,8 +27,12 @@
|
||||
#include <tactility/drivers/wifi.h>
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <lvgl/devices/indev.h>
|
||||
#include <lvgl/fonts.h>
|
||||
#include <lvgl/grid_navigation.h>
|
||||
#include <lvgl/lvgl.h>
|
||||
#include <lvgl/widgets/page_indicator.h>
|
||||
#include <lvgl/widgets/text_button.h>
|
||||
#include <lvgl.h>
|
||||
|
||||
#include <functional>
|
||||
@ -102,14 +106,36 @@ struct Context {
|
||||
std::vector<StepConfiguration> steps;
|
||||
uint32_t pendingStepDialogId = 0;
|
||||
|
||||
// Valid while the window is shown
|
||||
lv_obj_t* titleLabel = nullptr;
|
||||
lv_obj_t* descriptionLabel = nullptr;
|
||||
lv_obj_t* backButton = nullptr;
|
||||
lv_obj_t* skipButton = nullptr;
|
||||
lv_obj_t* continueButton = nullptr;
|
||||
lv_obj_t* pageIndicator = nullptr;
|
||||
};
|
||||
|
||||
/** The welcome page, a page per step, and the completion page */
|
||||
uint32_t getPageCount(const Context* ctx) {
|
||||
return static_cast<uint32_t>(ctx->steps.size()) + 2;
|
||||
}
|
||||
|
||||
uint32_t getPage(const Context* ctx) {
|
||||
switch (ctx->phase) {
|
||||
case Phase::Welcome:
|
||||
return 0;
|
||||
case Phase::StepIntro:
|
||||
return static_cast<uint32_t>(ctx->stepIndex) + 1;
|
||||
case Phase::Done:
|
||||
return getPageCount(ctx) - 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void renderCurrent(Context* ctx) {
|
||||
lvgl_page_indicator_set_page(ctx->pageIndicator, getPage(ctx));
|
||||
lv_obj_set_hidden(ctx->backButton, ctx->phase == Phase::Welcome);
|
||||
switch (ctx->phase) {
|
||||
case Phase::Welcome: {
|
||||
lv_label_set_text(ctx->titleLabel, "Welcome");
|
||||
@ -146,10 +172,29 @@ void advanceTo(Context* ctx, size_t index) {
|
||||
}
|
||||
|
||||
lvgl_lock();
|
||||
renderCurrent(ctx);
|
||||
// Covered by a step's app: createWidgets() renders the current page when the window is shown again
|
||||
if (ctx->titleLabel != nullptr) {
|
||||
renderCurrent(ctx);
|
||||
}
|
||||
lvgl_unlock();
|
||||
}
|
||||
|
||||
void onBackClicked(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
// The previous page: a step's intro page, or the welcome page before the first step
|
||||
const size_t previousPage = getPage(ctx) - 1;
|
||||
if (ctx->phase == Phase::Welcome) {
|
||||
return;
|
||||
} else if (previousPage == 0) {
|
||||
ctx->phase = Phase::Welcome;
|
||||
lvgl_lock();
|
||||
renderCurrent(ctx);
|
||||
lvgl_unlock();
|
||||
} else {
|
||||
advanceTo(ctx, previousPage - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void onSkipClicked(lv_event_t* event) {
|
||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||
if (ctx->phase == Phase::StepIntro) {
|
||||
@ -193,23 +238,59 @@ void createWidgets(lv_obj_t* parent, void* userData) {
|
||||
int title_margin = lvgl_get_text_font_height(FONT_SIZE_LARGE);
|
||||
lv_obj_align_to(ctx->titleLabel, ctx->descriptionLabel, LV_ALIGN_OUT_TOP_MID, 0, -title_margin);
|
||||
|
||||
ctx->skipButton = lv_button_create(parent);
|
||||
lv_obj_t* skip_label = lv_label_create(ctx->skipButton);
|
||||
lv_label_set_text(skip_label, "Skip");
|
||||
lv_obj_center(skip_label);
|
||||
lv_obj_align(ctx->skipButton, LV_ALIGN_BOTTOM_LEFT, 12, -12);
|
||||
// Back on the left, the page indicator in the middle, and Skip next to Continue on the right
|
||||
auto* buttonRow = lv_obj_create(parent);
|
||||
lv_obj_remove_style_all(buttonRow);
|
||||
lv_obj_set_size(buttonRow, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_pad_hor(buttonRow, 8, LV_STATE_DEFAULT);
|
||||
lv_obj_set_flex_flow(buttonRow, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(buttonRow, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_align(buttonRow, LV_ALIGN_BOTTOM_MID, 0, -8);
|
||||
|
||||
ctx->backButton = lvgl_text_button_create(buttonRow);
|
||||
lv_label_set_text(lv_label_create(ctx->backButton), "Back");
|
||||
lv_obj_add_event_cb(ctx->backButton, onBackClicked, LV_EVENT_SHORT_CLICKED, ctx);
|
||||
|
||||
auto* spacer = lv_obj_create(buttonRow);
|
||||
lv_obj_remove_style_all(spacer);
|
||||
lv_obj_set_height(spacer, 1);
|
||||
lv_obj_set_flex_grow(spacer, 1);
|
||||
|
||||
ctx->skipButton = lvgl_text_button_create(buttonRow);
|
||||
lv_label_set_text(lv_label_create(ctx->skipButton), "Skip");
|
||||
lv_obj_add_event_cb(ctx->skipButton, onSkipClicked, LV_EVENT_SHORT_CLICKED, ctx);
|
||||
|
||||
ctx->continueButton = lv_button_create(parent);
|
||||
lv_obj_t* continue_label = lv_label_create(ctx->continueButton);
|
||||
lv_label_set_text(continue_label, "Continue");
|
||||
lv_obj_center(continue_label);
|
||||
lv_obj_align(ctx->continueButton, LV_ALIGN_BOTTOM_RIGHT, -12, -12);
|
||||
ctx->continueButton = lv_button_create(buttonRow);
|
||||
lv_label_set_text(lv_label_create(ctx->continueButton), "Continue");
|
||||
lv_obj_add_event_cb(ctx->continueButton, onContinueClicked, LV_EVENT_SHORT_CLICKED, ctx);
|
||||
|
||||
// Centered on the row, outside the flex layout. It shows the page number instead of the dots when they don't fit.
|
||||
ctx->pageIndicator = lvgl_page_indicator_create(buttonRow);
|
||||
lv_obj_add_flag(ctx->pageIndicator, LV_OBJ_FLAG_IGNORE_LAYOUT);
|
||||
lv_obj_set_style_max_width(ctx->pageIndicator, LV_PCT(25), LV_STATE_DEFAULT);
|
||||
lvgl_page_indicator_set_page_count(ctx->pageIndicator, getPageCount(ctx));
|
||||
lv_obj_align(ctx->pageIndicator, LV_ALIGN_CENTER, 0, 0);
|
||||
|
||||
// The arrow keys move between the buttons, and Continue has the focus first
|
||||
lvgl_grid_navigation_add(buttonRow);
|
||||
if (!lvgl_indev_exists(LV_INDEV_TYPE_POINTER)) {
|
||||
lv_group_focus_obj(buttonRow);
|
||||
lv_gridnav_set_focused(buttonRow, ctx->continueButton, LV_ANIM_OFF);
|
||||
}
|
||||
|
||||
renderCurrent(ctx);
|
||||
}
|
||||
|
||||
void destroyWidgets(void* userData) {
|
||||
auto* ctx = static_cast<Context*>(userData);
|
||||
ctx->titleLabel = nullptr;
|
||||
ctx->descriptionLabel = nullptr;
|
||||
ctx->backButton = nullptr;
|
||||
ctx->skipButton = nullptr;
|
||||
ctx->continueButton = nullptr;
|
||||
ctx->pageIndicator = nullptr;
|
||||
}
|
||||
|
||||
int32_t appMain(int argc, char* argv[]) {
|
||||
uint32_t appInstanceId = app_scheduler_current_app_id();
|
||||
Context ctx {};
|
||||
@ -288,7 +369,7 @@ int32_t appMain(int argc, char* argv[]) {
|
||||
AppEventSubscription sub {};
|
||||
check(app_event_subscribe(&sub, &event_group) == ERROR_NONE);
|
||||
|
||||
WindowId window = window_manager_create(appInstanceId, createWidgets, &ctx);
|
||||
WindowId window = window_manager_create_ext(appInstanceId, createWidgets, destroyWidgets, &ctx);
|
||||
|
||||
bool shouldClose = false;
|
||||
while (!shouldClose) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user