mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 02:45:05 +00:00
Launcher improvements
This commit is contained in:
parent
b088c35449
commit
a6b880e33f
@ -28,6 +28,13 @@ void lvgl_grid_navigation_remove(lv_obj_t* container);
|
|||||||
*/
|
*/
|
||||||
void lvgl_grid_navigation_step(lv_group_t* group, bool next);
|
void lvgl_grid_navigation_step(lv_group_t* group, bool next);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Hides the selection that keys made, e.g. when the touchscreen is used. The focus stays where it is,
|
||||||
|
* and the next key that moves the focus shows the selection again.
|
||||||
|
* @param[in] group the focus group
|
||||||
|
*/
|
||||||
|
void lvgl_focus_hide_key_selection(lv_group_t* group);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param[in] container a container that was passed to lvgl_grid_navigation_add()
|
* @param[in] container a container that was passed to lvgl_grid_navigation_add()
|
||||||
* @return the focused child, or NULL when no child is focused
|
* @return the focused child, or NULL when no child is focused
|
||||||
|
|||||||
37
Modules/lvgl-module/include/lvgl/widgets/page_indicator.h
Normal file
37
Modules/lvgl-module/include/lvgl/widgets/page_indicator.h
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a page indicator: a row of dots with one dot per page, where the dot of the current page is filled.
|
||||||
|
* The theme determines the size and colour of the dots.
|
||||||
|
* When the dots are wider than the indicator's maximum width (lv_obj_set_style_max_width()), it shows the page number instead, e.g. "2/12".
|
||||||
|
* @param[in] parent the parent object
|
||||||
|
* @return the created page indicator
|
||||||
|
*/
|
||||||
|
lv_obj_t* lvgl_page_indicator_create(lv_obj_t* parent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the number of pages, which is the number of dots.
|
||||||
|
* @param[in] obj the page indicator
|
||||||
|
* @param[in] count the number of pages
|
||||||
|
*/
|
||||||
|
void lvgl_page_indicator_set_page_count(lv_obj_t* obj, uint32_t count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the current page, whose dot is filled.
|
||||||
|
* @param[in] obj the page indicator
|
||||||
|
* @param[in] page the zero-based index of the current page
|
||||||
|
*/
|
||||||
|
void lvgl_page_indicator_set_page(lv_obj_t* obj, uint32_t page);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -1,6 +1,7 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
#include <lvgl/devices/pointer.h>
|
#include <lvgl/devices/pointer.h>
|
||||||
#include <lvgl/devices/device_context.h>
|
#include <lvgl/devices/device_context.h>
|
||||||
|
#include <lvgl/grid_navigation.h>
|
||||||
|
|
||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
#include <tactility/drivers/pointer.h>
|
#include <tactility/drivers/pointer.h>
|
||||||
@ -140,8 +141,8 @@ static void lvgl_pointer_pool_refresh(struct LvglPointerPool* pool, int32_t nati
|
|||||||
// the touch controller reports points in a different order (no touch-ID/tracking field exists
|
// the touch controller reports points in a different order (no touch-ID/tracking field exists
|
||||||
// anywhere in this stack - see esp_lcd_touch_get_coordinates()/PointerApi.get_touched_points()).
|
// anywhere in this stack - see esp_lcd_touch_get_coordinates()/PointerApi.get_touched_points()).
|
||||||
// Unmatched raw points (new touches) claim the nearest inactive slot. Slots with no matching
|
// Unmatched raw points (new touches) claim the nearest inactive slot. Slots with no matching
|
||||||
// point this round go inactive (RELEASED).
|
// point this round go inactive (RELEASED). Returns true when a new touch started.
|
||||||
static void lvgl_pointer_pool_assign(struct LvglPointerPool* pool, int32_t native_x_max) {
|
static bool lvgl_pointer_pool_assign(struct LvglPointerPool* pool, int32_t native_x_max) {
|
||||||
// Touch drivers clamp raw coordinates to the panel's configured native resolution regardless
|
// Touch drivers clamp raw coordinates to the panel's configured native resolution regardless
|
||||||
// of calibration, so native_x_max is a valid scale reference for the distance cap even when calibration is disabled.
|
// of calibration, so native_x_max is a valid scale reference for the distance cap even when calibration is disabled.
|
||||||
// Falls back to a conservative fixed pixel value if the display/resolution isn't available for some reason.
|
// Falls back to a conservative fixed pixel value if the display/resolution isn't available for some reason.
|
||||||
@ -182,6 +183,7 @@ static void lvgl_pointer_pool_assign(struct LvglPointerPool* pool, int32_t nativ
|
|||||||
|
|
||||||
// Second pass: any unclaimed raw point is a new touch - hand it to the first inactive slot
|
// Second pass: any unclaimed raw point is a new touch - hand it to the first inactive slot
|
||||||
// that wasn't just released this round.
|
// that wasn't just released this round.
|
||||||
|
bool touch_started = false;
|
||||||
for (uint8_t r = 0; r < pool->raw_count; r++) {
|
for (uint8_t r = 0; r < pool->raw_count; r++) {
|
||||||
if (raw_claimed[r]) continue;
|
if (raw_claimed[r]) continue;
|
||||||
for (uint8_t s = 0; s < pool->slot_count; s++) {
|
for (uint8_t s = 0; s < pool->slot_count; s++) {
|
||||||
@ -190,9 +192,11 @@ static void lvgl_pointer_pool_assign(struct LvglPointerPool* pool, int32_t nativ
|
|||||||
pool->slot_point[s].x = (lv_coord_t)pool->raw_x[r];
|
pool->slot_point[s].x = (lv_coord_t)pool->raw_x[r];
|
||||||
pool->slot_point[s].y = (lv_coord_t)pool->raw_y[r];
|
pool->slot_point[s].y = (lv_coord_t)pool->raw_y[r];
|
||||||
raw_claimed[r] = true;
|
raw_claimed[r] = true;
|
||||||
|
touch_started = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return touch_started;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The actual LVGL indev read callback, shared by every slot in the pool. Only the first slot to
|
// The actual LVGL indev read callback, shared by every slot in the pool. Only the first slot to
|
||||||
@ -219,7 +223,11 @@ static void lvgl_pointer_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
|||||||
int32_t native_x_max = display != NULL ? lv_display_get_original_horizontal_resolution(display) - 1 : 0;
|
int32_t native_x_max = display != NULL ? lv_display_get_original_horizontal_resolution(display) - 1 : 0;
|
||||||
int32_t native_y_max = display != NULL ? lv_display_get_original_vertical_resolution(display) - 1 : 0;
|
int32_t native_y_max = display != NULL ? lv_display_get_original_vertical_resolution(display) - 1 : 0;
|
||||||
lvgl_pointer_pool_refresh(pool, native_x_max, native_y_max);
|
lvgl_pointer_pool_refresh(pool, native_x_max, native_y_max);
|
||||||
lvgl_pointer_pool_assign(pool, native_x_max);
|
// A touch hides the selection that keys made, so it doesn't look like two widgets are selected.
|
||||||
|
// This runs before LVGL handles the press, so a widget that the touch focuses isn't affected.
|
||||||
|
if (lvgl_pointer_pool_assign(pool, native_x_max) && lv_group_get_default() != NULL) {
|
||||||
|
lvgl_focus_hide_key_selection(lv_group_get_default());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pool->round_pos = (uint8_t)((pool->round_pos + 1) % pool->slot_count);
|
pool->round_pos = (uint8_t)((pool->round_pos + 1) % pool->slot_count);
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
#include <lvgl/devices/device_context.h>
|
#include <lvgl/devices/device_context.h>
|
||||||
#include <lvgl/devices/keyboard.h>
|
#include <lvgl/devices/keyboard.h>
|
||||||
#include <lvgl/devices/keyboard_private.h>
|
#include <lvgl/devices/keyboard_private.h>
|
||||||
|
#include <lvgl/grid_navigation.h>
|
||||||
#include <lvgl/lvgl.h>
|
#include <lvgl/lvgl.h>
|
||||||
|
|
||||||
#include <tactility/drivers/keyboard.h>
|
#include <tactility/drivers/keyboard.h>
|
||||||
@ -25,6 +26,8 @@ struct LvglTrackballCtx {
|
|||||||
int32_t pending_count;
|
int32_t pending_count;
|
||||||
// Keys mode: the LVGL key that is reported as pressed, or 0
|
// Keys mode: the LVGL key that is reported as pressed, or 0
|
||||||
uint32_t pressed_key;
|
uint32_t pressed_key;
|
||||||
|
// Pointer mode: whether the button was pressed during the previous read
|
||||||
|
bool pointer_pressed;
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline int32_t clamp(int32_t value, int32_t min_value, int32_t max_value) {
|
static inline int32_t clamp(int32_t value, int32_t min_value, int32_t max_value) {
|
||||||
@ -153,6 +156,12 @@ static void lvgl_trackball_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
|||||||
data->point.y = static_cast<int16_t>(ctx->cursor_y);
|
data->point.y = static_cast<int16_t>(ctx->cursor_y);
|
||||||
data->state = pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
|
data->state = pressed ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
|
||||||
|
|
||||||
|
// Like a touch, a click hides the selection that keys made
|
||||||
|
if (pressed && !ctx->pointer_pressed && lv_group_get_default() != nullptr) {
|
||||||
|
lvgl_focus_hide_key_selection(lv_group_get_default());
|
||||||
|
}
|
||||||
|
ctx->pointer_pressed = pressed;
|
||||||
|
|
||||||
if (pressed) {
|
if (pressed) {
|
||||||
lv_display_trigger_activity(display);
|
lv_display_trigger_activity(display);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,6 +110,22 @@ void lvgl_grid_navigation_step(lv_group_t* group, bool next) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void lvgl_focus_hide_key_selection(lv_group_t* group) {
|
||||||
|
lv_obj_t* focused = lv_group_get_focused(group);
|
||||||
|
if (focused == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lv_group_set_editing(group, false);
|
||||||
|
constexpr auto key_states = static_cast<lv_state_t>(LV_STATE_FOCUS_KEY | LV_STATE_EDITED);
|
||||||
|
lv_obj_remove_state(focused, key_states);
|
||||||
|
if (lvgl_grid_navigation_is_container(focused)) {
|
||||||
|
lv_obj_t* child = lvgl_grid_navigation_get_focused(focused);
|
||||||
|
if (child != nullptr) {
|
||||||
|
lv_obj_remove_state(child, key_states);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lv_obj_t* lvgl_grid_navigation_get_focused(lv_obj_t* container) {
|
lv_obj_t* lvgl_grid_navigation_get_focused(lv_obj_t* container) {
|
||||||
// Grid navigation marks the focused child with the focused state
|
// Grid navigation marks the focused child with the focused state
|
||||||
const uint32_t child_count = lv_obj_get_child_count(container);
|
const uint32_t child_count = lv_obj_get_child_count(container);
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
#include <lvgl/widgets/card.h>
|
#include <lvgl/widgets/card.h>
|
||||||
#include <lvgl/widgets/chip.h>
|
#include <lvgl/widgets/chip.h>
|
||||||
#include <lvgl/widgets/icon_button.h>
|
#include <lvgl/widgets/icon_button.h>
|
||||||
|
#include <lvgl/widgets/page_indicator.h>
|
||||||
#include <lvgl/widgets/spinner.h>
|
#include <lvgl/widgets/spinner.h>
|
||||||
#include <lvgl/widgets/toolbar.h>
|
#include <lvgl/widgets/toolbar.h>
|
||||||
|
|
||||||
@ -69,6 +70,7 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(lvgl_grid_navigation_step),
|
DEFINE_MODULE_SYMBOL(lvgl_grid_navigation_step),
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_grid_navigation_get_focused),
|
DEFINE_MODULE_SYMBOL(lvgl_grid_navigation_get_focused),
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_grid_navigation_is_container),
|
DEFINE_MODULE_SYMBOL(lvgl_grid_navigation_is_container),
|
||||||
|
DEFINE_MODULE_SYMBOL(lvgl_focus_hide_key_selection),
|
||||||
// lvgl_insets
|
// lvgl_insets
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_display_get_shape),
|
DEFINE_MODULE_SYMBOL(lvgl_display_get_shape),
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_display_get_insets),
|
DEFINE_MODULE_SYMBOL(lvgl_display_get_insets),
|
||||||
@ -81,6 +83,9 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
|||||||
DEFINE_MODULE_SYMBOL(lvgl_theme_is_mono),
|
DEFINE_MODULE_SYMBOL(lvgl_theme_is_mono),
|
||||||
// lvgl_badge
|
// lvgl_badge
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_badge_create),
|
DEFINE_MODULE_SYMBOL(lvgl_badge_create),
|
||||||
|
DEFINE_MODULE_SYMBOL(lvgl_page_indicator_create),
|
||||||
|
DEFINE_MODULE_SYMBOL(lvgl_page_indicator_set_page_count),
|
||||||
|
DEFINE_MODULE_SYMBOL(lvgl_page_indicator_set_page),
|
||||||
// lvgl_card
|
// lvgl_card
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_card_create),
|
DEFINE_MODULE_SYMBOL(lvgl_card_create),
|
||||||
// lvgl_chip
|
// lvgl_chip
|
||||||
|
|||||||
@ -16,6 +16,8 @@ extern const lv_obj_class_t lvgl_sliderbox_class;
|
|||||||
extern const lv_obj_class_t lvgl_card_class;
|
extern const lv_obj_class_t lvgl_card_class;
|
||||||
extern const lv_obj_class_t lvgl_chip_class;
|
extern const lv_obj_class_t lvgl_chip_class;
|
||||||
extern const lv_obj_class_t lvgl_badge_class;
|
extern const lv_obj_class_t lvgl_badge_class;
|
||||||
|
extern const lv_obj_class_t lvgl_page_indicator_class;
|
||||||
|
extern const lv_obj_class_t lvgl_page_indicator_dot_class;
|
||||||
|
|
||||||
#define NEUTRAL_4 lv_color_hex(0x0E0E0E)
|
#define NEUTRAL_4 lv_color_hex(0x0E0E0E)
|
||||||
#define NEUTRAL_6 lv_color_hex(0x131313)
|
#define NEUTRAL_6 lv_color_hex(0x131313)
|
||||||
@ -137,6 +139,9 @@ extern const lv_obj_class_t lvgl_badge_class;
|
|||||||
#define CHIP_SHAPE (theme->config.is_compact ? CORNER_COMPACT : CORNER_SMALL)
|
#define CHIP_SHAPE (theme->config.is_compact ? CORNER_COMPACT : CORNER_SMALL)
|
||||||
#define CHIP_PAD_HOR (theme->config.is_compact ? 3 : SPACE_4)
|
#define CHIP_PAD_HOR (theme->config.is_compact ? 3 : SPACE_4)
|
||||||
#define CHIP_PAD_VER (theme->config.is_compact ? 2 : LV_DPX_CALC(theme->disp_dpi, 6))
|
#define CHIP_PAD_VER (theme->config.is_compact ? 2 : LV_DPX_CALC(theme->disp_dpi, 6))
|
||||||
|
#define PAGE_INDICATOR_COLOR COLOR_ON_SURFACE_VARIANT
|
||||||
|
#define PAGE_INDICATOR_DOT_SIZE (theme->config.is_compact ? 4 : LV_DPX_CALC(theme->disp_dpi, 6))
|
||||||
|
#define PAGE_INDICATOR_GAP (theme->config.is_compact ? 3 : LV_DPX_CALC(theme->disp_dpi, 6))
|
||||||
#define BADGE_COLOR COLOR_SECONDARY
|
#define BADGE_COLOR COLOR_SECONDARY
|
||||||
#define BADGE_SIZE (theme->config.is_compact ? 4 : LV_DPX_CALC(theme->disp_dpi, 6))
|
#define BADGE_SIZE (theme->config.is_compact ? 4 : LV_DPX_CALC(theme->disp_dpi, 6))
|
||||||
#define SCROLLBAR_COLOR COLOR_OUTLINE
|
#define SCROLLBAR_COLOR COLOR_OUTLINE
|
||||||
@ -334,6 +339,12 @@ typedef struct {
|
|||||||
lv_style_t screen;
|
lv_style_t screen;
|
||||||
lv_style_t container;
|
lv_style_t container;
|
||||||
lv_style_t card;
|
lv_style_t card;
|
||||||
|
lv_style_t page_indicator;
|
||||||
|
#if LV_USE_LABEL
|
||||||
|
lv_style_t page_indicator_label;
|
||||||
|
#endif
|
||||||
|
lv_style_t page_indicator_dot;
|
||||||
|
lv_style_t page_indicator_dot_current;
|
||||||
lv_style_t badge;
|
lv_style_t badge;
|
||||||
lv_style_t chip;
|
lv_style_t chip;
|
||||||
lv_style_t chip_selected;
|
lv_style_t chip_selected;
|
||||||
@ -758,6 +769,29 @@ static void style_init(my_theme_t * theme)
|
|||||||
lv_style_set_pad_all(&theme->styles.card, CARD_PAD);
|
lv_style_set_pad_all(&theme->styles.card, CARD_PAD);
|
||||||
lv_style_set_pad_gap(&theme->styles.card, CARD_GAP);
|
lv_style_set_pad_gap(&theme->styles.card, CARD_GAP);
|
||||||
|
|
||||||
|
style_init_reset(&theme->styles.page_indicator);
|
||||||
|
lv_style_set_bg_opa(&theme->styles.page_indicator, LV_OPA_TRANSP);
|
||||||
|
lv_style_set_border_width(&theme->styles.page_indicator, 0);
|
||||||
|
lv_style_set_pad_all(&theme->styles.page_indicator, 0);
|
||||||
|
lv_style_set_pad_gap(&theme->styles.page_indicator, PAGE_INDICATOR_GAP);
|
||||||
|
|
||||||
|
#if LV_USE_LABEL
|
||||||
|
style_init_reset(&theme->styles.page_indicator_label);
|
||||||
|
lv_style_set_text_color(&theme->styles.page_indicator_label, PAGE_INDICATOR_COLOR);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
style_init_reset(&theme->styles.page_indicator_dot);
|
||||||
|
lv_style_set_width(&theme->styles.page_indicator_dot, PAGE_INDICATOR_DOT_SIZE);
|
||||||
|
lv_style_set_height(&theme->styles.page_indicator_dot, PAGE_INDICATOR_DOT_SIZE);
|
||||||
|
lv_style_set_radius(&theme->styles.page_indicator_dot, LV_RADIUS_CIRCLE);
|
||||||
|
lv_style_set_bg_color(&theme->styles.page_indicator_dot, PAGE_INDICATOR_COLOR);
|
||||||
|
lv_style_set_bg_opa(&theme->styles.page_indicator_dot, LV_OPA_TRANSP);
|
||||||
|
lv_style_set_border_color(&theme->styles.page_indicator_dot, PAGE_INDICATOR_COLOR);
|
||||||
|
lv_style_set_border_width(&theme->styles.page_indicator_dot, 1);
|
||||||
|
|
||||||
|
style_init_reset(&theme->styles.page_indicator_dot_current);
|
||||||
|
lv_style_set_bg_opa(&theme->styles.page_indicator_dot_current, LV_OPA_COVER);
|
||||||
|
|
||||||
style_init_reset(&theme->styles.badge);
|
style_init_reset(&theme->styles.badge);
|
||||||
lv_style_set_width(&theme->styles.badge, BADGE_SIZE);
|
lv_style_set_width(&theme->styles.badge, BADGE_SIZE);
|
||||||
lv_style_set_height(&theme->styles.badge, BADGE_SIZE);
|
lv_style_set_height(&theme->styles.badge, BADGE_SIZE);
|
||||||
@ -1802,6 +1836,11 @@ static void theme_apply(lv_theme_t * th, lv_obj_t * obj)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
/* page_indicator > label */
|
||||||
|
if(lv_obj_check_type(parent, &lvgl_page_indicator_class)) {
|
||||||
|
lv_obj_add_style(obj, &theme->styles.page_indicator_label, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -1833,6 +1872,17 @@ static void theme_apply(lv_theme_t * th, lv_obj_t * obj)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
if(lv_obj_check_type(obj, &lvgl_page_indicator_class)) {
|
||||||
|
/* page_indicator */
|
||||||
|
lv_obj_add_style(obj, &theme->styles.page_indicator, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(lv_obj_check_type(obj, &lvgl_page_indicator_dot_class)) {
|
||||||
|
/* page_indicator_dot */
|
||||||
|
lv_obj_add_style(obj, &theme->styles.page_indicator_dot, 0);
|
||||||
|
lv_obj_add_style(obj, &theme->styles.page_indicator_dot_current, LV_STATE_CHECKED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(lv_obj_check_type(obj, &lvgl_badge_class)) {
|
if(lv_obj_check_type(obj, &lvgl_badge_class)) {
|
||||||
/* badge */
|
/* badge */
|
||||||
lv_obj_add_style(obj, &theme->styles.badge, 0);
|
lv_obj_add_style(obj, &theme->styles.badge, 0);
|
||||||
|
|||||||
@ -16,6 +16,8 @@ extern const lv_obj_class_t lvgl_sliderbox_class;
|
|||||||
extern const lv_obj_class_t lvgl_card_class;
|
extern const lv_obj_class_t lvgl_card_class;
|
||||||
extern const lv_obj_class_t lvgl_chip_class;
|
extern const lv_obj_class_t lvgl_chip_class;
|
||||||
extern const lv_obj_class_t lvgl_badge_class;
|
extern const lv_obj_class_t lvgl_badge_class;
|
||||||
|
extern const lv_obj_class_t lvgl_page_indicator_class;
|
||||||
|
extern const lv_obj_class_t lvgl_page_indicator_dot_class;
|
||||||
|
|
||||||
#define BLACK lv_color_black()
|
#define BLACK lv_color_black()
|
||||||
#define WHITE lv_color_white()
|
#define WHITE lv_color_white()
|
||||||
@ -87,6 +89,9 @@ extern const lv_obj_class_t lvgl_badge_class;
|
|||||||
#define CHIP_SHAPE (theme->config.is_compact ? CORNER_COMPACT : CORNER_SMALL)
|
#define CHIP_SHAPE (theme->config.is_compact ? CORNER_COMPACT : CORNER_SMALL)
|
||||||
#define CHIP_PAD_HOR (theme->config.is_compact ? 3 : SPACE_4)
|
#define CHIP_PAD_HOR (theme->config.is_compact ? 3 : SPACE_4)
|
||||||
#define CHIP_PAD_VER (theme->config.is_compact ? 2 : SPACE_2)
|
#define CHIP_PAD_VER (theme->config.is_compact ? 2 : SPACE_2)
|
||||||
|
#define PAGE_INDICATOR_COLOR COLOR_ON_SURFACE
|
||||||
|
#define PAGE_INDICATOR_DOT_SIZE (theme->config.is_compact ? 4 : LV_DPX_CALC(theme->disp_dpi, 6))
|
||||||
|
#define PAGE_INDICATOR_GAP (theme->config.is_compact ? 3 : LV_DPX_CALC(theme->disp_dpi, 6))
|
||||||
#define BADGE_COLOR COLOR_ON_SURFACE
|
#define BADGE_COLOR COLOR_ON_SURFACE
|
||||||
#define BADGE_SIZE (theme->config.is_compact ? 4 : LV_DPX_CALC(theme->disp_dpi, 6))
|
#define BADGE_SIZE (theme->config.is_compact ? 4 : LV_DPX_CALC(theme->disp_dpi, 6))
|
||||||
#define SCROLLBAR_COLOR COLOR_ON_SURFACE
|
#define SCROLLBAR_COLOR COLOR_ON_SURFACE
|
||||||
@ -216,6 +221,12 @@ typedef struct {
|
|||||||
lv_style_t screen;
|
lv_style_t screen;
|
||||||
lv_style_t container;
|
lv_style_t container;
|
||||||
lv_style_t card;
|
lv_style_t card;
|
||||||
|
lv_style_t page_indicator;
|
||||||
|
#if LV_USE_LABEL
|
||||||
|
lv_style_t page_indicator_label;
|
||||||
|
#endif
|
||||||
|
lv_style_t page_indicator_dot;
|
||||||
|
lv_style_t page_indicator_dot_current;
|
||||||
lv_style_t badge;
|
lv_style_t badge;
|
||||||
lv_style_t chip;
|
lv_style_t chip;
|
||||||
#if LV_USE_BUTTON || LV_USE_MSGBOX
|
#if LV_USE_BUTTON || LV_USE_MSGBOX
|
||||||
@ -548,6 +559,29 @@ static void style_init(my_theme_t * theme)
|
|||||||
lv_style_set_pad_all(&theme->styles.card, CARD_PAD);
|
lv_style_set_pad_all(&theme->styles.card, CARD_PAD);
|
||||||
lv_style_set_pad_gap(&theme->styles.card, CARD_GAP);
|
lv_style_set_pad_gap(&theme->styles.card, CARD_GAP);
|
||||||
|
|
||||||
|
style_init_reset(&theme->styles.page_indicator);
|
||||||
|
lv_style_set_bg_opa(&theme->styles.page_indicator, LV_OPA_TRANSP);
|
||||||
|
lv_style_set_border_width(&theme->styles.page_indicator, 0);
|
||||||
|
lv_style_set_pad_all(&theme->styles.page_indicator, 0);
|
||||||
|
lv_style_set_pad_gap(&theme->styles.page_indicator, PAGE_INDICATOR_GAP);
|
||||||
|
|
||||||
|
#if LV_USE_LABEL
|
||||||
|
style_init_reset(&theme->styles.page_indicator_label);
|
||||||
|
lv_style_set_text_color(&theme->styles.page_indicator_label, PAGE_INDICATOR_COLOR);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
style_init_reset(&theme->styles.page_indicator_dot);
|
||||||
|
lv_style_set_width(&theme->styles.page_indicator_dot, PAGE_INDICATOR_DOT_SIZE);
|
||||||
|
lv_style_set_height(&theme->styles.page_indicator_dot, PAGE_INDICATOR_DOT_SIZE);
|
||||||
|
lv_style_set_radius(&theme->styles.page_indicator_dot, LV_RADIUS_CIRCLE);
|
||||||
|
lv_style_set_bg_color(&theme->styles.page_indicator_dot, PAGE_INDICATOR_COLOR);
|
||||||
|
lv_style_set_bg_opa(&theme->styles.page_indicator_dot, LV_OPA_TRANSP);
|
||||||
|
lv_style_set_border_color(&theme->styles.page_indicator_dot, PAGE_INDICATOR_COLOR);
|
||||||
|
lv_style_set_border_width(&theme->styles.page_indicator_dot, 1);
|
||||||
|
|
||||||
|
style_init_reset(&theme->styles.page_indicator_dot_current);
|
||||||
|
lv_style_set_bg_opa(&theme->styles.page_indicator_dot_current, LV_OPA_COVER);
|
||||||
|
|
||||||
style_init_reset(&theme->styles.badge);
|
style_init_reset(&theme->styles.badge);
|
||||||
lv_style_set_width(&theme->styles.badge, BADGE_SIZE);
|
lv_style_set_width(&theme->styles.badge, BADGE_SIZE);
|
||||||
lv_style_set_height(&theme->styles.badge, BADGE_SIZE);
|
lv_style_set_height(&theme->styles.badge, BADGE_SIZE);
|
||||||
@ -1401,6 +1435,11 @@ static void theme_apply(lv_theme_t * th, lv_obj_t * obj)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
/* page_indicator > label */
|
||||||
|
if(lv_obj_check_type(parent, &lvgl_page_indicator_class)) {
|
||||||
|
lv_obj_add_style(obj, &theme->styles.page_indicator_label, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -1428,6 +1467,17 @@ static void theme_apply(lv_theme_t * th, lv_obj_t * obj)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
if(lv_obj_check_type(obj, &lvgl_page_indicator_class)) {
|
||||||
|
/* page_indicator */
|
||||||
|
lv_obj_add_style(obj, &theme->styles.page_indicator, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(lv_obj_check_type(obj, &lvgl_page_indicator_dot_class)) {
|
||||||
|
/* page_indicator_dot */
|
||||||
|
lv_obj_add_style(obj, &theme->styles.page_indicator_dot, 0);
|
||||||
|
lv_obj_add_style(obj, &theme->styles.page_indicator_dot_current, LV_STATE_CHECKED);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if(lv_obj_check_type(obj, &lvgl_badge_class)) {
|
if(lv_obj_check_type(obj, &lvgl_badge_class)) {
|
||||||
/* badge */
|
/* badge */
|
||||||
lv_obj_add_style(obj, &theme->styles.badge, 0);
|
lv_obj_add_style(obj, &theme->styles.badge, 0);
|
||||||
|
|||||||
134
Modules/lvgl-module/source/widgets/page_indicator.cpp
Normal file
134
Modules/lvgl-module/source/widgets/page_indicator.cpp
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#define LV_USE_PRIVATE_API 1 // For the lv_obj_class_t declaration
|
||||||
|
|
||||||
|
#include <lvgl/widgets/page_indicator.h>
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
static void refresh(lv_obj_t* obj);
|
||||||
|
|
||||||
|
static void on_style_changed(lv_event_t* event) {
|
||||||
|
refresh(lv_event_get_current_target_obj(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void page_indicator_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) {
|
||||||
|
LV_UNUSED(class_p);
|
||||||
|
lv_obj_remove_flag(obj, LV_OBJ_FLAG_CLICKABLE);
|
||||||
|
lv_obj_remove_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW);
|
||||||
|
lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
|
// Shows the page numbers instead of the dots when they don't fit in the indicator's maximum width
|
||||||
|
lv_obj_t* label = lv_label_create(obj);
|
||||||
|
lv_obj_add_flag(label, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
// A change of the maximum width can switch between dots and numbers
|
||||||
|
lv_obj_add_event_cb(obj, on_style_changed, LV_EVENT_STYLE_CHANGED, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void page_indicator_dot_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj) {
|
||||||
|
LV_UNUSED(class_p);
|
||||||
|
lv_obj_remove_flag(obj, LV_OBJ_FLAG_CLICKABLE);
|
||||||
|
lv_obj_remove_flag(obj, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not static: the theme styles page indicators and their dots by these classes
|
||||||
|
extern "C" const lv_obj_class_t lvgl_page_indicator_class = {
|
||||||
|
.base_class = &lv_obj_class,
|
||||||
|
.constructor_cb = page_indicator_constructor,
|
||||||
|
.destructor_cb = nullptr,
|
||||||
|
.event_cb = nullptr,
|
||||||
|
.user_data = nullptr,
|
||||||
|
.name = "page_indicator",
|
||||||
|
.width_def = LV_SIZE_CONTENT,
|
||||||
|
.height_def = LV_SIZE_CONTENT,
|
||||||
|
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
|
||||||
|
.group_def = LV_OBJ_CLASS_GROUP_DEF_FALSE,
|
||||||
|
.instance_size = 0,
|
||||||
|
.theme_inheritable = LV_OBJ_CLASS_THEME_INHERITABLE_FALSE
|
||||||
|
};
|
||||||
|
|
||||||
|
extern "C" const lv_obj_class_t lvgl_page_indicator_dot_class = {
|
||||||
|
.base_class = &lv_obj_class,
|
||||||
|
.constructor_cb = page_indicator_dot_constructor,
|
||||||
|
.destructor_cb = nullptr,
|
||||||
|
.event_cb = nullptr,
|
||||||
|
.user_data = nullptr,
|
||||||
|
.name = "page_indicator_dot",
|
||||||
|
.width_def = 0,
|
||||||
|
.height_def = 0,
|
||||||
|
.editable = LV_OBJ_CLASS_EDITABLE_INHERIT,
|
||||||
|
.group_def = LV_OBJ_CLASS_GROUP_DEF_FALSE,
|
||||||
|
.instance_size = 0,
|
||||||
|
.theme_inheritable = LV_OBJ_CLASS_THEME_INHERITABLE_FALSE
|
||||||
|
};
|
||||||
|
|
||||||
|
// The first child is the label, the dots follow
|
||||||
|
static lv_obj_t* get_label(lv_obj_t* obj) {
|
||||||
|
return lv_obj_get_child(obj, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t get_dot_count(lv_obj_t* obj) {
|
||||||
|
return lv_obj_get_child_count(obj) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static lv_obj_t* get_dot(lv_obj_t* obj, uint32_t index) {
|
||||||
|
return lv_obj_get_child(obj, static_cast<int32_t>(index + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void refresh(lv_obj_t* obj) {
|
||||||
|
const uint32_t count = get_dot_count(obj);
|
||||||
|
if (count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const int32_t dot_width = lv_obj_get_style_width(get_dot(obj, 0), LV_PART_MAIN);
|
||||||
|
const int32_t gap = lv_obj_get_style_pad_column(obj, LV_PART_MAIN);
|
||||||
|
const int32_t dots_width = static_cast<int32_t>(count) * dot_width + static_cast<int32_t>(count - 1) * gap;
|
||||||
|
const bool show_numbers = dots_width > lv_obj_get_style_max_width(obj, LV_PART_MAIN);
|
||||||
|
|
||||||
|
lv_obj_t* label = get_label(obj);
|
||||||
|
lv_obj_set_flag(label, LV_OBJ_FLAG_HIDDEN, !show_numbers);
|
||||||
|
uint32_t page = 0;
|
||||||
|
for (uint32_t i = 0; i < count; i++) {
|
||||||
|
lv_obj_t* dot = get_dot(obj, i);
|
||||||
|
lv_obj_set_flag(dot, LV_OBJ_FLAG_HIDDEN, show_numbers);
|
||||||
|
if (lv_obj_has_state(dot, LV_STATE_CHECKED)) {
|
||||||
|
page = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (show_numbers) {
|
||||||
|
char text[24];
|
||||||
|
std::snprintf(text, sizeof(text), "%lu/%lu", static_cast<unsigned long>(page + 1), static_cast<unsigned long>(count));
|
||||||
|
lv_label_set_text(label, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
lv_obj_t* lvgl_page_indicator_create(lv_obj_t* parent) {
|
||||||
|
lv_obj_t* obj = lv_obj_class_create_obj(&lvgl_page_indicator_class, parent);
|
||||||
|
lv_obj_class_init_obj(obj);
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
void lvgl_page_indicator_set_page_count(lv_obj_t* obj, uint32_t count) {
|
||||||
|
uint32_t current = get_dot_count(obj);
|
||||||
|
while (current > count) {
|
||||||
|
lv_obj_delete(lv_obj_get_child(obj, -1));
|
||||||
|
current--;
|
||||||
|
}
|
||||||
|
while (current < count) {
|
||||||
|
lv_obj_t* dot = lv_obj_class_create_obj(&lvgl_page_indicator_dot_class, obj);
|
||||||
|
lv_obj_class_init_obj(dot);
|
||||||
|
current++;
|
||||||
|
}
|
||||||
|
refresh(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void lvgl_page_indicator_set_page(lv_obj_t* obj, uint32_t page) {
|
||||||
|
const uint32_t count = get_dot_count(obj);
|
||||||
|
for (uint32_t i = 0; i < count; i++) {
|
||||||
|
lv_obj_set_state(get_dot(obj, i), LV_STATE_CHECKED, i == page);
|
||||||
|
}
|
||||||
|
refresh(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -29,6 +29,8 @@
|
|||||||
@widget card: lvgl_card_class;
|
@widget card: lvgl_card_class;
|
||||||
@widget chip: lvgl_chip_class;
|
@widget chip: lvgl_chip_class;
|
||||||
@widget badge: lvgl_badge_class;
|
@widget badge: lvgl_badge_class;
|
||||||
|
@widget page_indicator: lvgl_page_indicator_class;
|
||||||
|
@widget page_indicator_dot: lvgl_page_indicator_dot_class;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 1. Base tokens
|
// 1. Base tokens
|
||||||
@ -199,6 +201,11 @@
|
|||||||
@chip-pad-hor: if($isCompact, 3, @space-4);
|
@chip-pad-hor: if($isCompact, 3, @space-4);
|
||||||
@chip-pad-ver: if($isCompact, 2, 6dp);
|
@chip-pad-ver: if($isCompact, 2, 6dp);
|
||||||
|
|
||||||
|
// Page indicator (lvgl_page_indicator_create): a dot per page, the current page's dot is filled
|
||||||
|
@page-indicator-color: @color-on-surface-variant;
|
||||||
|
@page-indicator-dot-size: if($isCompact, 4, 6dp);
|
||||||
|
@page-indicator-gap: if($isCompact, 3, 6dp);
|
||||||
|
|
||||||
// Badge (lvgl_badge_create): a small dot that marks an item, e.g. a favourite
|
// Badge (lvgl_badge_create): a small dot that marks an item, e.g. a favourite
|
||||||
@badge-color: @color-secondary;
|
@badge-color: @color-secondary;
|
||||||
@badge-size: if($isCompact, 4, 6dp);
|
@badge-size: if($isCompact, 4, 6dp);
|
||||||
@ -565,6 +572,31 @@ card {
|
|||||||
padGap: @card-gap;
|
padGap: @card-gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page_indicator {
|
||||||
|
bgOpa: transp;
|
||||||
|
borderWidth: 0;
|
||||||
|
padAll: 0;
|
||||||
|
padGap: @page-indicator-gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
page_indicator_label {
|
||||||
|
textColor: @page-indicator-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
page_indicator_dot {
|
||||||
|
width: @page-indicator-dot-size;
|
||||||
|
height: @page-indicator-dot-size;
|
||||||
|
radius: circle;
|
||||||
|
bgColor: @page-indicator-color;
|
||||||
|
bgOpa: transp;
|
||||||
|
borderColor: @page-indicator-color;
|
||||||
|
borderWidth: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
page_indicator_dot_current {
|
||||||
|
bgOpa: cover;
|
||||||
|
}
|
||||||
|
|
||||||
badge {
|
badge {
|
||||||
width: @badge-size;
|
width: @badge-size;
|
||||||
height: @badge-size;
|
height: @badge-size;
|
||||||
@ -1374,6 +1406,19 @@ apply(checkbox) {
|
|||||||
disabled: state.disabled;
|
disabled: state.disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply(page_indicator) {
|
||||||
|
page_indicator: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(page_indicator > label) {
|
||||||
|
page_indicator_label: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(page_indicator_dot) {
|
||||||
|
page_indicator_dot: default;
|
||||||
|
page_indicator_dot_current: state.checked;
|
||||||
|
}
|
||||||
|
|
||||||
apply(badge) {
|
apply(badge) {
|
||||||
badge: default;
|
badge: default;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,6 +30,8 @@
|
|||||||
@widget card: lvgl_card_class;
|
@widget card: lvgl_card_class;
|
||||||
@widget chip: lvgl_chip_class;
|
@widget chip: lvgl_chip_class;
|
||||||
@widget badge: lvgl_badge_class;
|
@widget badge: lvgl_badge_class;
|
||||||
|
@widget page_indicator: lvgl_page_indicator_class;
|
||||||
|
@widget page_indicator_dot: lvgl_page_indicator_dot_class;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// 1. Base tokens
|
// 1. Base tokens
|
||||||
@ -142,6 +144,11 @@
|
|||||||
@chip-pad-hor: if($isCompact, 3, @space-4);
|
@chip-pad-hor: if($isCompact, 3, @space-4);
|
||||||
@chip-pad-ver: if($isCompact, 2, @space-2);
|
@chip-pad-ver: if($isCompact, 2, @space-2);
|
||||||
|
|
||||||
|
// Page indicator (lvgl_page_indicator_create): a dot per page, the current page's dot is filled
|
||||||
|
@page-indicator-color: @color-on-surface;
|
||||||
|
@page-indicator-dot-size: if($isCompact, 4, 6dp);
|
||||||
|
@page-indicator-gap: if($isCompact, 3, 6dp);
|
||||||
|
|
||||||
// Badge (lvgl_badge_create): a small dot that marks an item, e.g. a favourite
|
// Badge (lvgl_badge_create): a small dot that marks an item, e.g. a favourite
|
||||||
@badge-color: @color-on-surface;
|
@badge-color: @color-on-surface;
|
||||||
@badge-size: if($isCompact, 4, 6dp);
|
@badge-size: if($isCompact, 4, 6dp);
|
||||||
@ -392,6 +399,31 @@ card {
|
|||||||
padGap: @card-gap;
|
padGap: @card-gap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page_indicator {
|
||||||
|
bgOpa: transp;
|
||||||
|
borderWidth: 0;
|
||||||
|
padAll: 0;
|
||||||
|
padGap: @page-indicator-gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
page_indicator_label {
|
||||||
|
textColor: @page-indicator-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
page_indicator_dot {
|
||||||
|
width: @page-indicator-dot-size;
|
||||||
|
height: @page-indicator-dot-size;
|
||||||
|
radius: circle;
|
||||||
|
bgColor: @page-indicator-color;
|
||||||
|
bgOpa: transp;
|
||||||
|
borderColor: @page-indicator-color;
|
||||||
|
borderWidth: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
page_indicator_dot_current {
|
||||||
|
bgOpa: cover;
|
||||||
|
}
|
||||||
|
|
||||||
badge {
|
badge {
|
||||||
width: @badge-size;
|
width: @badge-size;
|
||||||
height: @badge-size;
|
height: @badge-size;
|
||||||
@ -1037,6 +1069,19 @@ apply(checkbox) {
|
|||||||
disabled: part.indicator, state.disabled;
|
disabled: part.indicator, state.disabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
apply(page_indicator) {
|
||||||
|
page_indicator: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(page_indicator > label) {
|
||||||
|
page_indicator_label: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
apply(page_indicator_dot) {
|
||||||
|
page_indicator_dot: default;
|
||||||
|
page_indicator_dot_current: state.checked;
|
||||||
|
}
|
||||||
|
|
||||||
apply(badge) {
|
apply(badge) {
|
||||||
badge: default;
|
badge: default;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,18 +39,36 @@ public:
|
|||||||
|
|
||||||
explicit AppGrid(const Callbacks& callbacks) : callbacks(callbacks) {}
|
explicit AppGrid(const Callbacks& callbacks) : callbacks(callbacks) {}
|
||||||
|
|
||||||
/** Shows the tile icons in the theme's primary color. Call before creating the widgets. */
|
enum class IconColor {
|
||||||
void setPrimaryColorIcons(bool enabled) { primaryColorIcons = enabled; }
|
/** The theme's icon colour */
|
||||||
|
Default,
|
||||||
|
/** The theme's primary colour */
|
||||||
|
Primary,
|
||||||
|
/** The theme's secondary colour */
|
||||||
|
Secondary
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Sets the colour of the tile icons. Call before creating the widgets. */
|
||||||
|
void setIconColor(IconColor color) { iconColor = color; }
|
||||||
|
|
||||||
|
/** Swiping left or right over the grid goes to the next or previous page. Call before creating the widgets. */
|
||||||
|
void setSwipeNavigation(bool enabled) { swipeNavigation = enabled; }
|
||||||
|
|
||||||
/** Adds the paging buttons to the toolbar, creates the grid below it and populates it. */
|
|
||||||
void createWidgets(lv_obj_t* parent, lv_obj_t* toolbar);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the grid and populates it. The page number and the page buttons go in pageBar, which is hidden when there's only one page. The page buttons wrap around.
|
* Creates the grid and populates it, with a bar below it: buttons on the left (see addBarButton()),
|
||||||
* @param[in] parent the container for the grid
|
* a page indicator in the center and page buttons on the right. The page indicator and buttons are
|
||||||
* @param[in] pageBar an empty container
|
* hidden when there's only one page, and the page buttons wrap around.
|
||||||
|
* @param[in] parent the container for the grid and the bar
|
||||||
*/
|
*/
|
||||||
void createWidgetsWithPageBar(lv_obj_t* parent, lv_obj_t* pageBar);
|
void createWidgetsWithBottomBar(lv_obj_t* parent);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an icon button to the left side of the bottom bar. Call right after createWidgetsWithBottomBar().
|
||||||
|
* @param[in] icon a shared icon (see lvgl/icons/shared.h)
|
||||||
|
* @return the button
|
||||||
|
*/
|
||||||
|
lv_obj_t* addBarButton(const char* icon, lv_event_cb_t onClicked, void* userData);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rebuilds the grid asynchronously, so it is safe to call from a tile's own event.
|
* Rebuilds the grid asynchronously, so it is safe to call from a tile's own event.
|
||||||
@ -64,22 +82,29 @@ private:
|
|||||||
std::vector<AppGridItem> items;
|
std::vector<AppGridItem> items;
|
||||||
uint32_t page = 0;
|
uint32_t page = 0;
|
||||||
uint32_t pageCount = 1;
|
uint32_t pageCount = 1;
|
||||||
bool primaryColorIcons = false;
|
IconColor iconColor = IconColor::Default;
|
||||||
|
bool swipeNavigation = false;
|
||||||
bool keepSelectionOnRepopulate = true;
|
bool keepSelectionOnRepopulate = true;
|
||||||
lv_obj_t* grid = nullptr;
|
lv_obj_t* grid = nullptr;
|
||||||
lv_obj_t* prevButton = nullptr;
|
lv_obj_t* prevButton = nullptr;
|
||||||
lv_obj_t* nextButton = nullptr;
|
lv_obj_t* nextButton = nullptr;
|
||||||
lv_obj_t* pageBar = nullptr;
|
lv_obj_t* pageIndicator = nullptr;
|
||||||
lv_obj_t* pageLabel = nullptr;
|
lv_obj_t* pageButtons = nullptr;
|
||||||
|
lv_obj_t* barButtons = nullptr;
|
||||||
|
|
||||||
void createGrid(lv_obj_t* parent);
|
void createGrid(lv_obj_t* parent);
|
||||||
|
void goToPreviousPage();
|
||||||
|
void goToNextPage();
|
||||||
void populate();
|
void populate();
|
||||||
|
|
||||||
static void onDeferredRepopulate(void* userData);
|
static void onDeferredRepopulate(void* userData);
|
||||||
|
static void onDeferredNextPage(void* userData);
|
||||||
|
static void onDeferredPreviousPage(void* userData);
|
||||||
static void onGridDeleted(lv_event_t* e);
|
static void onGridDeleted(lv_event_t* e);
|
||||||
static void onGridSizeChanged(lv_event_t* e);
|
static void onGridSizeChanged(lv_event_t* e);
|
||||||
static void onPrevPressed(lv_event_t* e);
|
static void onPrevPressed(lv_event_t* e);
|
||||||
static void onNextPressed(lv_event_t* e);
|
static void onNextPressed(lv_event_t* e);
|
||||||
|
static void onGridGesture(lv_event_t* e);
|
||||||
static void onTileClicked(lv_event_t* e);
|
static void onTileClicked(lv_event_t* e);
|
||||||
static void onTileLongPressed(lv_event_t* e);
|
static void onTileLongPressed(lv_event_t* e);
|
||||||
static void onTileKey(lv_event_t* e);
|
static void onTileKey(lv_event_t* e);
|
||||||
|
|||||||
@ -3,9 +3,10 @@
|
|||||||
#include <lvgl/devices/indev.h>
|
#include <lvgl/devices/indev.h>
|
||||||
#include <lvgl/fonts.h>
|
#include <lvgl/fonts.h>
|
||||||
#include <lvgl/grid_navigation.h>
|
#include <lvgl/grid_navigation.h>
|
||||||
|
#include <lvgl/insets.h>
|
||||||
#include <lvgl/widgets/badge.h>
|
#include <lvgl/widgets/badge.h>
|
||||||
#include <lvgl/widgets/icon_button.h>
|
#include <lvgl/widgets/icon_button.h>
|
||||||
#include <lvgl/widgets/toolbar.h>
|
#include <lvgl/widgets/page_indicator.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -54,8 +55,67 @@ const AppGridItem* getTileItem(lv_event_t* e) {
|
|||||||
return static_cast<const AppGridItem*>(lv_obj_get_user_data(lv_event_get_target_obj(e)));
|
return static_cast<const AppGridItem*>(lv_obj_get_user_data(lv_event_get_target_obj(e)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lv_obj_t* createBarSection(lv_obj_t* parent, lv_flex_align_t align) {
|
||||||
|
auto* section = lv_obj_create(parent);
|
||||||
|
lv_obj_set_size(section, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
||||||
|
lv_obj_set_style_pad_all(section, 0, LV_STATE_DEFAULT);
|
||||||
|
lv_obj_set_style_border_width(section, 0, LV_STATE_DEFAULT);
|
||||||
|
lv_obj_set_flex_flow(section, LV_FLEX_FLOW_ROW);
|
||||||
|
lv_obj_set_flex_align(section, align, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
|
lv_obj_remove_flag(section, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
return section;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The width that a section's visible children need, including the gaps between them
|
||||||
|
int32_t getContentWidth(lv_obj_t* section) {
|
||||||
|
int32_t width = 0;
|
||||||
|
uint32_t visible = 0;
|
||||||
|
const uint32_t count = lv_obj_get_child_count(section);
|
||||||
|
for (uint32_t i = 0; i < count; i++) {
|
||||||
|
lv_obj_t* child = lv_obj_get_child(section, static_cast<int32_t>(i));
|
||||||
|
if (!lv_obj_has_flag(child, LV_OBJ_FLAG_HIDDEN)) {
|
||||||
|
width += lv_obj_get_width(child);
|
||||||
|
visible++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return visible > 1 ? width + static_cast<int32_t>(visible - 1) * lv_obj_get_style_pad_column(section, LV_PART_MAIN) : width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The page indicator gets the space between the side sections, which grow equally to keep it centered
|
||||||
|
void updatePageIndicatorWidth(void* data) {
|
||||||
|
auto* bottom_bar = static_cast<lv_obj_t*>(data);
|
||||||
|
auto* left_section = lv_obj_get_child(bottom_bar, 0);
|
||||||
|
auto* indicator_section = lv_obj_get_child(bottom_bar, 1);
|
||||||
|
auto* page_buttons = lv_obj_get_child(bottom_bar, 2);
|
||||||
|
auto* indicator = lv_obj_get_child(indicator_section, 0);
|
||||||
|
if (indicator == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const int32_t side_width = std::max(getContentWidth(left_section), getContentWidth(page_buttons));
|
||||||
|
const int32_t gaps = 2 * lv_obj_get_style_pad_column(bottom_bar, LV_PART_MAIN);
|
||||||
|
const int32_t available = lv_obj_get_content_width(bottom_bar) - 2 * side_width - gaps;
|
||||||
|
lv_obj_set_style_max_width(indicator, std::max<int32_t>(0, available), LV_STATE_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onBottomBarSizeChanged(lv_event_t* event) {
|
||||||
|
lv_async_call(updatePageIndicatorWidth, lv_event_get_target_obj(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
void onBottomBarDeleted(lv_event_t* event) {
|
||||||
|
lv_async_call_cancel(updatePageIndicatorWidth, lv_event_get_target_obj(event));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
void AppGrid::onDeferredNextPage(void* userData) {
|
||||||
|
static_cast<AppGrid*>(userData)->goToNextPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGrid::onDeferredPreviousPage(void* userData) {
|
||||||
|
static_cast<AppGrid*>(userData)->goToPreviousPage();
|
||||||
|
}
|
||||||
|
|
||||||
void AppGrid::onDeferredRepopulate(void* userData) {
|
void AppGrid::onDeferredRepopulate(void* userData) {
|
||||||
static_cast<AppGrid*>(userData)->populate();
|
static_cast<AppGrid*>(userData)->populate();
|
||||||
}
|
}
|
||||||
@ -63,6 +123,8 @@ void AppGrid::onDeferredRepopulate(void* userData) {
|
|||||||
void AppGrid::onGridDeleted(lv_event_t* e) {
|
void AppGrid::onGridDeleted(lv_event_t* e) {
|
||||||
// Cancels a still-pending repopulate scheduled right before the grid itself was deleted (e.g. the app closing)
|
// Cancels a still-pending repopulate scheduled right before the grid itself was deleted (e.g. the app closing)
|
||||||
lv_async_call_cancel(onDeferredRepopulate, lv_event_get_user_data(e));
|
lv_async_call_cancel(onDeferredRepopulate, lv_event_get_user_data(e));
|
||||||
|
lv_async_call_cancel(onDeferredNextPage, lv_event_get_user_data(e));
|
||||||
|
lv_async_call_cancel(onDeferredPreviousPage, lv_event_get_user_data(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deferred: page size depends on the grid size, which is final only after the layout pass that triggered this.
|
// Deferred: page size depends on the grid size, which is final only after the layout pass that triggered this.
|
||||||
@ -70,28 +132,42 @@ void AppGrid::onGridSizeChanged(lv_event_t* e) {
|
|||||||
lv_async_call(onDeferredRepopulate, lv_event_get_user_data(e));
|
lv_async_call(onDeferredRepopulate, lv_event_get_user_data(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppGrid::onPrevPressed(lv_event_t* e) {
|
// Changing the page doesn't keep the selected app selected: that would return to the selected app's page
|
||||||
auto* self = static_cast<AppGrid*>(lv_event_get_user_data(e));
|
void AppGrid::goToPreviousPage() {
|
||||||
if (self->page > 0) {
|
page = page > 0 ? page - 1 : pageCount - 1;
|
||||||
self->page--;
|
keepSelectionOnRepopulate = false;
|
||||||
} else if (self->pageBar != nullptr) {
|
populate();
|
||||||
self->page = self->pageCount - 1;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
self->populate();
|
|
||||||
|
void AppGrid::goToNextPage() {
|
||||||
|
page = page + 1 < pageCount ? page + 1 : 0;
|
||||||
|
keepSelectionOnRepopulate = false;
|
||||||
|
populate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGrid::onPrevPressed(lv_event_t* e) {
|
||||||
|
static_cast<AppGrid*>(lv_event_get_user_data(e))->goToPreviousPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppGrid::onNextPressed(lv_event_t* e) {
|
void AppGrid::onNextPressed(lv_event_t* e) {
|
||||||
|
static_cast<AppGrid*>(lv_event_get_user_data(e))->goToNextPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swipes that start on a tile reach the grid too, as tiles pass their gestures on to the grid
|
||||||
|
void AppGrid::onGridGesture(lv_event_t* e) {
|
||||||
auto* self = static_cast<AppGrid*>(lv_event_get_user_data(e));
|
auto* self = static_cast<AppGrid*>(lv_event_get_user_data(e));
|
||||||
if (self->page + 1 < self->pageCount) {
|
lv_indev_t* indev = lv_indev_active();
|
||||||
self->page++;
|
if (indev == nullptr || self->pageCount <= 1) {
|
||||||
} else if (self->pageBar != nullptr) {
|
|
||||||
self->page = 0;
|
|
||||||
} else {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self->populate();
|
const lv_dir_t direction = lv_indev_get_gesture_dir(indev);
|
||||||
|
if (direction != LV_DIR_LEFT && direction != LV_DIR_RIGHT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Keeps the tile where the swipe started from being clicked when the finger is lifted
|
||||||
|
lv_indev_wait_release(indev);
|
||||||
|
// Deferred: changing the page deletes the tiles, possibly including the one this event came from
|
||||||
|
lv_async_call(direction == LV_DIR_LEFT ? onDeferredNextPage : onDeferredPreviousPage, self);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppGrid::onTileClicked(lv_event_t* e) {
|
void AppGrid::onTileClicked(lv_event_t* e) {
|
||||||
@ -119,8 +195,9 @@ void AppGrid::populate() {
|
|||||||
// replacement tiles doesn't restore the focus, so the app id is remembered here and
|
// replacement tiles doesn't restore the focus, so the app id is remembered here and
|
||||||
// re-focused on its new tile once rebuilt.
|
// re-focused on its new tile once rebuilt.
|
||||||
std::string focusedAppId;
|
std::string focusedAppId;
|
||||||
|
const bool keepSelection = keepSelectionOnRepopulate;
|
||||||
lv_group_t* group = lv_group_get_default();
|
lv_group_t* group = lv_group_get_default();
|
||||||
if (group != nullptr && keepSelectionOnRepopulate) {
|
if (group != nullptr && keepSelection) {
|
||||||
// Grid navigation focuses the grid in the group, and a tile within the grid
|
// Grid navigation focuses the grid in the group, and a tile within the grid
|
||||||
lv_obj_t* focused = lv_group_get_focused(group) == grid ? lvgl_grid_navigation_get_focused(grid) : nullptr;
|
lv_obj_t* focused = lv_group_get_focused(group) == grid ? lvgl_grid_navigation_get_focused(grid) : nullptr;
|
||||||
if (focused != nullptr && lv_obj_get_parent(focused) == grid) {
|
if (focused != nullptr && lv_obj_get_parent(focused) == grid) {
|
||||||
@ -152,15 +229,10 @@ void AppGrid::populate() {
|
|||||||
page = std::min(page, page_count - 1);
|
page = std::min(page, page_count - 1);
|
||||||
pageCount = page_count;
|
pageCount = page_count;
|
||||||
|
|
||||||
if (pageBar != nullptr) {
|
lv_obj_set_flag(pageIndicator, LV_OBJ_FLAG_HIDDEN, page_count <= 1);
|
||||||
lv_obj_set_flag(pageBar, LV_OBJ_FLAG_HIDDEN, page_count <= 1);
|
lv_obj_set_flag(pageButtons, LV_OBJ_FLAG_HIDDEN, page_count <= 1);
|
||||||
lv_label_set_text_fmt(pageLabel, "%lu/%lu", static_cast<unsigned long>(page + 1), static_cast<unsigned long>(page_count));
|
lvgl_page_indicator_set_page_count(pageIndicator, page_count);
|
||||||
} else {
|
lvgl_page_indicator_set_page(pageIndicator, page);
|
||||||
lv_obj_set_state(prevButton, LV_STATE_DISABLED, page == 0);
|
|
||||||
lv_obj_set_state(nextButton, LV_STATE_DISABLED, page + 1 >= page_count);
|
|
||||||
lv_obj_set_flag(prevButton, LV_OBJ_FLAG_HIDDEN, page_count <= 1);
|
|
||||||
lv_obj_set_flag(nextButton, LV_OBJ_FLAG_HIDDEN, page_count <= 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_obj_t* focusedTile = nullptr;
|
lv_obj_t* focusedTile = nullptr;
|
||||||
const uint32_t first = page * layout.pageSize;
|
const uint32_t first = page * layout.pageSize;
|
||||||
@ -182,8 +254,10 @@ void AppGrid::populate() {
|
|||||||
lv_obj_set_style_text_align(icon, LV_TEXT_ALIGN_CENTER, LV_STATE_DEFAULT);
|
lv_obj_set_style_text_align(icon, LV_TEXT_ALIGN_CENTER, LV_STATE_DEFAULT);
|
||||||
lv_obj_set_size(icon, layout.iconSize, layout.iconSize);
|
lv_obj_set_size(icon, layout.iconSize, layout.iconSize);
|
||||||
lv_label_set_text(icon, item.icon);
|
lv_label_set_text(icon, item.icon);
|
||||||
if (primaryColorIcons) {
|
if (iconColor == IconColor::Primary) {
|
||||||
lv_obj_set_style_text_color(icon, lv_theme_get_color_primary(icon), LV_STATE_DEFAULT);
|
lv_obj_set_style_text_color(icon, lv_theme_get_color_primary(icon), LV_STATE_DEFAULT);
|
||||||
|
} else if (iconColor == IconColor::Secondary) {
|
||||||
|
lv_obj_set_style_text_color(icon, lv_theme_get_color_secondary(icon), LV_STATE_DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
lv_obj_t* label = lv_label_create(tile);
|
lv_obj_t* label = lv_label_create(tile);
|
||||||
@ -216,6 +290,10 @@ void AppGrid::populate() {
|
|||||||
if (focusedTile != nullptr) {
|
if (focusedTile != nullptr) {
|
||||||
lv_group_focus_obj(grid);
|
lv_group_focus_obj(grid);
|
||||||
lv_gridnav_set_focused(grid, focusedTile, LV_ANIM_OFF);
|
lv_gridnav_set_focused(grid, focusedTile, LV_ANIM_OFF);
|
||||||
|
} else if (!keepSelection && group != nullptr && lv_group_get_focused(group) == grid) {
|
||||||
|
// Grid navigation selects the first new tile when the grid is focused, but a page change shows no selection.
|
||||||
|
// The next key that moves the focus selects a tile again.
|
||||||
|
lvgl_focus_hide_key_selection(group);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,42 +310,38 @@ void AppGrid::createGrid(lv_obj_t* parent) {
|
|||||||
// The arrow keys move between the tiles in rows and columns
|
// The arrow keys move between the tiles in rows and columns
|
||||||
lvgl_grid_navigation_add(grid);
|
lvgl_grid_navigation_add(grid);
|
||||||
lv_obj_add_event_cb(grid, onGridDeleted, LV_EVENT_DELETE, this);
|
lv_obj_add_event_cb(grid, onGridDeleted, LV_EVENT_DELETE, this);
|
||||||
|
if (swipeNavigation) {
|
||||||
|
// LVGL sends a gesture to the first object, starting at the touched one, that doesn't pass it on to its parent
|
||||||
|
lv_obj_remove_flag(grid, LV_OBJ_FLAG_GESTURE_BUBBLE);
|
||||||
|
lv_obj_add_event_cb(grid, onGridGesture, LV_EVENT_GESTURE, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppGrid::createWidgets(lv_obj_t* parent, lv_obj_t* toolbar) {
|
void AppGrid::createWidgetsWithBottomBar(lv_obj_t* parent) {
|
||||||
prevButton = lvgl_toolbar_add_text_button_action(toolbar, "<", onPrevPressed, this);
|
// The side sections grow equally, so the page indicator stays centered
|
||||||
nextButton = lvgl_toolbar_add_text_button_action(toolbar, ">", onNextPressed, this);
|
auto* bottom_bar = createBarSection(parent, LV_FLEX_ALIGN_START);
|
||||||
|
lv_obj_set_width(bottom_bar, LV_PCT(100));
|
||||||
|
lvgl_obj_add_edge_padding(bottom_bar);
|
||||||
|
barButtons = createBarSection(bottom_bar, LV_FLEX_ALIGN_START);
|
||||||
|
lv_obj_set_flex_grow(barButtons, 1);
|
||||||
|
auto* indicator_section = createBarSection(bottom_bar, LV_FLEX_ALIGN_CENTER);
|
||||||
|
pageButtons = createBarSection(bottom_bar, LV_FLEX_ALIGN_END);
|
||||||
|
lv_obj_set_flex_grow(pageButtons, 1);
|
||||||
|
lv_obj_add_event_cb(bottom_bar, onBottomBarSizeChanged, LV_EVENT_SIZE_CHANGED, nullptr);
|
||||||
|
lv_obj_add_event_cb(bottom_bar, onBottomBarDeleted, LV_EVENT_DELETE, nullptr);
|
||||||
|
|
||||||
createGrid(parent);
|
createGrid(parent);
|
||||||
|
lv_obj_move_foreground(bottom_bar);
|
||||||
|
|
||||||
// Resolves the grid's flex_grow height, which the page size is derived from.
|
pageIndicator = lvgl_page_indicator_create(indicator_section);
|
||||||
lv_obj_update_layout(parent);
|
prevButton = lvgl_icon_button_create(pageButtons);
|
||||||
lv_obj_add_event_cb(grid, onGridSizeChanged, LV_EVENT_SIZE_CHANGED, this);
|
|
||||||
|
|
||||||
populate();
|
|
||||||
|
|
||||||
const bool next_visible = !lv_obj_has_flag(nextButton, LV_OBJ_FLAG_HIDDEN);
|
|
||||||
if (next_visible && !lvgl_indev_exists(LV_INDEV_TYPE_POINTER)) {
|
|
||||||
lv_group_focus_obj(nextButton);
|
|
||||||
lv_obj_add_state(nextButton, LV_STATE_FOCUS_KEY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppGrid::createWidgetsWithPageBar(lv_obj_t* parent, lv_obj_t* pageBar) {
|
|
||||||
createGrid(parent);
|
|
||||||
|
|
||||||
this->pageBar = pageBar;
|
|
||||||
lv_obj_set_flex_flow(pageBar, LV_FLEX_FLOW_ROW);
|
|
||||||
lv_obj_set_flex_align(pageBar, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
|
||||||
pageLabel = lv_label_create(pageBar);
|
|
||||||
prevButton = lvgl_icon_button_create(pageBar);
|
|
||||||
lv_label_set_text(lv_label_create(prevButton), "<");
|
lv_label_set_text(lv_label_create(prevButton), "<");
|
||||||
lv_obj_add_event_cb(prevButton, onPrevPressed, LV_EVENT_SHORT_CLICKED, this);
|
lv_obj_add_event_cb(prevButton, onPrevPressed, LV_EVENT_SHORT_CLICKED, this);
|
||||||
nextButton = lvgl_icon_button_create(pageBar);
|
nextButton = lvgl_icon_button_create(pageButtons);
|
||||||
lv_label_set_text(lv_label_create(nextButton), ">");
|
lv_label_set_text(lv_label_create(nextButton), ">");
|
||||||
lv_obj_add_event_cb(nextButton, onNextPressed, LV_EVENT_SHORT_CLICKED, this);
|
lv_obj_add_event_cb(nextButton, onNextPressed, LV_EVENT_SHORT_CLICKED, this);
|
||||||
// The arrow keys move between the page buttons
|
// The arrow keys move between the page buttons
|
||||||
lvgl_grid_navigation_add(pageBar);
|
lvgl_grid_navigation_add(pageButtons);
|
||||||
|
|
||||||
// Resolves the grid's flex_grow height, which the page size is derived from.
|
// Resolves the grid's flex_grow height, which the page size is derived from.
|
||||||
lv_obj_update_layout(parent);
|
lv_obj_update_layout(parent);
|
||||||
@ -281,4 +355,13 @@ void AppGrid::createWidgetsWithPageBar(lv_obj_t* parent, lv_obj_t* pageBar) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lv_obj_t* AppGrid::addBarButton(const char* icon, lv_event_cb_t onClicked, void* userData) {
|
||||||
|
auto* button = lvgl_icon_button_create(barButtons);
|
||||||
|
auto* label = lv_label_create(button);
|
||||||
|
lv_obj_set_style_text_font(label, lvgl_get_shared_icon_default_font(), LV_STATE_DEFAULT);
|
||||||
|
lv_label_set_text(label, icon);
|
||||||
|
lv_obj_add_event_cb(button, onClicked, LV_EVENT_SHORT_CLICKED, userData);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,11 +11,8 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
#include <lvgl/fonts.h>
|
|
||||||
#include <lvgl/icons/shared.h>
|
#include <lvgl/icons/shared.h>
|
||||||
#include <lvgl/insets.h>
|
|
||||||
#include <lvgl/theme.h>
|
#include <lvgl/theme.h>
|
||||||
#include <lvgl/widgets/icon_button.h>
|
|
||||||
|
|
||||||
#include <lvgl_window_manager/window_manager.h>
|
#include <lvgl_window_manager/window_manager.h>
|
||||||
|
|
||||||
@ -49,7 +46,6 @@ constexpr IconEntry ICONS[] = {
|
|||||||
{"tactility.i2cscanner", LVGL_ICON_SHARED_CABLE},
|
{"tactility.i2cscanner", LVGL_ICON_SHARED_CABLE},
|
||||||
{"tactility.notes", LVGL_ICON_SHARED_EDIT_NOTE},
|
{"tactility.notes", LVGL_ICON_SHARED_EDIT_NOTE},
|
||||||
{"tactility.screenshot", LVGL_ICON_SHARED_IMAGE},
|
{"tactility.screenshot", LVGL_ICON_SHARED_IMAGE},
|
||||||
{"tactility.settings", LVGL_ICON_SHARED_SETTINGS},
|
|
||||||
{"tactility.systeminfo", LVGL_ICON_SHARED_DEVICES},
|
{"tactility.systeminfo", LVGL_ICON_SHARED_DEVICES},
|
||||||
{"tactility.terminal", LVGL_ICON_SHARED_TERMINAL},
|
{"tactility.terminal", LVGL_ICON_SHARED_TERMINAL},
|
||||||
{"tactility.webserversettings", LVGL_ICON_SHARED_CLOUD},
|
{"tactility.webserversettings", LVGL_ICON_SHARED_CLOUD},
|
||||||
@ -58,7 +54,6 @@ constexpr IconEntry ICONS[] = {
|
|||||||
// Hidden apps that are still shown in the launcher
|
// Hidden apps that are still shown in the launcher
|
||||||
constexpr const char* SHOWN_HIDDEN_APP_IDS[] = {
|
constexpr const char* SHOWN_HIDDEN_APP_IDS[] = {
|
||||||
"tactility.files",
|
"tactility.files",
|
||||||
"tactility.settings",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const char* appIcon(const ::AppManifest* manifest) {
|
const char* appIcon(const ::AppManifest* manifest) {
|
||||||
@ -187,46 +182,21 @@ bool supportsPowerOff() {
|
|||||||
return supported;
|
return supported;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addShortcut(lv_obj_t* parent, const char* icon, const char* appId) {
|
|
||||||
auto* button = lvgl_icon_button_create(parent);
|
|
||||||
auto* label = lv_label_create(button);
|
|
||||||
lv_obj_set_style_text_font(label, lvgl_get_shared_icon_default_font(), LV_STATE_DEFAULT);
|
|
||||||
lv_label_set_text(label, icon);
|
|
||||||
lv_obj_add_event_cb(button, onShortcutPressed, LV_EVENT_SHORT_CLICKED, const_cast<char*>(appId));
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_obj_t* createBarSection(lv_obj_t* parent, lv_flex_align_t align) {
|
|
||||||
auto* section = lv_obj_create(parent);
|
|
||||||
lv_obj_set_size(section, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
|
||||||
lv_obj_set_style_pad_all(section, 0, LV_STATE_DEFAULT);
|
|
||||||
lv_obj_set_style_border_width(section, 0, LV_STATE_DEFAULT);
|
|
||||||
lv_obj_set_flex_flow(section, LV_FLEX_FLOW_ROW);
|
|
||||||
lv_obj_set_flex_align(section, align, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
|
||||||
lv_obj_remove_flag(section, LV_OBJ_FLAG_SCROLLABLE);
|
|
||||||
return section;
|
|
||||||
}
|
|
||||||
|
|
||||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||||
auto* ctx = static_cast<Context*>(userData);
|
auto* ctx = static_cast<Context*>(userData);
|
||||||
|
|
||||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||||
|
|
||||||
// Power off on the left, the page number and page buttons on the right
|
|
||||||
auto* bottom_bar = createBarSection(parent, LV_FLEX_ALIGN_START);
|
|
||||||
lv_obj_set_width(bottom_bar, LV_PCT(100));
|
|
||||||
lvgl_obj_add_edge_padding(bottom_bar);
|
|
||||||
auto* left_section = createBarSection(bottom_bar, LV_FLEX_ALIGN_START);
|
|
||||||
lv_obj_set_flex_grow(left_section, 1);
|
|
||||||
auto* page_bar = createBarSection(bottom_bar, LV_FLEX_ALIGN_END);
|
|
||||||
|
|
||||||
// The monochrome theme's primary color is black, also on a black background
|
// The monochrome theme's primary color is black, also on a black background
|
||||||
ctx->grid.setPrimaryColorIcons(!lvgl_theme_is_mono());
|
if (!lvgl_theme_is_mono()) {
|
||||||
ctx->grid.createWidgetsWithPageBar(parent, page_bar);
|
ctx->grid.setIconColor(AppGrid::IconColor::Primary);
|
||||||
lv_obj_move_foreground(bottom_bar);
|
}
|
||||||
|
ctx->grid.setSwipeNavigation(true);
|
||||||
|
ctx->grid.createWidgetsWithBottomBar(parent);
|
||||||
|
ctx->grid.addBarButton(LVGL_ICON_SHARED_SETTINGS, onShortcutPressed, const_cast<char*>("tactility.settings"));
|
||||||
if (isAppRegistered("tactility.poweroff") && supportsPowerOff()) {
|
if (isAppRegistered("tactility.poweroff") && supportsPowerOff()) {
|
||||||
addShortcut(left_section, LVGL_ICON_SHARED_POWER_SETTINGS_NEW, "tactility.poweroff");
|
ctx->grid.addBarButton(LVGL_ICON_SHARED_POWER_SETTINGS_NEW, onShortcutPressed, const_cast<char*>("tactility.poweroff"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,7 @@
|
|||||||
#include <Tactility/app/AppGrid.h>
|
#include <Tactility/app/AppGrid.h>
|
||||||
|
|
||||||
#include <lvgl/icons/shared.h>
|
#include <lvgl/icons/shared.h>
|
||||||
#include <lvgl/widgets/toolbar.h>
|
#include <lvgl/theme.h>
|
||||||
#include <tactility/check.h>
|
#include <tactility/check.h>
|
||||||
|
|
||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
@ -109,9 +109,12 @@ void createWidgets(lv_obj_t* parent, void* userData) {
|
|||||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||||
|
|
||||||
auto* toolbar = lvgl_toolbar_create(parent, "Settings");
|
if (!lvgl_theme_is_mono()) {
|
||||||
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx);
|
ctx->grid.setIconColor(AppGrid::IconColor::Secondary);
|
||||||
ctx->grid.createWidgets(parent, toolbar);
|
}
|
||||||
|
ctx->grid.setSwipeNavigation(true);
|
||||||
|
ctx->grid.createWidgetsWithBottomBar(parent);
|
||||||
|
ctx->grid.addBarButton(LVGL_ICON_SHARED_CLOSE, onBackPressed, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t appMain(int argc, char* argv[]) {
|
int32_t appMain(int argc, char* argv[]) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user