mirror of
https://github.com/ratspeak/ratdeck.git
synced 2026-05-15 03:45:06 +00:00
07025bfa23
- 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
216 lines
6.1 KiB
C++
216 lines
6.1 KiB
C++
#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();
|
|
}
|
|
|
|
_currentScreen = screen;
|
|
|
|
if (_currentScreen) {
|
|
_currentScreen->onEnter();
|
|
_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) {
|
|
_overlay->markDirty();
|
|
}
|
|
forceRedraw();
|
|
}
|
|
|
|
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());
|
|
bool needOverlayRedraw = (_overlay && _overlay->isDirty());
|
|
|
|
if (!needStatusRedraw && !needTabRedraw && !needContentRedraw && !needOverlayRedraw)
|
|
return;
|
|
|
|
if (!_bootMode && needStatusRedraw) {
|
|
_statusBar.draw(*_gfx);
|
|
_statusBar.clearDirty();
|
|
}
|
|
|
|
if (needContentRedraw) {
|
|
if (_bootMode) {
|
|
_gfx->setClipRect(0, 0, Theme::SCREEN_W, Theme::SCREEN_H);
|
|
} else {
|
|
_gfx->setClipRect(0, Theme::CONTENT_Y, Theme::CONTENT_W, Theme::CONTENT_H);
|
|
}
|
|
_gfx->fillRect(0, _bootMode ? 0 : Theme::CONTENT_Y,
|
|
Theme::CONTENT_W, _bootMode ? Theme::SCREEN_H : Theme::CONTENT_H,
|
|
Theme::BG);
|
|
_currentScreen->draw(*_gfx);
|
|
_currentScreen->clearDirty();
|
|
_gfx->clearClipRect();
|
|
}
|
|
|
|
if (needOverlayRedraw && _overlay) {
|
|
_gfx->setClipRect(0, Theme::CONTENT_Y, Theme::CONTENT_W, Theme::CONTENT_H);
|
|
_overlay->draw(*_gfx);
|
|
_overlay->clearDirty();
|
|
_gfx->clearClipRect();
|
|
}
|
|
|
|
if (!_bootMode && needTabRedraw) {
|
|
_tabBar.draw(*_gfx);
|
|
_tabBar.clearDirty();
|
|
}
|
|
}
|
|
|
|
void UIManager::forceRedraw() {
|
|
_statusBar.markDirty();
|
|
_tabBar.markDirty();
|
|
if (_currentScreen) _currentScreen->markDirty();
|
|
if (_overlay) _overlay->markDirty();
|
|
}
|
|
|
|
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;
|
|
}
|