mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 02:45:05 +00:00
UX improvements: AppList, Settings, non-touch devices (#671)
- Implement double size icon assets in `lvgl-module` - `AppList` and `Settings` app now show a paginated grid of icons - T-Lora Pager gets a larger font to improve readability (14 -> 16 pixels) - Improve default widget selection in several apps (`PowerOff`, `AlertDialog`, `AppList`, `Settings`, `Boot`) - USB HID LVGL device is now only created when necessary. And the code is called in a safer way. - Renamed “Region & Language” to “Locale.” - The terminal toolbar is shown only when pointer input is available.
This commit is contained in:
parent
34ca63ec3c
commit
2b6b4b5167
@ -22,3 +22,4 @@ display.dpi=227
|
|||||||
lvgl.colorDepth=16
|
lvgl.colorDepth=16
|
||||||
lvgl.dpi=150
|
lvgl.dpi=150
|
||||||
lvgl.uiDensity=compact
|
lvgl.uiDensity=compact
|
||||||
|
lvgl.fontSize=16
|
||||||
|
|||||||
@ -14,6 +14,7 @@ if (DEFINED ENV{ESP_IDF_VERSION})
|
|||||||
GET_PROPERTY_VALUE(sdkconfig_text "CONFIG_TT_LVGL_STATUSBAR_ICON_SIZE" statusbar_symbol_size)
|
GET_PROPERTY_VALUE(sdkconfig_text "CONFIG_TT_LVGL_STATUSBAR_ICON_SIZE" statusbar_symbol_size)
|
||||||
GET_PROPERTY_VALUE(sdkconfig_text "CONFIG_TT_LVGL_LAUNCHER_ICON_SIZE" launcher_symbol_size)
|
GET_PROPERTY_VALUE(sdkconfig_text "CONFIG_TT_LVGL_LAUNCHER_ICON_SIZE" launcher_symbol_size)
|
||||||
GET_PROPERTY_VALUE(sdkconfig_text "CONFIG_TT_LVGL_SHARED_ICON_SIZE" shared_symbol_size)
|
GET_PROPERTY_VALUE(sdkconfig_text "CONFIG_TT_LVGL_SHARED_ICON_SIZE" shared_symbol_size)
|
||||||
|
math(EXPR shared_symbol_2x_size "${shared_symbol_size} * 2")
|
||||||
else ()
|
else ()
|
||||||
# Default values for simulator
|
# Default values for simulator
|
||||||
set(font_size_small 10)
|
set(font_size_small 10)
|
||||||
@ -22,6 +23,7 @@ else ()
|
|||||||
set(statusbar_symbol_size 20)
|
set(statusbar_symbol_size 20)
|
||||||
set(launcher_symbol_size 36)
|
set(launcher_symbol_size 36)
|
||||||
set(shared_symbol_size 16)
|
set(shared_symbol_size 16)
|
||||||
|
set(shared_symbol_2x_size 32)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
message("Font sizes:")
|
message("Font sizes:")
|
||||||
@ -32,6 +34,7 @@ message("Icon sizes:")
|
|||||||
message(" - statusbar: ${statusbar_symbol_size}")
|
message(" - statusbar: ${statusbar_symbol_size}")
|
||||||
message(" - launcher: ${launcher_symbol_size}")
|
message(" - launcher: ${launcher_symbol_size}")
|
||||||
message(" - shared: ${shared_symbol_size}")
|
message(" - shared: ${shared_symbol_size}")
|
||||||
|
message(" - shared_2x: ${shared_symbol_2x_size}")
|
||||||
|
|
||||||
# ####################################
|
# ####################################
|
||||||
# Read device properties
|
# Read device properties
|
||||||
@ -71,6 +74,7 @@ file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
|
|||||||
list(APPEND SOURCE_FILES "source-fonts/material_symbols_statusbar_${statusbar_symbol_size}.c")
|
list(APPEND SOURCE_FILES "source-fonts/material_symbols_statusbar_${statusbar_symbol_size}.c")
|
||||||
list(APPEND SOURCE_FILES "source-fonts/material_symbols_launcher_${launcher_symbol_size}.c")
|
list(APPEND SOURCE_FILES "source-fonts/material_symbols_launcher_${launcher_symbol_size}.c")
|
||||||
list(APPEND SOURCE_FILES "source-fonts/material_symbols_shared_${shared_symbol_size}.c")
|
list(APPEND SOURCE_FILES "source-fonts/material_symbols_shared_${shared_symbol_size}.c")
|
||||||
|
list(APPEND SOURCE_FILES "source-fonts/material_symbols_shared_${shared_symbol_2x_size}.c")
|
||||||
|
|
||||||
list(APPEND REQUIRES_LIST
|
list(APPEND REQUIRES_LIST
|
||||||
TactilityKernel
|
TactilityKernel
|
||||||
@ -119,6 +123,8 @@ target_compile_definitions(${MODULE_NAME} PUBLIC
|
|||||||
"-DTT_LVGL_LAUNCHER_FONT_ICON_SYMBOL=material_symbols_launcher_${launcher_symbol_size}"
|
"-DTT_LVGL_LAUNCHER_FONT_ICON_SYMBOL=material_symbols_launcher_${launcher_symbol_size}"
|
||||||
"-DTT_LVGL_SHARED_FONT_ICON_SIZE=${shared_symbol_size}"
|
"-DTT_LVGL_SHARED_FONT_ICON_SIZE=${shared_symbol_size}"
|
||||||
"-DTT_LVGL_SHARED_FONT_ICON_SYMBOL=material_symbols_shared_${shared_symbol_size}"
|
"-DTT_LVGL_SHARED_FONT_ICON_SYMBOL=material_symbols_shared_${shared_symbol_size}"
|
||||||
|
"-DTT_LVGL_SHARED_FONT_ICON_2X_SIZE=${shared_symbol_2x_size}"
|
||||||
|
"-DTT_LVGL_SHARED_FONT_ICON_2X_SYMBOL=material_symbols_shared_${shared_symbol_2x_size}"
|
||||||
# UiDensity
|
# UiDensity
|
||||||
"-DTT_LVGL_UI_DENSITY=${ui_density_variable}"
|
"-DTT_LVGL_UI_DENSITY=${ui_density_variable}"
|
||||||
)
|
)
|
||||||
|
|||||||
10
Modules/lvgl-module/assets/generate-all.py → Modules/lvgl-module/generate-icons.py
Normal file → Executable file
10
Modules/lvgl-module/assets/generate-all.py → Modules/lvgl-module/generate-icons.py
Normal file → Executable file
@ -1,3 +1,4 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
import os
|
import os
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
@ -10,7 +11,7 @@ def download_file(url: str, filename: str):
|
|||||||
|
|
||||||
def generate(bpp, size, font_file: str, symbols: list, output: str):
|
def generate(bpp, size, font_file: str, symbols: list, output: str):
|
||||||
output_file_name = f"{output}_{size}.c"
|
output_file_name = f"{output}_{size}.c"
|
||||||
output_path = os.path.join("..", "source-fonts", output_file_name)
|
output_path = os.path.join("source-fonts", output_file_name)
|
||||||
print(f"Generating {output_file_name}")
|
print(f"Generating {output_file_name}")
|
||||||
cmd = "lv_font_conv --no-compress --no-prefilter --bpp {} --size {} --font {} -r {} --format lvgl -o {} --force-fast-kern-format".format(bpp, size, font_file, ",".join(symbols), output_path)
|
cmd = "lv_font_conv --no-compress --no-prefilter --bpp {} --size {} --font {} -r {} --format lvgl -o {} --force-fast-kern-format".format(bpp, size, font_file, ",".join(symbols), output_path)
|
||||||
ret = os.system(cmd)
|
ret = os.system(cmd)
|
||||||
@ -46,7 +47,7 @@ def generate_icon_fonts(font_file, font_sizes, symbols, output):
|
|||||||
def generate_icon_names(codepoint_map: dict, codepoint_names: list, variable_name: str):
|
def generate_icon_names(codepoint_map: dict, codepoint_names: list, variable_name: str):
|
||||||
filename = f"{variable_name}.h"
|
filename = f"{variable_name}.h"
|
||||||
print(f"Generating {filename}")
|
print(f"Generating {filename}")
|
||||||
output_path = os.path.join("..", "include", "lvgl", "icons", filename)
|
output_path = os.path.join("include", "lvgl", "icons", filename)
|
||||||
with open(output_path, 'w') as f:
|
with open(output_path, 'w') as f:
|
||||||
f.write("// SPDX-License-Identifier: Apache-2.0\n\n")
|
f.write("// SPDX-License-Identifier: Apache-2.0\n\n")
|
||||||
f.write("#pragma once\n\n")
|
f.write("#pragma once\n\n")
|
||||||
@ -165,7 +166,10 @@ shared_symbol_sizes = [
|
|||||||
16, # T-Deck reference, with montserrat 14 in lv_list as icon with text
|
16, # T-Deck reference, with montserrat 14 in lv_list as icon with text
|
||||||
20,
|
20,
|
||||||
24,
|
24,
|
||||||
32
|
32,
|
||||||
|
40,
|
||||||
|
48,
|
||||||
|
64
|
||||||
]
|
]
|
||||||
|
|
||||||
statusbar_symbol_sizes = [
|
statusbar_symbol_sizes = [
|
||||||
22
Modules/lvgl-module/include/lvgl/devices/indev.h
Normal file
22
Modules/lvgl-module/include/lvgl/devices/indev.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks whether any registered LVGL input device is of the given type.
|
||||||
|
* @warning Caller must hold the LVGL lock.
|
||||||
|
* @param[in] type the input device type to look for
|
||||||
|
* @return true if at least one input device of the given type exists
|
||||||
|
*/
|
||||||
|
bool lvgl_indev_exists(lv_indev_type_t type);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -17,6 +17,9 @@ enum LvglFontSize {
|
|||||||
const lv_font_t* lvgl_get_shared_icon_font(void);
|
const lv_font_t* lvgl_get_shared_icon_font(void);
|
||||||
uint32_t lvgl_get_shared_icon_font_height(void);
|
uint32_t lvgl_get_shared_icon_font_height(void);
|
||||||
|
|
||||||
|
const lv_font_t* lvgl_get_shared_icon_font_2x(void);
|
||||||
|
uint32_t lvgl_get_shared_icon_font_2x_height(void);
|
||||||
|
|
||||||
const lv_font_t* lvgl_get_text_font(enum LvglFontSize font_size);
|
const lv_font_t* lvgl_get_text_font(enum LvglFontSize font_size);
|
||||||
uint32_t lvgl_get_text_font_height(enum LvglFontSize font_size);
|
uint32_t lvgl_get_text_font_height(enum LvglFontSize font_size);
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <lvgl.h>
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool lvgl_has_indev_of_type(lv_indev_type_t type);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Size: 30 px
|
* Size: 30 px
|
||||||
* Bpp: 2
|
* Bpp: 2
|
||||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 30 --font MaterialSymbolsRounded.ttf -r 0xE5C3,0xE2C7,0xE8B8 --format lvgl -o ../source-fonts/material_symbols_launcher_30.c --force-fast-kern-format
|
* Opts: --no-compress --no-prefilter --bpp 2 --size 30 --font MaterialSymbolsRounded.ttf -r 0xE5C3,0xE2C7,0xE8B8 --format lvgl -o source-fonts/material_symbols_launcher_30.c --force-fast-kern-format
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Size: 36 px
|
* Size: 36 px
|
||||||
* Bpp: 2
|
* Bpp: 2
|
||||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 36 --font MaterialSymbolsRounded.ttf -r 0xE5C3,0xE2C7,0xE8B8 --format lvgl -o ../source-fonts/material_symbols_launcher_36.c --force-fast-kern-format
|
* Opts: --no-compress --no-prefilter --bpp 2 --size 36 --font MaterialSymbolsRounded.ttf -r 0xE5C3,0xE2C7,0xE8B8 --format lvgl -o source-fonts/material_symbols_launcher_36.c --force-fast-kern-format
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Size: 42 px
|
* Size: 42 px
|
||||||
* Bpp: 2
|
* Bpp: 2
|
||||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 42 --font MaterialSymbolsRounded.ttf -r 0xE5C3,0xE2C7,0xE8B8 --format lvgl -o ../source-fonts/material_symbols_launcher_42.c --force-fast-kern-format
|
* Opts: --no-compress --no-prefilter --bpp 2 --size 42 --font MaterialSymbolsRounded.ttf -r 0xE5C3,0xE2C7,0xE8B8 --format lvgl -o source-fonts/material_symbols_launcher_42.c --force-fast-kern-format
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Size: 48 px
|
* Size: 48 px
|
||||||
* Bpp: 2
|
* Bpp: 2
|
||||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 48 --font MaterialSymbolsRounded.ttf -r 0xE5C3,0xE2C7,0xE8B8 --format lvgl -o ../source-fonts/material_symbols_launcher_48.c --force-fast-kern-format
|
* Opts: --no-compress --no-prefilter --bpp 2 --size 48 --font MaterialSymbolsRounded.ttf -r 0xE5C3,0xE2C7,0xE8B8 --format lvgl -o source-fonts/material_symbols_launcher_48.c --force-fast-kern-format
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Size: 64 px
|
* Size: 64 px
|
||||||
* Bpp: 2
|
* Bpp: 2
|
||||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 64 --font MaterialSymbolsRounded.ttf -r 0xE5C3,0xE2C7,0xE8B8 --format lvgl -o ../source-fonts/material_symbols_launcher_64.c --force-fast-kern-format
|
* Opts: --no-compress --no-prefilter --bpp 2 --size 64 --font MaterialSymbolsRounded.ttf -r 0xE5C3,0xE2C7,0xE8B8 --format lvgl -o source-fonts/material_symbols_launcher_64.c --force-fast-kern-format
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Size: 72 px
|
* Size: 72 px
|
||||||
* Bpp: 2
|
* Bpp: 2
|
||||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 72 --font MaterialSymbolsRounded.ttf -r 0xE5C3,0xE2C7,0xE8B8 --format lvgl -o ../source-fonts/material_symbols_launcher_72.c --force-fast-kern-format
|
* Opts: --no-compress --no-prefilter --bpp 2 --size 72 --font MaterialSymbolsRounded.ttf -r 0xE5C3,0xE2C7,0xE8B8 --format lvgl -o source-fonts/material_symbols_launcher_72.c --force-fast-kern-format
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Size: 12 px
|
* Size: 12 px
|
||||||
* Bpp: 2
|
* Bpp: 2
|
||||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 12 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o ../source-fonts/material_symbols_shared_12.c --force-fast-kern-format
|
* Opts: --no-compress --no-prefilter --bpp 2 --size 12 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o source-fonts/material_symbols_shared_12.c --force-fast-kern-format
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Size: 16 px
|
* Size: 16 px
|
||||||
* Bpp: 2
|
* Bpp: 2
|
||||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 16 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o ../source-fonts/material_symbols_shared_16.c --force-fast-kern-format
|
* Opts: --no-compress --no-prefilter --bpp 2 --size 16 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o source-fonts/material_symbols_shared_16.c --force-fast-kern-format
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Size: 20 px
|
* Size: 20 px
|
||||||
* Bpp: 2
|
* Bpp: 2
|
||||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 20 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o ../source-fonts/material_symbols_shared_20.c --force-fast-kern-format
|
* Opts: --no-compress --no-prefilter --bpp 2 --size 20 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o source-fonts/material_symbols_shared_20.c --force-fast-kern-format
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Size: 24 px
|
* Size: 24 px
|
||||||
* Bpp: 2
|
* Bpp: 2
|
||||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 24 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o ../source-fonts/material_symbols_shared_24.c --force-fast-kern-format
|
* Opts: --no-compress --no-prefilter --bpp 2 --size 24 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o source-fonts/material_symbols_shared_24.c --force-fast-kern-format
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Size: 32 px
|
* Size: 32 px
|
||||||
* Bpp: 2
|
* Bpp: 2
|
||||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 32 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o ../source-fonts/material_symbols_shared_32.c --force-fast-kern-format
|
* Opts: --no-compress --no-prefilter --bpp 2 --size 32 --font MaterialSymbolsRounded.ttf -r 0xE145,0xE5C3,0xE770,0xEF40,0xEBCC,0xEFE6,0xEF4A,0xE5CD,0xF15C,0xE668,0xE92E,0xE326,0xEB97,0xE745,0xEC1C,0xE2C7,0xF720,0xF090,0xE8AF,0xE30F,0xE8FD,0xE9F4,0xE3F4,0xE316,0xE90F,0xEA07,0xE9B9,0xE159,0xE5D2,0xE28D,0xE5D4,0xE405,0xE89C,0xF8C7,0xE5D5,0xEF7A,0xE8B8,0xE9F7,0xE55D,0xF028,0xE1E0,0xE63E,0xE1A7,0xEB8E,0xEAD6,0xE3AF,0xFFF75 --format lvgl -o source-fonts/material_symbols_shared_32.c --force-fast-kern-format
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||||
|
|||||||
1632
Modules/lvgl-module/source-fonts/material_symbols_shared_40.c
Normal file
1632
Modules/lvgl-module/source-fonts/material_symbols_shared_40.c
Normal file
File diff suppressed because it is too large
Load Diff
2130
Modules/lvgl-module/source-fonts/material_symbols_shared_48.c
Normal file
2130
Modules/lvgl-module/source-fonts/material_symbols_shared_48.c
Normal file
File diff suppressed because it is too large
Load Diff
3653
Modules/lvgl-module/source-fonts/material_symbols_shared_64.c
Normal file
3653
Modules/lvgl-module/source-fonts/material_symbols_shared_64.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Size: 12 px
|
* Size: 12 px
|
||||||
* Bpp: 2
|
* Bpp: 2
|
||||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 12 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9,0xE1E0 --format lvgl -o ../source-fonts/material_symbols_statusbar_12.c --force-fast-kern-format
|
* Opts: --no-compress --no-prefilter --bpp 2 --size 12 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9,0xE1E0 --format lvgl -o source-fonts/material_symbols_statusbar_12.c --force-fast-kern-format
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Size: 16 px
|
* Size: 16 px
|
||||||
* Bpp: 2
|
* Bpp: 2
|
||||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 16 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9,0xE1E0 --format lvgl -o ../source-fonts/material_symbols_statusbar_16.c --force-fast-kern-format
|
* Opts: --no-compress --no-prefilter --bpp 2 --size 16 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9,0xE1E0 --format lvgl -o source-fonts/material_symbols_statusbar_16.c --force-fast-kern-format
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Size: 20 px
|
* Size: 20 px
|
||||||
* Bpp: 2
|
* Bpp: 2
|
||||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 20 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9,0xE1E0 --format lvgl -o ../source-fonts/material_symbols_statusbar_20.c --force-fast-kern-format
|
* Opts: --no-compress --no-prefilter --bpp 2 --size 20 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9,0xE1E0 --format lvgl -o source-fonts/material_symbols_statusbar_20.c --force-fast-kern-format
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/*******************************************************************************
|
/*******************************************************************************
|
||||||
* Size: 30 px
|
* Size: 30 px
|
||||||
* Bpp: 2
|
* Bpp: 2
|
||||||
* Opts: --no-compress --no-prefilter --bpp 2 --size 30 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9,0xE1E0 --format lvgl -o ../source-fonts/material_symbols_statusbar_30.c --force-fast-kern-format
|
* Opts: --no-compress --no-prefilter --bpp 2 --size 30 --font MaterialSymbolsRounded.ttf -r 0xF1DB,0xF15C,0xE322,0xE623,0xF057,0xF0B0,0xEBE4,0xEBD6,0xEBE1,0xF065,0xE1DA,0xF064,0xF257,0xF256,0xF255,0xF254,0xF253,0xF252,0xF24F,0xF250,0xE1A7,0xE60F,0xE1A8,0xE1A9,0xE1E0 --format lvgl -o source-fonts/material_symbols_statusbar_30.c --force-fast-kern-format
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
#include <lvgl/devices/indev_private.h>
|
#include <lvgl/devices/indev.h>
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
bool lvgl_has_indev_of_type(lv_indev_type_t type) {
|
bool lvgl_indev_exists(lv_indev_type_t type) {
|
||||||
for (lv_indev_t* indev = lv_indev_get_next(nullptr); indev != nullptr; indev = lv_indev_get_next(indev)) {
|
for (lv_indev_t* indev = lv_indev_get_next(nullptr); indev != nullptr; indev = lv_indev_get_next(indev)) {
|
||||||
lv_indev_type_t to_check = lv_indev_get_type(indev);
|
if (lv_indev_get_type(indev) == type) {
|
||||||
if (to_check == type) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ extern const lv_font_t TT_LVGL_TEXT_FONT_LARGE_SYMBOL;
|
|||||||
extern const lv_font_t TT_LVGL_LAUNCHER_FONT_ICON_SYMBOL;
|
extern const lv_font_t TT_LVGL_LAUNCHER_FONT_ICON_SYMBOL;
|
||||||
extern const lv_font_t TT_LVGL_STATUSBAR_FONT_ICON_SYMBOL;
|
extern const lv_font_t TT_LVGL_STATUSBAR_FONT_ICON_SYMBOL;
|
||||||
extern const lv_font_t TT_LVGL_SHARED_FONT_ICON_SYMBOL;
|
extern const lv_font_t TT_LVGL_SHARED_FONT_ICON_SYMBOL;
|
||||||
|
extern const lv_font_t TT_LVGL_SHARED_FONT_ICON_2X_SYMBOL;
|
||||||
|
|
||||||
uint32_t lvgl_get_text_font_height(enum LvglFontSize font_size) {
|
uint32_t lvgl_get_text_font_height(enum LvglFontSize font_size) {
|
||||||
switch (font_size) {
|
switch (font_size) {
|
||||||
@ -34,6 +35,10 @@ uint32_t lvgl_get_shared_icon_font_height() { return TT_LVGL_SHARED_FONT_ICON_SI
|
|||||||
|
|
||||||
const lv_font_t* lvgl_get_shared_icon_font() { return &TT_LVGL_SHARED_FONT_ICON_SYMBOL; }
|
const lv_font_t* lvgl_get_shared_icon_font() { return &TT_LVGL_SHARED_FONT_ICON_SYMBOL; }
|
||||||
|
|
||||||
|
uint32_t lvgl_get_shared_icon_font_2x_height() { return TT_LVGL_SHARED_FONT_ICON_SIZE * 2; }
|
||||||
|
|
||||||
|
const lv_font_t* lvgl_get_shared_icon_font_2x() { return &TT_LVGL_SHARED_FONT_ICON_2X_SYMBOL; }
|
||||||
|
|
||||||
uint32_t lvgl_get_launcher_icon_font_height() { return TT_LVGL_LAUNCHER_FONT_ICON_SIZE; }
|
uint32_t lvgl_get_launcher_icon_font_height() { return TT_LVGL_LAUNCHER_FONT_ICON_SIZE; }
|
||||||
|
|
||||||
const lv_font_t* lvgl_get_launcher_icon_font() { return &TT_LVGL_LAUNCHER_FONT_ICON_SYMBOL; }
|
const lv_font_t* lvgl_get_launcher_icon_font() { return &TT_LVGL_LAUNCHER_FONT_ICON_SYMBOL; }
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
#include <lvgl/module.h>
|
#include <lvgl/module.h>
|
||||||
|
|
||||||
#include <lvgl/devices/display.h>
|
#include <lvgl/devices/display.h>
|
||||||
|
#include <lvgl/devices/indev.h>
|
||||||
#include <lvgl/devices/keyboard.h>
|
#include <lvgl/devices/keyboard.h>
|
||||||
#include <lvgl/devices/pointer.h>
|
#include <lvgl/devices/pointer.h>
|
||||||
|
|
||||||
@ -37,6 +38,8 @@ const struct ModuleSymbol lvgl_module_symbols[] = {
|
|||||||
// lvgl_display
|
// lvgl_display
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_display_add),
|
DEFINE_MODULE_SYMBOL(lvgl_display_add),
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_display_remove),
|
DEFINE_MODULE_SYMBOL(lvgl_display_remove),
|
||||||
|
// lvgl_indev
|
||||||
|
DEFINE_MODULE_SYMBOL(lvgl_indev_exists),
|
||||||
// lvgl_keyboard
|
// lvgl_keyboard
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_keyboard_add),
|
DEFINE_MODULE_SYMBOL(lvgl_keyboard_add),
|
||||||
DEFINE_MODULE_SYMBOL(lvgl_keyboard_remove),
|
DEFINE_MODULE_SYMBOL(lvgl_keyboard_remove),
|
||||||
|
|||||||
@ -402,6 +402,7 @@ static bool api_hid_subscribe(struct Device* device, UsbHidQueueHandle event_que
|
|||||||
if (!ctx || !event_queue) return false;
|
if (!ctx || !event_queue) return false;
|
||||||
if (xSemaphoreTake(ctx->sub_mutex, pdMS_TO_TICKS(100)) != pdTRUE) return false;
|
if (xSemaphoreTake(ctx->sub_mutex, pdMS_TO_TICKS(100)) != pdTRUE) return false;
|
||||||
bool added = false;
|
bool added = false;
|
||||||
|
bool is_new = false;
|
||||||
for (int i = 0; i < MAX_SUBSCRIBERS; i++) {
|
for (int i = 0; i < MAX_SUBSCRIBERS; i++) {
|
||||||
if (ctx->subscribers[i] == static_cast<QueueHandle_t>(event_queue)) {
|
if (ctx->subscribers[i] == static_cast<QueueHandle_t>(event_queue)) {
|
||||||
added = true;
|
added = true;
|
||||||
@ -410,9 +411,22 @@ static bool api_hid_subscribe(struct Device* device, UsbHidQueueHandle event_que
|
|||||||
if (!ctx->subscribers[i]) {
|
if (!ctx->subscribers[i]) {
|
||||||
ctx->subscribers[i] = static_cast<QueueHandle_t>(event_queue);
|
ctx->subscribers[i] = static_cast<QueueHandle_t>(event_queue);
|
||||||
added = true;
|
added = true;
|
||||||
|
is_new = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Reports devices that connected before this subscription. Done under sub_mutex so it is ordered with publish_event().
|
||||||
|
if (is_new) {
|
||||||
|
auto queue = static_cast<QueueHandle_t>(event_queue);
|
||||||
|
if (ctx->kb_handle.load() != nullptr) {
|
||||||
|
UsbHidEvent evt = { .type = USB_HID_EVENT_KEYBOARD_CONNECTED };
|
||||||
|
xQueueSend(queue, &evt, 0);
|
||||||
|
}
|
||||||
|
if (ctx->mouse_connected) {
|
||||||
|
UsbHidEvent evt = { .type = USB_HID_EVENT_MOUSE_CONNECTED };
|
||||||
|
xQueueSend(queue, &evt, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
xSemaphoreGive(ctx->sub_mutex);
|
xSemaphoreGive(ctx->sub_mutex);
|
||||||
if (!added) LOG_W(TAG, "subscriber list full");
|
if (!added) LOG_W(TAG, "subscriber list full");
|
||||||
return added;
|
return added;
|
||||||
|
|||||||
@ -3,7 +3,9 @@
|
|||||||
namespace tt::lvgl {
|
namespace tt::lvgl {
|
||||||
|
|
||||||
#ifdef ESP_PLATFORM
|
#ifdef ESP_PLATFORM
|
||||||
|
/** Creates the USB keyboard and mouse indevs. Must be called from LvglModuleConfig.on_start. */
|
||||||
void startUsbHidInput();
|
void startUsbHidInput();
|
||||||
|
/** Deletes the indevs created by startUsbHidInput(). Must be called from LvglModuleConfig.on_stop, before LVGL detaches its devices. */
|
||||||
void stopUsbHidInput();
|
void stopUsbHidInput();
|
||||||
#else
|
#else
|
||||||
inline void startUsbHidInput() {}
|
inline void startUsbHidInput() {}
|
||||||
|
|||||||
74
Tactility/Private/Tactility/app/AppGrid.h
Normal file
74
Tactility/Private/Tactility/app/AppGrid.h
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <app/manifest.h>
|
||||||
|
|
||||||
|
#include <lvgl.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace tt::app {
|
||||||
|
|
||||||
|
struct AppGridItem {
|
||||||
|
/** Owned copy, so a tile never references the app ledger directly */
|
||||||
|
::AppManifest manifest;
|
||||||
|
/** Shared icon font glyph */
|
||||||
|
const char* icon;
|
||||||
|
/** Rendered with an accent colour when not selected */
|
||||||
|
bool highlighted;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paged, non-scrolling grid of app tiles (icon above name) with "<" and ">" toolbar buttons.
|
||||||
|
* Must outlive the widgets it creates.
|
||||||
|
*/
|
||||||
|
class AppGrid final {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
struct Callbacks {
|
||||||
|
/** Returns the items in display order. Called on every rebuild. */
|
||||||
|
std::vector<AppGridItem> (*collect)(void* userData);
|
||||||
|
void (*onClicked)(const ::AppManifest& manifest, void* userData);
|
||||||
|
/** Optional */
|
||||||
|
void (*onLongPressed)(const ::AppManifest& manifest, void* userData);
|
||||||
|
/** Optional */
|
||||||
|
void (*onKey)(const ::AppManifest& manifest, uint32_t key, void* userData);
|
||||||
|
void* userData;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit AppGrid(const Callbacks& callbacks) : callbacks(callbacks) {}
|
||||||
|
|
||||||
|
/** 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuilds the grid asynchronously, so it is safe to call from a tile's own event.
|
||||||
|
* @param[in] keepSelection when true, the selected app stays selected and its page is shown
|
||||||
|
*/
|
||||||
|
void requestRepopulate(bool keepSelection);
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Callbacks callbacks;
|
||||||
|
std::vector<AppGridItem> items;
|
||||||
|
uint32_t page = 0;
|
||||||
|
bool keepSelectionOnRepopulate = true;
|
||||||
|
lv_obj_t* grid = nullptr;
|
||||||
|
lv_obj_t* prevButton = nullptr;
|
||||||
|
lv_obj_t* nextButton = nullptr;
|
||||||
|
|
||||||
|
void populate();
|
||||||
|
|
||||||
|
static void onDeferredRepopulate(void* userData);
|
||||||
|
static void onGridDeleted(lv_event_t* e);
|
||||||
|
static void onGridSizeChanged(lv_event_t* e);
|
||||||
|
static void onPrevPressed(lv_event_t* e);
|
||||||
|
static void onNextPressed(lv_event_t* e);
|
||||||
|
static void onTileClicked(lv_event_t* e);
|
||||||
|
static void onTileLongPressed(lv_event_t* e);
|
||||||
|
static void onTileKey(lv_event_t* e);
|
||||||
|
static void onTileFocusChanged(lv_event_t* e);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
240
Tactility/Source/app/AppGrid.cpp
Normal file
240
Tactility/Source/app/AppGrid.cpp
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
#include <Tactility/app/AppGrid.h>
|
||||||
|
|
||||||
|
#include <lvgl/devices/indev.h>
|
||||||
|
#include <lvgl/fonts.h>
|
||||||
|
#include <lvgl/widgets/toolbar.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace tt::app {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr int32_t TILE_GAP = 6;
|
||||||
|
constexpr uint32_t ICON_COLOR_SELECTED = 0x33BBFF;
|
||||||
|
constexpr uint32_t ICON_COLOR_HIGHLIGHTED = 0xF876E9;
|
||||||
|
|
||||||
|
struct PageLayout {
|
||||||
|
int32_t iconSize;
|
||||||
|
int32_t pad;
|
||||||
|
int32_t tileWidth;
|
||||||
|
int32_t tileHeight;
|
||||||
|
int32_t topOffset;
|
||||||
|
uint32_t pageSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
PageLayout computePageLayout(lv_obj_t* grid) {
|
||||||
|
const auto icon_size = static_cast<int32_t>(lvgl_get_shared_icon_font_2x_height());
|
||||||
|
const auto pad = icon_size / 16;
|
||||||
|
const auto gap = TILE_GAP;
|
||||||
|
const auto text_height = lv_font_get_line_height(lvgl_get_text_font(FONT_SIZE_SMALL));
|
||||||
|
const auto tile_min_width = 2 * icon_size + 2 * pad;
|
||||||
|
const auto tile_height = 2 * pad + icon_size + pad / 2 + text_height;
|
||||||
|
const auto available_width = lv_obj_get_content_width(grid);
|
||||||
|
const auto available_height = lv_obj_get_content_height(grid);
|
||||||
|
const auto columns = std::max<int32_t>(1, (available_width + gap) / (tile_min_width + gap));
|
||||||
|
const auto rows = std::max<int32_t>(1, (available_height + gap) / (tile_height + gap));
|
||||||
|
return {
|
||||||
|
.iconSize = icon_size,
|
||||||
|
.pad = pad,
|
||||||
|
.tileWidth = std::max<int32_t>(tile_min_width, (available_width - (columns - 1) * gap) / columns),
|
||||||
|
.tileHeight = tile_height,
|
||||||
|
.topOffset = std::max<int32_t>(0, (available_height - (rows * tile_height + (rows - 1) * gap)) / 2),
|
||||||
|
.pageSize = static_cast<uint32_t>(columns * rows)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
lv_color_t getIconColor(bool focused, bool highlighted) {
|
||||||
|
if (focused) {
|
||||||
|
return lv_color_hex(ICON_COLOR_SELECTED);
|
||||||
|
}
|
||||||
|
return highlighted ? lv_color_hex(ICON_COLOR_HIGHLIGHTED) : lv_color_white();
|
||||||
|
}
|
||||||
|
|
||||||
|
const AppGridItem* getTileItem(lv_event_t* e) {
|
||||||
|
return static_cast<const AppGridItem*>(lv_obj_get_user_data(lv_event_get_target_obj(e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void AppGrid::onDeferredRepopulate(void* userData) {
|
||||||
|
static_cast<AppGrid*>(userData)->populate();
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
lv_async_call_cancel(onDeferredRepopulate, 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.
|
||||||
|
void AppGrid::onGridSizeChanged(lv_event_t* e) {
|
||||||
|
lv_async_call(onDeferredRepopulate, lv_event_get_user_data(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGrid::onPrevPressed(lv_event_t* e) {
|
||||||
|
auto* self = static_cast<AppGrid*>(lv_event_get_user_data(e));
|
||||||
|
if (self->page > 0) {
|
||||||
|
self->page--;
|
||||||
|
self->populate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGrid::onNextPressed(lv_event_t* e) {
|
||||||
|
auto* self = static_cast<AppGrid*>(lv_event_get_user_data(e));
|
||||||
|
self->page++;
|
||||||
|
self->populate();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGrid::onTileClicked(lv_event_t* e) {
|
||||||
|
const auto* self = static_cast<AppGrid*>(lv_event_get_user_data(e));
|
||||||
|
self->callbacks.onClicked(getTileItem(e)->manifest, self->callbacks.userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGrid::onTileLongPressed(lv_event_t* e) {
|
||||||
|
const auto* self = static_cast<AppGrid*>(lv_event_get_user_data(e));
|
||||||
|
self->callbacks.onLongPressed(getTileItem(e)->manifest, self->callbacks.userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGrid::onTileKey(lv_event_t* e) {
|
||||||
|
const auto* self = static_cast<AppGrid*>(lv_event_get_user_data(e));
|
||||||
|
self->callbacks.onKey(getTileItem(e)->manifest, lv_event_get_key(e), self->callbacks.userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGrid::onTileFocusChanged(lv_event_t* e) {
|
||||||
|
lv_obj_t* tile = lv_event_get_target_obj(e);
|
||||||
|
lv_obj_t* icon = lv_obj_get_child(tile, 0);
|
||||||
|
const bool focused = lv_event_get_code(e) == LV_EVENT_FOCUSED;
|
||||||
|
lv_obj_set_style_text_color(icon, getIconColor(focused, getTileItem(e)->highlighted), LV_STATE_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGrid::requestRepopulate(bool keepSelection) {
|
||||||
|
keepSelectionOnRepopulate = keepSelection;
|
||||||
|
lv_async_call(onDeferredRepopulate, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGrid::populate() {
|
||||||
|
// Captured before lv_obj_clean() deletes the currently focused tile below: LVGL moves
|
||||||
|
// focus elsewhere as it leaves the group, and creating replacement tiles doesn't restore
|
||||||
|
// it, so the app id is remembered here and re-focused on its new tile once rebuilt.
|
||||||
|
std::string focusedAppId;
|
||||||
|
lv_group_t* group = lv_group_get_default();
|
||||||
|
if (group != nullptr && keepSelectionOnRepopulate) {
|
||||||
|
lv_obj_t* focused = lv_group_get_focused(group);
|
||||||
|
if (focused != nullptr && lv_obj_get_parent(focused) == grid) {
|
||||||
|
focusedAppId = static_cast<const AppGridItem*>(lv_obj_get_user_data(focused))->manifest.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keepSelectionOnRepopulate = true;
|
||||||
|
|
||||||
|
// Old tiles hold pointers into items, so they are deleted before items is replaced.
|
||||||
|
lv_obj_clean(grid);
|
||||||
|
items = callbacks.collect(callbacks.userData);
|
||||||
|
|
||||||
|
// Centers a full page of rows, so a partially filled last page keeps its rows in the same positions.
|
||||||
|
lv_obj_set_style_pad_top(grid, 0, LV_STATE_DEFAULT);
|
||||||
|
const PageLayout layout = computePageLayout(grid);
|
||||||
|
lv_obj_set_style_pad_top(grid, layout.topOffset, LV_STATE_DEFAULT);
|
||||||
|
|
||||||
|
const uint32_t item_count = items.size();
|
||||||
|
const uint32_t page_count = std::max<uint32_t>(1, (item_count + layout.pageSize - 1) / layout.pageSize);
|
||||||
|
// Follows the focused app onto its new page, e.g. after a reorder moved it.
|
||||||
|
if (!focusedAppId.empty()) {
|
||||||
|
const auto it = std::ranges::find_if(items, [&](const AppGridItem& item) {
|
||||||
|
return focusedAppId == item.manifest.id;
|
||||||
|
});
|
||||||
|
if (it != items.end()) {
|
||||||
|
page = static_cast<uint32_t>(it - items.begin()) / layout.pageSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
page = std::min(page, page_count - 1);
|
||||||
|
|
||||||
|
lv_obj_set_state(prevButton, LV_STATE_DISABLED, page == 0);
|
||||||
|
lv_obj_set_state(nextButton, LV_STATE_DISABLED, page + 1 >= page_count);
|
||||||
|
// The toolbar wraps each action button, so the wrapper is hidden to free its space too
|
||||||
|
lv_obj_set_flag(lv_obj_get_parent(prevButton), LV_OBJ_FLAG_HIDDEN, page_count <= 1);
|
||||||
|
lv_obj_set_flag(lv_obj_get_parent(nextButton), LV_OBJ_FLAG_HIDDEN, page_count <= 1);
|
||||||
|
|
||||||
|
lv_obj_t* focusedTile = nullptr;
|
||||||
|
const uint32_t first = page * layout.pageSize;
|
||||||
|
const uint32_t last = std::min(item_count, first + layout.pageSize);
|
||||||
|
for (uint32_t i = first; i < last; i++) {
|
||||||
|
const auto& item = items[i];
|
||||||
|
|
||||||
|
lv_obj_t* tile = lv_button_create(grid);
|
||||||
|
lv_obj_set_size(tile, layout.tileWidth, layout.tileHeight);
|
||||||
|
lv_obj_set_style_pad_all(tile, layout.pad, LV_STATE_DEFAULT);
|
||||||
|
lv_obj_set_style_pad_row(tile, layout.pad / 2, LV_STATE_DEFAULT);
|
||||||
|
lv_obj_set_style_shadow_width(tile, 0, LV_STATE_DEFAULT);
|
||||||
|
lv_obj_set_style_bg_opa(tile, LV_OPA_TRANSP, LV_STATE_DEFAULT);
|
||||||
|
lv_obj_set_style_outline_width(tile, 0, LV_STATE_FOCUSED);
|
||||||
|
lv_obj_set_style_outline_width(tile, 0, LV_STATE_FOCUS_KEY);
|
||||||
|
lv_obj_set_flex_flow(tile, LV_FLEX_FLOW_COLUMN);
|
||||||
|
lv_obj_set_flex_align(tile, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||||
|
lv_obj_set_user_data(tile, const_cast<AppGridItem*>(&item));
|
||||||
|
|
||||||
|
lv_obj_t* icon = lv_label_create(tile);
|
||||||
|
lv_obj_set_style_text_font(icon, lvgl_get_shared_icon_font_2x(), 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_style_text_color(icon, getIconColor(false, item.highlighted), LV_STATE_DEFAULT);
|
||||||
|
lv_label_set_text(icon, item.icon);
|
||||||
|
|
||||||
|
lv_obj_t* label = lv_label_create(tile);
|
||||||
|
lv_obj_set_style_text_font(label, lvgl_get_text_font(FONT_SIZE_SMALL), LV_STATE_DEFAULT);
|
||||||
|
lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, LV_STATE_DEFAULT);
|
||||||
|
lv_obj_set_width(label, LV_PCT(100));
|
||||||
|
lv_label_set_long_mode(label, LV_LABEL_LONG_MODE_DOTS);
|
||||||
|
lv_label_set_text(label, item.manifest.name);
|
||||||
|
|
||||||
|
lv_obj_add_event_cb(tile, onTileClicked, LV_EVENT_SHORT_CLICKED, this);
|
||||||
|
if (callbacks.onLongPressed != nullptr) {
|
||||||
|
lv_obj_add_event_cb(tile, onTileLongPressed, LV_EVENT_LONG_PRESSED, this);
|
||||||
|
}
|
||||||
|
if (callbacks.onKey != nullptr) {
|
||||||
|
lv_obj_add_event_cb(tile, onTileKey, LV_EVENT_KEY, this);
|
||||||
|
}
|
||||||
|
lv_obj_add_event_cb(tile, onTileFocusChanged, LV_EVENT_FOCUSED, nullptr);
|
||||||
|
lv_obj_add_event_cb(tile, onTileFocusChanged, LV_EVENT_DEFOCUSED, nullptr);
|
||||||
|
|
||||||
|
if (!focusedAppId.empty() && focusedAppId == item.manifest.id) {
|
||||||
|
focusedTile = tile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (focusedTile != nullptr) {
|
||||||
|
lv_group_focus_obj(focusedTile);
|
||||||
|
lv_obj_add_state(focusedTile, LV_STATE_FOCUS_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppGrid::createWidgets(lv_obj_t* parent, lv_obj_t* toolbar) {
|
||||||
|
prevButton = lvgl_toolbar_add_text_button_action(toolbar, "<", onPrevPressed, this);
|
||||||
|
nextButton = lvgl_toolbar_add_text_button_action(toolbar, ">", onNextPressed, this);
|
||||||
|
|
||||||
|
grid = lv_obj_create(parent);
|
||||||
|
lv_obj_set_width(grid, LV_PCT(100));
|
||||||
|
lv_obj_set_flex_grow(grid, 1);
|
||||||
|
lv_obj_set_flex_flow(grid, LV_FLEX_FLOW_ROW_WRAP);
|
||||||
|
lv_obj_set_style_pad_all(grid, 0, LV_STATE_DEFAULT);
|
||||||
|
lv_obj_set_style_pad_row(grid, TILE_GAP, LV_STATE_DEFAULT);
|
||||||
|
lv_obj_set_style_pad_column(grid, TILE_GAP, LV_STATE_DEFAULT);
|
||||||
|
lv_obj_set_style_border_width(grid, 0, LV_STATE_DEFAULT);
|
||||||
|
lv_obj_remove_flag(grid, LV_OBJ_FLAG_SCROLLABLE);
|
||||||
|
|
||||||
|
// Resolves the grid's flex_grow height, which the page size is derived from.
|
||||||
|
lv_obj_update_layout(parent);
|
||||||
|
|
||||||
|
lv_obj_add_event_cb(grid, onGridDeleted, LV_EVENT_DELETE, this);
|
||||||
|
lv_obj_add_event_cb(grid, onGridSizeChanged, LV_EVENT_SIZE_CHANGED, this);
|
||||||
|
|
||||||
|
populate();
|
||||||
|
|
||||||
|
const bool next_visible = !lv_obj_has_flag(lv_obj_get_parent(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -12,6 +12,7 @@
|
|||||||
#include <tactility/log.h>
|
#include <tactility/log.h>
|
||||||
|
|
||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
|
#include <lvgl/devices/indev.h>
|
||||||
#include <lvgl/widgets/toolbar.h>
|
#include <lvgl/widgets/toolbar.h>
|
||||||
|
|
||||||
namespace tt::app::alertdialog {
|
namespace tt::app::alertdialog {
|
||||||
@ -55,7 +56,7 @@ void onButtonPressed(lv_event_t* e) {
|
|||||||
app_event_emit_close(btnCtx->ctx->appInstanceId);
|
app_event_emit_close(btnCtx->ctx->appInstanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void createButton(Context* ctx, lv_obj_t* parent, const std::string& text, int32_t index) {
|
lv_obj_t* createButton(Context* ctx, lv_obj_t* parent, const std::string& text, int32_t index) {
|
||||||
lv_obj_t* button = lv_button_create(parent);
|
lv_obj_t* button = lv_button_create(parent);
|
||||||
lv_obj_t* button_label = lv_label_create(button);
|
lv_obj_t* button_label = lv_label_create(button);
|
||||||
lv_obj_align(button_label, LV_ALIGN_CENTER, 0, 0);
|
lv_obj_align(button_label, LV_ALIGN_CENTER, 0, 0);
|
||||||
@ -63,6 +64,7 @@ void createButton(Context* ctx, lv_obj_t* parent, const std::string& text, int32
|
|||||||
auto* btnCtx = new ButtonContext { ctx, index };
|
auto* btnCtx = new ButtonContext { ctx, index };
|
||||||
lv_obj_add_event_cb(button, onButtonPressed, LV_EVENT_SHORT_CLICKED, btnCtx);
|
lv_obj_add_event_cb(button, onButtonPressed, LV_EVENT_SHORT_CLICKED, btnCtx);
|
||||||
lv_obj_add_event_cb(button, onButtonDeleted, LV_EVENT_DELETE, btnCtx);
|
lv_obj_add_event_cb(button, onButtonDeleted, LV_EVENT_DELETE, btnCtx);
|
||||||
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
void createWidgets(lv_obj_t* parent, void* userData) {
|
void createWidgets(lv_obj_t* parent, void* userData) {
|
||||||
@ -92,8 +94,17 @@ void createWidgets(lv_obj_t* parent, void* userData) {
|
|||||||
lv_obj_set_style_pad_column(button_wrapper, 12, LV_STATE_DEFAULT);
|
lv_obj_set_style_pad_column(button_wrapper, 12, LV_STATE_DEFAULT);
|
||||||
lv_obj_set_style_pad_ver(button_wrapper, 4, LV_STATE_DEFAULT);
|
lv_obj_set_style_pad_ver(button_wrapper, 4, LV_STATE_DEFAULT);
|
||||||
|
|
||||||
|
lv_obj_t* first_button = nullptr;
|
||||||
for (int32_t index = 0; index < argc - 2; index++) {
|
for (int32_t index = 0; index < argc - 2; index++) {
|
||||||
createButton(ctx, button_wrapper, argv[2 + index], index);
|
lv_obj_t* button = createButton(ctx, button_wrapper, argv[2 + index], index);
|
||||||
|
if (first_button == nullptr) {
|
||||||
|
first_button = button;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first_button != nullptr && !lvgl_indev_exists(LV_INDEV_TYPE_POINTER)) {
|
||||||
|
lv_group_focus_obj(first_button);
|
||||||
|
lv_obj_add_state(first_button, LV_STATE_FOCUS_KEY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <lvgl_window_manager/window_manager.h>
|
#include <lvgl_window_manager/window_manager.h>
|
||||||
|
|
||||||
|
#include <Tactility/app/AppGrid.h>
|
||||||
#include <Tactility/app/alertdialog/AlertDialog.h>
|
#include <Tactility/app/alertdialog/AlertDialog.h>
|
||||||
#include <Tactility/app/applist/Favourites.h>
|
#include <Tactility/app/applist/Favourites.h>
|
||||||
|
|
||||||
@ -19,7 +20,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <lvgl/icons/shared.h>
|
#include <lvgl/icons/shared.h>
|
||||||
#include <lvgl/fonts.h>
|
|
||||||
#include <lvgl/widgets/toolbar.h>
|
#include <lvgl/widgets/toolbar.h>
|
||||||
|
|
||||||
namespace tt::app::applist {
|
namespace tt::app::applist {
|
||||||
@ -28,12 +28,6 @@ constexpr auto* TAG = "AppList";
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct Context {
|
|
||||||
uint32_t appInstanceId;
|
|
||||||
Favourites favourites;
|
|
||||||
std::vector<::AppManifest> manifests;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct IconEntry {
|
struct IconEntry {
|
||||||
const char* id;
|
const char* id;
|
||||||
const char* icon;
|
const char* icon;
|
||||||
@ -58,82 +52,6 @@ const char* appIcon(const ::AppManifest* manifest) {
|
|||||||
return LVGL_ICON_SHARED_DEPLOYED_CODE;
|
return LVGL_ICON_SHARED_DEPLOYED_CODE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void populateList(lv_obj_t* list);
|
|
||||||
|
|
||||||
// Named (not a lambda) so lv_async_call_cancel() below has a stable function pointer to match against.
|
|
||||||
void deferredRepopulate(void* userData) {
|
|
||||||
populateList(static_cast<lv_obj_t*>(userData));
|
|
||||||
}
|
|
||||||
|
|
||||||
void onListDeleted(lv_event_t* e) {
|
|
||||||
// Cancels a still-pending deferredRepopulate() scheduled by a long-press/key-press right
|
|
||||||
// before the list itself was deleted (e.g. the app closing)
|
|
||||||
// Otherwise it would run later against this now-dangling lv_obj_t*.
|
|
||||||
lv_async_call_cancel(deferredRepopulate, lv_event_get_target_obj(e));
|
|
||||||
}
|
|
||||||
|
|
||||||
void onAppPressed(lv_event_t* e) {
|
|
||||||
const auto* manifest = static_cast<const ::AppManifest*>(lv_event_get_user_data(e));
|
|
||||||
uint32_t instanceId = 0;
|
|
||||||
AppStartContext context;
|
|
||||||
if (app_start_context_from_id(manifest->id, &context) == ERROR_NONE && app_start_with_context(&context, &instanceId) == ERROR_NONE) {
|
|
||||||
lv_obj_t* list = lv_obj_get_parent(lv_event_get_target_obj(e));
|
|
||||||
auto* ctx = static_cast<Context*>(lv_obj_get_user_data(list));
|
|
||||||
app_event_emit_close(ctx->appInstanceId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onAppLongPressed(lv_event_t* e) {
|
|
||||||
const auto* manifest = static_cast<const ::AppManifest*>(lv_event_get_user_data(e));
|
|
||||||
lv_obj_t* list = lv_obj_get_parent(lv_event_get_target_obj(e));
|
|
||||||
auto* ctx = static_cast<Context*>(lv_obj_get_user_data(list));
|
|
||||||
if (!ctx->favourites.toggle(manifest->id)) {
|
|
||||||
LOG_E(TAG, "Failed to save favourites");
|
|
||||||
}
|
|
||||||
// Deferred: populateList() deletes this button via lv_obj_clean() while its own long-press is still dispatching.
|
|
||||||
lv_async_call(deferredRepopulate, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onAppKeyPressed(lv_event_t* e) {
|
|
||||||
const uint32_t key = lv_event_get_key(e);
|
|
||||||
if (key != 'f' && key != 'F') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const auto* manifest = static_cast<const ::AppManifest*>(lv_event_get_user_data(e));
|
|
||||||
lv_obj_t* list = lv_obj_get_parent(lv_event_get_target_obj(e));
|
|
||||||
auto* ctx = static_cast<Context*>(lv_obj_get_user_data(list));
|
|
||||||
if (!ctx->favourites.toggle(manifest->id)) {
|
|
||||||
LOG_E(TAG, "Failed to save favourites");
|
|
||||||
}
|
|
||||||
// Deferred: populateList() deletes this button via lv_obj_clean() while its own key event is still dispatching.
|
|
||||||
lv_async_call(deferredRepopulate, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onBackPressed(lv_event_t* event) {
|
|
||||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
|
||||||
app_event_emit_close(ctx->appInstanceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void onHelpPressed(lv_event_t* event) {
|
|
||||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
|
||||||
alertdialog::start(ctx->appInstanceId, "Help", "Long-press an app, or press F, to toggle it as a favorite.");
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_obj_t* createAppWidget(const ::AppManifest* manifest, lv_obj_t* list, bool favourite) {
|
|
||||||
// Plain "*" prefix, not an icon: shared Material Symbols font is subsetted and has no star glyph.
|
|
||||||
const std::string label = favourite ? (std::string("* ") + manifest->name) : manifest->name;
|
|
||||||
lv_obj_t* btn = lv_list_add_button(list, appIcon(manifest), label.c_str());
|
|
||||||
lv_obj_t* image = lv_obj_get_child(btn, 0);
|
|
||||||
lv_obj_set_style_text_font(image, lvgl_get_shared_icon_font(), LV_PART_MAIN);
|
|
||||||
// Not read by any event here (those get the manifest via their own callback user data) - lets
|
|
||||||
// populateList() identify a button by app id after a rebuild, to restore focus onto it.
|
|
||||||
lv_obj_set_user_data(btn, const_cast<::AppManifest*>(manifest));
|
|
||||||
lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_SHORT_CLICKED, const_cast<::AppManifest*>(manifest));
|
|
||||||
lv_obj_add_event_cb(btn, &onAppLongPressed, LV_EVENT_LONG_PRESSED, const_cast<::AppManifest*>(manifest));
|
|
||||||
lv_obj_add_event_cb(btn, &onAppKeyPressed, LV_EVENT_KEY, const_cast<::AppManifest*>(manifest));
|
|
||||||
return btn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void collectManifest(const ::AppManifest* manifest, void* context) {
|
void collectManifest(const ::AppManifest* manifest, void* context) {
|
||||||
auto* manifests = static_cast<std::vector<::AppManifest>*>(context);
|
auto* manifests = static_cast<std::vector<::AppManifest>*>(context);
|
||||||
manifests->push_back(*manifest);
|
manifests->push_back(*manifest);
|
||||||
@ -150,25 +68,25 @@ void collectIncompatibleAppIds(const AppPackage* package, void* context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void populateList(lv_obj_t* list) {
|
std::vector<AppGridItem> collectItems(void* userData);
|
||||||
// Captured before lv_obj_clean() deletes the currently focused button below: LVGL moves
|
void onAppClicked(const ::AppManifest& manifest, void* userData);
|
||||||
// focus elsewhere as it leaves the group, and creating replacement buttons doesn't restore
|
void onAppLongPressed(const ::AppManifest& manifest, void* userData);
|
||||||
// it, so the app id is remembered here and re-focused on its new button once rebuilt.
|
void onAppKey(const ::AppManifest& manifest, uint32_t key, void* userData);
|
||||||
std::string focusedAppId;
|
|
||||||
lv_group_t* group = lv_group_get_default();
|
|
||||||
if (group != nullptr) {
|
|
||||||
lv_obj_t* focused = lv_group_get_focused(group);
|
|
||||||
if (focused != nullptr && lv_obj_get_parent(focused) == list) {
|
|
||||||
const auto* focusedManifest = static_cast<const ::AppManifest*>(lv_obj_get_user_data(focused));
|
|
||||||
if (focusedManifest != nullptr) {
|
|
||||||
focusedAppId = focusedManifest->id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lv_obj_clean(list);
|
struct Context {
|
||||||
|
uint32_t appInstanceId;
|
||||||
|
Favourites favourites;
|
||||||
|
AppGrid grid { AppGrid::Callbacks {
|
||||||
|
.collect = collectItems,
|
||||||
|
.onClicked = onAppClicked,
|
||||||
|
.onLongPressed = onAppLongPressed,
|
||||||
|
.onKey = onAppKey,
|
||||||
|
.userData = this
|
||||||
|
} };
|
||||||
|
};
|
||||||
|
|
||||||
auto* ctx = static_cast<Context*>(lv_obj_get_user_data(list));
|
std::vector<AppGridItem> collectItems(void* userData) {
|
||||||
|
const auto* ctx = static_cast<Context*>(userData);
|
||||||
const std::vector<std::string> favouriteIds = ctx->favourites.load();
|
const std::vector<std::string> favouriteIds = ctx->favourites.load();
|
||||||
|
|
||||||
std::vector<::AppManifest> collected;
|
std::vector<::AppManifest> collected;
|
||||||
@ -176,55 +94,74 @@ void populateList(lv_obj_t* list) {
|
|||||||
std::vector<std::string> incompatibleIds;
|
std::vector<std::string> incompatibleIds;
|
||||||
app_manager_for_each_package(collectIncompatibleAppIds, &incompatibleIds);
|
app_manager_for_each_package(collectIncompatibleAppIds, &incompatibleIds);
|
||||||
std::erase_if(collected, [&](const ::AppManifest& manifest) {
|
std::erase_if(collected, [&](const ::AppManifest& manifest) {
|
||||||
|
const bool is_valid_category = (manifest.category == APP_CATEGORY_USER) || (manifest.category == APP_CATEGORY_SYSTEM);
|
||||||
|
if (!is_valid_category || (manifest.flags & APP_MANIFEST_FLAG_HIDDEN) != 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return std::ranges::find(incompatibleIds, std::string(manifest.id)) != incompatibleIds.end();
|
return std::ranges::find(incompatibleIds, std::string(manifest.id)) != incompatibleIds.end();
|
||||||
});
|
});
|
||||||
std::ranges::sort(collected, [&](const ::AppManifest& a, const ::AppManifest& b) {
|
|
||||||
const bool aFavourite = Favourites::contains(favouriteIds, a.id);
|
std::vector<AppGridItem> items;
|
||||||
const bool bFavourite = Favourites::contains(favouriteIds, b.id);
|
items.reserve(collected.size());
|
||||||
if (aFavourite != bFavourite) {
|
for (const auto& manifest : collected) {
|
||||||
return aFavourite;
|
items.push_back({ manifest, appIcon(&manifest), Favourites::contains(favouriteIds, manifest.id) });
|
||||||
}
|
}
|
||||||
return strcmp(a.name, b.name) < 0;
|
std::ranges::sort(items, [](const AppGridItem& a, const AppGridItem& b) {
|
||||||
|
if (a.highlighted != b.highlighted) {
|
||||||
|
return a.highlighted;
|
||||||
|
}
|
||||||
|
return strcmp(a.manifest.name, b.manifest.name) < 0;
|
||||||
});
|
});
|
||||||
// Old buttons (and every pointer into the previous ctx->manifests) are already gone via
|
return items;
|
||||||
// lv_obj_clean() above, so this is safe to replace wholesale before building new ones.
|
|
||||||
ctx->manifests = std::move(collected);
|
|
||||||
|
|
||||||
lv_obj_t* focusedButton = nullptr;
|
|
||||||
for (const auto& manifest: ctx->manifests) {
|
|
||||||
bool is_valid_category = (manifest.category == APP_CATEGORY_USER) || (manifest.category == APP_CATEGORY_SYSTEM);
|
|
||||||
if (is_valid_category && (manifest.flags & APP_MANIFEST_FLAG_HIDDEN) == 0) {
|
|
||||||
lv_obj_t* btn = createAppWidget(&manifest, list, Favourites::contains(favouriteIds, manifest.id));
|
|
||||||
if (!focusedAppId.empty() && focusedAppId == manifest.id) {
|
|
||||||
focusedButton = btn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onAppClicked(const ::AppManifest& manifest, void* userData) {
|
||||||
|
const auto* ctx = static_cast<Context*>(userData);
|
||||||
|
uint32_t instanceId = 0;
|
||||||
|
AppStartContext context;
|
||||||
|
if (app_start_context_from_id(manifest.id, &context) == ERROR_NONE && app_start_with_context(&context, &instanceId) == ERROR_NONE) {
|
||||||
|
app_event_emit_close(ctx->appInstanceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (focusedButton != nullptr) {
|
void toggleFavourite(Context* ctx, const char* appId, bool keepSelection) {
|
||||||
lv_group_focus_obj(focusedButton);
|
if (!ctx->favourites.toggle(appId)) {
|
||||||
lv_obj_add_state(focusedButton, LV_STATE_FOCUS_KEY);
|
LOG_E(TAG, "Failed to save favourites");
|
||||||
}
|
}
|
||||||
|
ctx->grid.requestRepopulate(keepSelection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAppLongPressed(const ::AppManifest& manifest, void* userData) {
|
||||||
|
toggleFavourite(static_cast<Context*>(userData), manifest.id, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAppKey(const ::AppManifest& manifest, uint32_t key, void* userData) {
|
||||||
|
if (key == 'f' || key == 'F') {
|
||||||
|
toggleFavourite(static_cast<Context*>(userData), manifest.id, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onBackPressed(lv_event_t* event) {
|
||||||
|
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||||
|
app_event_emit_close(ctx->appInstanceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onHelpPressed(lv_event_t* event) {
|
||||||
|
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||||
|
alertdialog::start(ctx->appInstanceId, "Help", "Long-press an app, or press F, to toggle it as a favorite.");
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
// Flex column + flex_grow so the toolbar/list split recomputes on layout instead of going stale after a resize.
|
// Flex column + flex_grow so the toolbar/grid split recomputes on layout instead of going stale after a resize.
|
||||||
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, "Apps");
|
auto* toolbar = lvgl_toolbar_create(parent, "Apps");
|
||||||
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx);
|
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx);
|
||||||
|
ctx->grid.createWidgets(parent, toolbar);
|
||||||
lvgl_toolbar_add_text_button_action(toolbar, "?", onHelpPressed, ctx);
|
lvgl_toolbar_add_text_button_action(toolbar, "?", onHelpPressed, ctx);
|
||||||
|
|
||||||
lv_obj_t* list = lv_list_create(parent);
|
|
||||||
lv_obj_set_width(list, LV_PCT(100));
|
|
||||||
lv_obj_set_flex_grow(list, 1);
|
|
||||||
lv_obj_set_user_data(list, ctx);
|
|
||||||
lv_obj_add_event_cb(list, &onListDeleted, LV_EVENT_DELETE, nullptr);
|
|
||||||
|
|
||||||
populateList(list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t appMain(int argc, char* argv[]) {
|
int32_t appMain(int argc, char* argv[]) {
|
||||||
@ -274,7 +211,7 @@ extern const ::AppManifest manifest = {
|
|||||||
.category = APP_CATEGORY_SYSTEM,
|
.category = APP_CATEGORY_SYSTEM,
|
||||||
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
|
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
|
||||||
.flags = APP_MANIFEST_FLAG_HIDDEN,
|
.flags = APP_MANIFEST_FLAG_HIDDEN,
|
||||||
.stack = { .depth = 4000, .desired_memory_capability = 0 },
|
.stack = { .depth = 5120, .desired_memory_capability = 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@ -27,6 +27,7 @@
|
|||||||
#include <Tactility/settings/DisplaySettings.h>
|
#include <Tactility/settings/DisplaySettings.h>
|
||||||
|
|
||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
|
#include <lvgl/devices/indev.h>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <format>
|
#include <format>
|
||||||
@ -214,6 +215,13 @@ void createSplashWidgets(lv_obj_t* root, void*) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void selectIfNoPointer(lv_obj_t* button) {
|
||||||
|
if (!lvgl_indev_exists(LV_INDEV_TYPE_POINTER)) {
|
||||||
|
lv_group_focus_obj(button);
|
||||||
|
lv_obj_add_state(button, LV_STATE_FOCUS_KEY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void createSdCardMissingWidgets(lv_obj_t* root, void*) {
|
void createSdCardMissingWidgets(lv_obj_t* root, void*) {
|
||||||
lvgl::obj_set_style_bg_blacken(root);
|
lvgl::obj_set_style_bg_blacken(root);
|
||||||
lv_obj_set_style_border_width(root, 0, LV_STATE_DEFAULT);
|
lv_obj_set_style_border_width(root, 0, LV_STATE_DEFAULT);
|
||||||
@ -235,6 +243,7 @@ void createSdCardMissingWidgets(lv_obj_t* root, void*) {
|
|||||||
esp_restart();
|
esp_restart();
|
||||||
#endif
|
#endif
|
||||||
}, LV_EVENT_SHORT_CLICKED, nullptr);
|
}, LV_EVENT_SHORT_CLICKED, nullptr);
|
||||||
|
selectIfNoPointer(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replaces the splash with a self-contained error screen (no dependency on the old alertdialog
|
// Replaces the splash with a self-contained error screen (no dependency on the old alertdialog
|
||||||
@ -267,6 +276,7 @@ void createUsbBootModeFailedWidgets(lv_obj_t* root, void*) {
|
|||||||
esp_restart();
|
esp_restart();
|
||||||
#endif
|
#endif
|
||||||
}, LV_EVENT_SHORT_CLICKED, nullptr);
|
}, LV_EVENT_SHORT_CLICKED, nullptr);
|
||||||
|
selectIfNoPointer(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showUsbBootModeFailedScreen() {
|
void showUsbBootModeFailedScreen() {
|
||||||
|
|||||||
@ -109,7 +109,7 @@ 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, "Region & Language");
|
auto* toolbar = lvgl_toolbar_create(parent, "Locale");
|
||||||
// The global toolbar nav callback only knows how to stop old-model apps.
|
// The global toolbar nav callback only knows how to stop old-model apps.
|
||||||
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx);
|
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx);
|
||||||
|
|
||||||
@ -180,7 +180,7 @@ int32_t appMain(int argc, char* argv[]) {
|
|||||||
|
|
||||||
extern const ::AppManifest manifest = {
|
extern const ::AppManifest manifest = {
|
||||||
.id = "tactility.localesettings",
|
.id = "tactility.localesettings",
|
||||||
.name = "Region & Language",
|
.name = "Locale",
|
||||||
.category = APP_CATEGORY_SETTINGS,
|
.category = APP_CATEGORY_SETTINGS,
|
||||||
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
.location = { APP_LOCATION_MEMORY, reinterpret_cast<void*>(appMain) },
|
||||||
.flags = 0,
|
.flags = 0,
|
||||||
|
|||||||
@ -9,6 +9,7 @@
|
|||||||
#include <lvgl_window_manager/window_manager.h>
|
#include <lvgl_window_manager/window_manager.h>
|
||||||
|
|
||||||
#include <lvgl.h>
|
#include <lvgl.h>
|
||||||
|
#include <lvgl/devices/indev.h>
|
||||||
#include <lvgl/fonts.h>
|
#include <lvgl/fonts.h>
|
||||||
#include <tactility/check.h>
|
#include <tactility/check.h>
|
||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
@ -133,6 +134,11 @@ void createWidgets(lv_obj_t* parent, void* userData) {
|
|||||||
auto* no_label = lv_label_create(no_button);
|
auto* no_label = lv_label_create(no_button);
|
||||||
lv_label_set_text(no_label, "No");
|
lv_label_set_text(no_label, "No");
|
||||||
lv_obj_add_event_cb(no_button, onNoPressed, LV_EVENT_SHORT_CLICKED, ctx);
|
lv_obj_add_event_cb(no_button, onNoPressed, LV_EVENT_SHORT_CLICKED, ctx);
|
||||||
|
|
||||||
|
if (!lvgl_indev_exists(LV_INDEV_TYPE_POINTER)) {
|
||||||
|
lv_group_focus_obj(yes_button);
|
||||||
|
lv_obj_add_state(yes_button, LV_STATE_FOCUS_KEY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t appMain(int argc, char* argv[]) {
|
int32_t appMain(int argc, char* argv[]) {
|
||||||
|
|||||||
@ -6,8 +6,9 @@
|
|||||||
|
|
||||||
#include <lvgl_window_manager/window_manager.h>
|
#include <lvgl_window_manager/window_manager.h>
|
||||||
|
|
||||||
|
#include <Tactility/app/AppGrid.h>
|
||||||
|
|
||||||
#include <lvgl/icons/shared.h>
|
#include <lvgl/icons/shared.h>
|
||||||
#include <lvgl/fonts.h>
|
|
||||||
#include <lvgl/widgets/toolbar.h>
|
#include <lvgl/widgets/toolbar.h>
|
||||||
#include <tactility/check.h>
|
#include <tactility/check.h>
|
||||||
|
|
||||||
@ -21,14 +22,6 @@ namespace tt::app::settings {
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
struct Context {
|
|
||||||
uint32_t appInstanceId;
|
|
||||||
// Owned copies backing every button's user data, so a button never references the ledger
|
|
||||||
// directly - safe even if an external app is uninstalled while Settings stays open. Built
|
|
||||||
// once, in createWidgets() below (this list is never repopulated).
|
|
||||||
std::vector<::AppManifest> manifests;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct IconEntry {
|
struct IconEntry {
|
||||||
const char* id;
|
const char* id;
|
||||||
const char* icon;
|
const char* icon;
|
||||||
@ -60,37 +53,55 @@ const char* appIcon(const ::AppManifest* manifest) {
|
|||||||
return LVGL_ICON_SHARED_DEPLOYED_CODE;
|
return LVGL_ICON_SHARED_DEPLOYED_CODE;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onAppPressed(lv_event_t* e) {
|
void onAppClicked(const ::AppManifest& manifest, void*) {
|
||||||
// Fire-and-forget top-level navigation, same as AppList's own app-launch buttons.
|
// Fire-and-forget top-level navigation, same as AppList's own app-launch buttons.
|
||||||
const auto* manifest = static_cast<const ::AppManifest*>(lv_event_get_user_data(e));
|
|
||||||
uint32_t instanceId = 0;
|
uint32_t instanceId = 0;
|
||||||
// Re-resolved by id against the live ledger, not the (possibly stale) cached manifest above:
|
// Re-resolved by id against the live ledger, not the (possibly stale) cached manifest above:
|
||||||
// fails gracefully if the app was uninstalled since this button was built, instead of handing
|
// fails gracefully if the app was uninstalled since this button was built, instead of handing
|
||||||
// app_manager_start_internal() a pointer it would store for the new instance's whole lifetime.
|
// app_manager_start_internal() a pointer it would store for the new instance's whole lifetime.
|
||||||
AppStartContext context;
|
AppStartContext context;
|
||||||
if (app_start_context_from_id(manifest->id, &context) == ERROR_NONE) {
|
if (app_start_context_from_id(manifest.id, &context) == ERROR_NONE) {
|
||||||
app_start_with_context(&context, &instanceId);
|
app_start_with_context(&context, &instanceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onBackPressed(lv_event_t* event) {
|
|
||||||
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
|
||||||
app_event_emit_close(ctx->appInstanceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void createWidget(const ::AppManifest* manifest, lv_obj_t* list) {
|
|
||||||
check(list);
|
|
||||||
auto* btn = lv_list_add_button(list, appIcon(manifest), manifest->name);
|
|
||||||
lv_obj_t* image = lv_obj_get_child(btn, 0);
|
|
||||||
lv_obj_set_style_text_font(image, lvgl_get_shared_icon_font(), LV_PART_MAIN);
|
|
||||||
lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_SHORT_CLICKED, const_cast<::AppManifest*>(manifest));
|
|
||||||
}
|
|
||||||
|
|
||||||
void collectManifest(const ::AppManifest* manifest, void* context) {
|
void collectManifest(const ::AppManifest* manifest, void* context) {
|
||||||
auto* manifests = static_cast<std::vector<::AppManifest>*>(context);
|
auto* manifests = static_cast<std::vector<::AppManifest>*>(context);
|
||||||
manifests->push_back(*manifest);
|
manifests->push_back(*manifest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<AppGridItem> collectItems(void*) {
|
||||||
|
std::vector<::AppManifest> collected;
|
||||||
|
app_manager_for_each_manifest(collectManifest, &collected);
|
||||||
|
std::ranges::sort(collected, [](const ::AppManifest& a, const ::AppManifest& b) {
|
||||||
|
return strcmp(a.name, b.name) < 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<AppGridItem> items;
|
||||||
|
for (const auto& manifest : collected) {
|
||||||
|
if (manifest.category == APP_CATEGORY_SETTINGS && (manifest.flags & APP_MANIFEST_FLAG_HIDDEN) == 0) {
|
||||||
|
items.push_back({ manifest, appIcon(&manifest), false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
uint32_t appInstanceId;
|
||||||
|
AppGrid grid { AppGrid::Callbacks {
|
||||||
|
.collect = collectItems,
|
||||||
|
.onClicked = onAppClicked,
|
||||||
|
.onLongPressed = nullptr,
|
||||||
|
.onKey = nullptr,
|
||||||
|
.userData = nullptr
|
||||||
|
} };
|
||||||
|
};
|
||||||
|
|
||||||
|
void onBackPressed(lv_event_t* event) {
|
||||||
|
auto* ctx = static_cast<Context*>(lv_event_get_user_data(event));
|
||||||
|
app_event_emit_close(ctx->appInstanceId);
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
@ -99,23 +110,7 @@ void createWidgets(lv_obj_t* parent, void* userData) {
|
|||||||
|
|
||||||
auto* toolbar = lvgl_toolbar_create(parent, "Settings");
|
auto* toolbar = lvgl_toolbar_create(parent, "Settings");
|
||||||
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx);
|
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onBackPressed, ctx);
|
||||||
|
ctx->grid.createWidgets(parent, toolbar);
|
||||||
auto* list = lv_list_create(parent);
|
|
||||||
lv_obj_set_width(list, LV_PCT(100));
|
|
||||||
lv_obj_set_flex_grow(list, 1);
|
|
||||||
|
|
||||||
std::vector<::AppManifest> collected;
|
|
||||||
app_manager_for_each_manifest(collectManifest, &collected);
|
|
||||||
std::ranges::sort(collected, [](const ::AppManifest& a, const ::AppManifest& b) {
|
|
||||||
return strcmp(a.name, b.name) < 0;
|
|
||||||
});
|
|
||||||
ctx->manifests = std::move(collected);
|
|
||||||
|
|
||||||
for (const auto& manifest: ctx->manifests) {
|
|
||||||
if (manifest.category == APP_CATEGORY_SETTINGS && (manifest.flags & APP_MANIFEST_FLAG_HIDDEN) == 0) {
|
|
||||||
createWidget(&manifest, list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t appMain(int argc, char* argv[]) {
|
int32_t appMain(int argc, char* argv[]) {
|
||||||
@ -158,7 +153,7 @@ extern const ::AppManifest manifest = {
|
|||||||
.category = APP_CATEGORY_SYSTEM,
|
.category = APP_CATEGORY_SYSTEM,
|
||||||
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
|
.location = { .type = APP_LOCATION_MEMORY, .location = reinterpret_cast<void*>(appMain) },
|
||||||
.flags = APP_MANIFEST_FLAG_HIDDEN,
|
.flags = APP_MANIFEST_FLAG_HIDDEN,
|
||||||
.stack = { .depth = 2400, .desired_memory_capability = 0 },
|
.stack = { .depth = 4000, .desired_memory_capability = 0 },
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
#include <tactility/device.h>
|
#include <tactility/device.h>
|
||||||
#include <tactility/drivers/display.h>
|
#include <tactility/drivers/display.h>
|
||||||
#include <tactility/module.h>
|
#include <tactility/module.h>
|
||||||
|
#include <lvgl/devices/indev.h>
|
||||||
#include <lvgl/module.h>
|
#include <lvgl/module.h>
|
||||||
#include <lvgl/widgets/toolbar.h>
|
#include <lvgl/widgets/toolbar.h>
|
||||||
#include <lvgl_window_manager/window_manager.h>
|
#include <lvgl_window_manager/window_manager.h>
|
||||||
@ -46,8 +47,11 @@ void createWidgets(lv_obj_t* root, void* userData) {
|
|||||||
lv_obj_set_style_pad_all(root, 0, LV_STATE_DEFAULT);
|
lv_obj_set_style_pad_all(root, 0, LV_STATE_DEFAULT);
|
||||||
lv_obj_set_style_pad_row(root, 0, LV_STATE_DEFAULT);
|
lv_obj_set_style_pad_row(root, 0, LV_STATE_DEFAULT);
|
||||||
|
|
||||||
|
// The toolbar only offers a touch close button, so it is left out to give the terminal more room
|
||||||
|
if (lvgl_indev_exists(LV_INDEV_TYPE_POINTER)) {
|
||||||
auto* toolbar = lvgl_toolbar_create(root, "Terminal");
|
auto* toolbar = lvgl_toolbar_create(root, "Terminal");
|
||||||
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onClosePressed, ctx);
|
lvgl_toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, onClosePressed, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
auto* canvas = lv_canvas_create(root);
|
auto* canvas = lv_canvas_create(root);
|
||||||
lv_obj_set_width(canvas, LV_PCT(100));
|
lv_obj_set_width(canvas, LV_PCT(100));
|
||||||
|
|||||||
@ -182,6 +182,31 @@ static void keyboard_read_cb(lv_indev_t* indev, lv_indev_data_t* data) {
|
|||||||
data->state = LV_INDEV_STATE_RELEASED;
|
data->state = LV_INDEV_STATE_RELEASED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The pointer indev only exists while a mouse is connected, so apps can tell whether pointer input is available.
|
||||||
|
static void addMouseIndev(UsbHidInputCtx* ctx) {
|
||||||
|
if (ctx->mouse_indev != nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx->mouse_indev = lv_indev_create();
|
||||||
|
lv_indev_set_type(ctx->mouse_indev, LV_INDEV_TYPE_POINTER);
|
||||||
|
lv_indev_set_read_cb(ctx->mouse_indev, mouse_read_cb);
|
||||||
|
lv_indev_set_user_data(ctx->mouse_indev, ctx);
|
||||||
|
if (ctx->mouse_cursor != nullptr) {
|
||||||
|
lv_indev_set_cursor(ctx->mouse_indev, ctx->mouse_cursor);
|
||||||
|
lv_obj_remove_flag(ctx->mouse_cursor, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void removeMouseIndev(UsbHidInputCtx* ctx) {
|
||||||
|
if (ctx->mouse_indev != nullptr) {
|
||||||
|
lv_indev_delete(ctx->mouse_indev);
|
||||||
|
ctx->mouse_indev = nullptr;
|
||||||
|
}
|
||||||
|
if (ctx->mouse_cursor != nullptr) {
|
||||||
|
lv_obj_add_flag(ctx->mouse_cursor, LV_OBJ_FLAG_HIDDEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void usbHidInputTask(void* arg) {
|
static void usbHidInputTask(void* arg) {
|
||||||
auto* ctx = static_cast<UsbHidInputCtx*>(arg);
|
auto* ctx = static_cast<UsbHidInputCtx*>(arg);
|
||||||
LOG_I(TAG, "started");
|
LOG_I(TAG, "started");
|
||||||
@ -192,14 +217,6 @@ static void usbHidInputTask(void* arg) {
|
|||||||
// gets disabled mid-read.
|
// gets disabled mid-read.
|
||||||
lvgl_lock();
|
lvgl_lock();
|
||||||
|
|
||||||
ctx->mouse_indev = lv_indev_create();
|
|
||||||
lv_indev_set_type(ctx->mouse_indev, LV_INDEV_TYPE_POINTER);
|
|
||||||
lv_indev_set_read_cb(ctx->mouse_indev, mouse_read_cb);
|
|
||||||
lv_indev_set_user_data(ctx->mouse_indev, ctx);
|
|
||||||
if (ctx->mouse_cursor != nullptr) {
|
|
||||||
lv_indev_set_cursor(ctx->mouse_indev, ctx->mouse_cursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx->kb_indev = lv_indev_create();
|
ctx->kb_indev = lv_indev_create();
|
||||||
lv_indev_set_type(ctx->kb_indev, LV_INDEV_TYPE_KEYPAD);
|
lv_indev_set_type(ctx->kb_indev, LV_INDEV_TYPE_KEYPAD);
|
||||||
lv_indev_set_read_cb(ctx->kb_indev, keyboard_read_cb);
|
lv_indev_set_read_cb(ctx->kb_indev, keyboard_read_cb);
|
||||||
@ -282,17 +299,17 @@ static void usbHidInputTask(void* arg) {
|
|||||||
break;
|
break;
|
||||||
case USB_HID_EVENT_MOUSE_CONNECTED:
|
case USB_HID_EVENT_MOUSE_CONNECTED:
|
||||||
ctx->mouse_connected = true;
|
ctx->mouse_connected = true;
|
||||||
if (ctx->mouse_cursor && lvgl_try_lock(pdMS_TO_TICKS(200))) {
|
// Blocking lock: a skipped connect would leave the mouse without an indev
|
||||||
lv_obj_remove_flag(ctx->mouse_cursor, LV_OBJ_FLAG_HIDDEN);
|
lvgl_lock();
|
||||||
|
addMouseIndev(ctx);
|
||||||
lvgl_unlock();
|
lvgl_unlock();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case USB_HID_EVENT_MOUSE_DISCONNECTED:
|
case USB_HID_EVENT_MOUSE_DISCONNECTED:
|
||||||
ctx->mouse_connected = false;
|
ctx->mouse_connected = false;
|
||||||
if (ctx->mouse_cursor && lvgl_try_lock(pdMS_TO_TICKS(200))) {
|
ctx->mouse_btn1.store(false);
|
||||||
lv_obj_add_flag(ctx->mouse_cursor, LV_OBJ_FLAG_HIDDEN);
|
lvgl_lock();
|
||||||
|
removeMouseIndev(ctx);
|
||||||
lvgl_unlock();
|
lvgl_unlock();
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -78,7 +78,10 @@ extern const struct DeviceType USB_HOST_HID_TYPE;
|
|||||||
/** Returns true if any HID device (keyboard or mouse) is currently connected. */
|
/** Returns true if any HID device (keyboard or mouse) is currently connected. */
|
||||||
bool usb_host_hid_is_connected(struct Device* device);
|
bool usb_host_hid_is_connected(struct Device* device);
|
||||||
|
|
||||||
/** Subscribe a FreeRTOS queue to receive UsbHidEvent items from the HID driver. */
|
/**
|
||||||
|
* Subscribe a FreeRTOS queue to receive UsbHidEvent items from the HID driver.
|
||||||
|
* A new subscriber immediately receives a *_CONNECTED event for each device that is already connected.
|
||||||
|
*/
|
||||||
bool usb_host_hid_subscribe(struct Device* device, UsbHidQueueHandle event_queue);
|
bool usb_host_hid_subscribe(struct Device* device, UsbHidQueueHandle event_queue);
|
||||||
|
|
||||||
/** Unsubscribe a previously subscribed queue. */
|
/** Unsubscribe a previously subscribed queue. */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user