v1.4.0: LVGL UI, async radio TX, live TCP management, input fixes

- Migrate all screens to LVGL v8.4 widget system
- Non-blocking radio TX (async endPacket via LoRaInterface)
- Live TCP server switching with transient node cleanup
- Fix UI freeze during radio transmit
- Trackball long-press delete, deferred click with debounce
- Pin microReticulum to 392363c, fix list_directory API
- Fix CI build: portable include path, remove hardcoded local path
This commit is contained in:
DeFiDude
2026-03-07 13:00:59 -07:00
parent 9b7980665c
commit 07025bfa23
51 changed files with 3895 additions and 309 deletions
+113 -1
View File
@@ -1,14 +1,59 @@
#include "UIManager.h"
#include "Theme.h"
#include "LvTheme.h"
#include "hal/Display.h"
// --- LvScreen base ---
void LvScreen::destroyUI() {
// Content is owned by UIManager's _lvContent — just clear our pointers.
// The UIManager calls lv_obj_clean(_lvContent) before creating the next screen.
_screen = nullptr;
}
// --- UIManager ---
void UIManager::begin(LGFX_TDeck* gfx) {
_gfx = gfx;
_statusBar.setGfx(gfx);
_tabBar.setGfx(gfx);
// Initialize LVGL theme and create persistent UI structure
lv_obj_t* scr = lv_scr_act();
LvTheme::init(lv_disp_get_default());
// Apply screen background style
lv_obj_add_style(scr, LvTheme::styleScreen(), 0);
// Create LVGL status bar (top)
_lvStatusBar.create(scr);
// Create LVGL tab bar (bottom)
_lvTabBar.create(scr);
// Create content area between status bar and tab bar
_lvContent = lv_obj_create(scr);
lv_obj_set_pos(_lvContent, 0, Theme::STATUS_BAR_H);
lv_obj_set_size(_lvContent, Theme::CONTENT_W, Theme::CONTENT_H);
lv_obj_set_style_bg_color(_lvContent, lv_color_hex(Theme::BG), 0);
lv_obj_set_style_bg_opa(_lvContent, LV_OPA_COVER, 0);
lv_obj_set_style_border_width(_lvContent, 0, 0);
lv_obj_set_style_pad_all(_lvContent, 0, 0);
lv_obj_set_style_radius(_lvContent, 0, 0);
_lvglActive = true;
Serial.println("[UI] LVGL UI structure created");
}
void UIManager::setScreen(Screen* screen) {
// When setting a legacy screen, hide LVGL content and use legacy rendering
if (_currentLvScreen) {
_currentLvScreen->onExit();
_currentLvScreen->destroyUI();
_currentLvScreen = nullptr;
}
if (_currentScreen) {
_currentScreen->onExit();
}
@@ -20,9 +65,48 @@ void UIManager::setScreen(Screen* screen) {
_currentScreen->markDirty();
}
// Hide LVGL layers for legacy rendering
if (_lvglActive) {
lv_obj_add_flag(_lvStatusBar.obj(), LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(_lvTabBar.obj(), LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(_lvContent, LV_OBJ_FLAG_HIDDEN);
}
forceRedraw();
}
void UIManager::setLvScreen(LvScreen* screen) {
// Transition from legacy screen
if (_currentScreen) {
_currentScreen->onExit();
_currentScreen = nullptr;
}
// Transition from previous LVGL screen
if (_currentLvScreen) {
_currentLvScreen->onExit();
_currentLvScreen->destroyUI();
}
_currentLvScreen = screen;
// Show LVGL layers
if (_lvglActive) {
if (!_bootMode) {
lv_obj_clear_flag(_lvStatusBar.obj(), LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(_lvTabBar.obj(), LV_OBJ_FLAG_HIDDEN);
}
lv_obj_clear_flag(_lvContent, LV_OBJ_FLAG_HIDDEN);
}
if (_currentLvScreen) {
// Clean content area
lv_obj_clean(_lvContent);
_currentLvScreen->createUI(_lvContent);
_currentLvScreen->onEnter();
}
}
void UIManager::setOverlay(Screen* overlay) {
_overlay = overlay;
if (_overlay) {
@@ -33,17 +117,36 @@ void UIManager::setOverlay(Screen* overlay) {
void UIManager::setBootMode(bool boot) {
_bootMode = boot;
if (_lvglActive) {
if (boot) {
lv_obj_add_flag(_lvStatusBar.obj(), LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(_lvTabBar.obj(), LV_OBJ_FLAG_HIDDEN);
// In boot mode, content area is full screen
lv_obj_set_pos(_lvContent, 0, 0);
lv_obj_set_size(_lvContent, Theme::SCREEN_W, Theme::SCREEN_H);
} else {
lv_obj_clear_flag(_lvStatusBar.obj(), LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(_lvTabBar.obj(), LV_OBJ_FLAG_HIDDEN);
lv_obj_set_pos(_lvContent, 0, Theme::STATUS_BAR_H);
lv_obj_set_size(_lvContent, Theme::CONTENT_W, Theme::CONTENT_H);
}
}
forceRedraw();
}
void UIManager::update() {
_statusBar.update();
_lvStatusBar.update();
if (_currentScreen) _currentScreen->update();
if (_currentLvScreen) _currentLvScreen->refreshUI();
}
void UIManager::render() {
if (!_gfx) return;
// If an LVGL screen is active, don't do legacy rendering
if (_currentLvScreen) return;
bool needStatusRedraw = _statusBar.isDirty();
bool needTabRedraw = _tabBar.isDirty();
bool needContentRedraw = (_currentScreen && _currentScreen->isDirty());
@@ -59,7 +162,6 @@ void UIManager::render() {
if (needContentRedraw) {
if (_bootMode) {
// In boot mode, content area is full screen
_gfx->setClipRect(0, 0, Theme::SCREEN_W, Theme::SCREEN_H);
} else {
_gfx->setClipRect(0, Theme::CONTENT_Y, Theme::CONTENT_W, Theme::CONTENT_H);
@@ -96,8 +198,18 @@ bool UIManager::handleKey(const KeyEvent& event) {
if (_overlay) {
return _overlay->handleKey(event);
}
if (_currentLvScreen) {
return _currentLvScreen->handleKey(event);
}
if (_currentScreen) {
return _currentScreen->handleKey(event);
}
return false;
}
bool UIManager::handleLongPress() {
if (_currentLvScreen) {
return _currentLvScreen->handleLongPress();
}
return false;
}