Compare commits
4 Commits
05a65988dd
...
setup-hard
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d642c7d12 | ||
|
|
4cfa7333f1 | ||
|
|
48c6b97062 | ||
|
|
c5d6cfcd22 |
@@ -13,22 +13,31 @@
|
|||||||
#define MINIMUM_POWER_ON_DELAY_MS 100
|
#define MINIMUM_POWER_ON_DELAY_MS 100
|
||||||
#define PARTIAL_REFRESH_THRESHOLD 5 // Full refresh every N partial refreshes
|
#define PARTIAL_REFRESH_THRESHOLD 5 // Full refresh every N partial refreshes
|
||||||
|
|
||||||
static uint8_t* DRAW_BUFFER; // 1 bit per pixel
|
// Static flag to prevent multiple instances (these buffers are large, only one display allowed)
|
||||||
static uint8_t* OLD_DRAW_BUFFER; // 1 bit per pixel
|
static bool display_instance_exists = false;
|
||||||
static uint8_t* black_data;
|
|
||||||
static uint8_t* white_data;
|
|
||||||
|
|
||||||
EInkDisplayHandler::EInkDisplayHandler() {
|
EInkDisplayHandler::EInkDisplayHandler() {
|
||||||
black_data = static_cast<uint8_t*>(heap_caps_malloc(DISPLAY_BUFFER_SIZE, MALLOC_CAP_SPIRAM));
|
if (display_instance_exists) {
|
||||||
white_data = static_cast<uint8_t*>(heap_caps_malloc(DISPLAY_BUFFER_SIZE, MALLOC_CAP_SPIRAM));
|
ESP_LOGE(TAG, "Only one EInkDisplayHandler instance allowed!");
|
||||||
DRAW_BUFFER = static_cast<uint8_t*>(heap_caps_malloc(DISPLAY_BUFFER_SIZE, MALLOC_CAP_SPIRAM));
|
return;
|
||||||
OLD_DRAW_BUFFER = static_cast<uint8_t*>(heap_caps_malloc(DISPLAY_BUFFER_SIZE, MALLOC_CAP_SPIRAM));
|
}
|
||||||
memset(black_data, 0xFF, DISPLAY_BUFFER_SIZE); // eink uses 1 for black
|
display_instance_exists = true;
|
||||||
memset(white_data, 0x00, DISPLAY_BUFFER_SIZE);
|
|
||||||
memset(DRAW_BUFFER, 0x00, DISPLAY_BUFFER_SIZE); // start with all white (0 = white in e-ink)
|
black_data_ = static_cast<uint8_t*>(heap_caps_malloc(DISPLAY_BUFFER_SIZE, MALLOC_CAP_SPIRAM));
|
||||||
memset(OLD_DRAW_BUFFER, 0x00, DISPLAY_BUFFER_SIZE); // start with all white (0 = white in e-ink)
|
white_data_ = static_cast<uint8_t*>(heap_caps_malloc(DISPLAY_BUFFER_SIZE, MALLOC_CAP_SPIRAM));
|
||||||
draw_buffer_ = DRAW_BUFFER;
|
draw_buffer_ = static_cast<uint8_t*>(heap_caps_malloc(DISPLAY_BUFFER_SIZE, MALLOC_CAP_SPIRAM));
|
||||||
old_buffer_ = OLD_DRAW_BUFFER;
|
old_buffer_ = static_cast<uint8_t*>(heap_caps_malloc(DISPLAY_BUFFER_SIZE, MALLOC_CAP_SPIRAM));
|
||||||
|
|
||||||
|
// Check for allocation failures
|
||||||
|
if (!black_data_ || !white_data_ || !draw_buffer_ || !old_buffer_) {
|
||||||
|
ESP_LOGE(TAG, "Failed to allocate display buffers!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(black_data_, 0xFF, DISPLAY_BUFFER_SIZE); // eink uses 1 for black
|
||||||
|
memset(white_data_, 0x00, DISPLAY_BUFFER_SIZE);
|
||||||
|
memset(draw_buffer_, 0x00, DISPLAY_BUFFER_SIZE); // start with all white (0 = white in e-ink)
|
||||||
|
memset(old_buffer_, 0x00, DISPLAY_BUFFER_SIZE); // start with all white (0 = white in e-ink)
|
||||||
|
|
||||||
refresh_mutex_ = xSemaphoreCreateMutex();
|
refresh_mutex_ = xSemaphoreCreateMutex();
|
||||||
if (refresh_mutex_ == nullptr) {
|
if (refresh_mutex_ == nullptr) {
|
||||||
@@ -46,18 +55,23 @@ EInkDisplayHandler::~EInkDisplayHandler() {
|
|||||||
if (tp_io_handle_ != nullptr) {
|
if (tp_io_handle_ != nullptr) {
|
||||||
esp_lcd_panel_io_del(tp_io_handle_);
|
esp_lcd_panel_io_del(tp_io_handle_);
|
||||||
}
|
}
|
||||||
if (black_data != nullptr) {
|
if (black_data_ != nullptr) {
|
||||||
heap_caps_free(black_data);
|
heap_caps_free(black_data_);
|
||||||
|
black_data_ = nullptr;
|
||||||
}
|
}
|
||||||
if (white_data != nullptr) {
|
if (white_data_ != nullptr) {
|
||||||
heap_caps_free(white_data);
|
heap_caps_free(white_data_);
|
||||||
|
white_data_ = nullptr;
|
||||||
}
|
}
|
||||||
if (DRAW_BUFFER != nullptr) {
|
if (draw_buffer_ != nullptr) {
|
||||||
heap_caps_free(DRAW_BUFFER);
|
heap_caps_free(draw_buffer_);
|
||||||
|
draw_buffer_ = nullptr;
|
||||||
}
|
}
|
||||||
if (OLD_DRAW_BUFFER != nullptr) {
|
if (old_buffer_ != nullptr) {
|
||||||
heap_caps_free(OLD_DRAW_BUFFER);
|
heap_caps_free(old_buffer_);
|
||||||
|
old_buffer_ = nullptr;
|
||||||
}
|
}
|
||||||
|
display_instance_exists = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t EInkDisplayHandler::deep_sleep_display(void) {
|
esp_err_t EInkDisplayHandler::deep_sleep_display(void) {
|
||||||
@@ -185,7 +199,7 @@ esp_err_t EInkDisplayHandler::full_write(const uint8_t* framebuffer, const bool
|
|||||||
ESP_LOGE(TAG, "Failed to send old data command: %s", esp_err_to_name(err));
|
ESP_LOGE(TAG, "Failed to send old data command: %s", esp_err_to_name(err));
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
err = epd_handler_.transfer_spi_data(white_basemap ? black_data : white_data, DISPLAY_BUFFER_SIZE, transaction_guard.transaction_id()); // Send all white data (0xFF)
|
err = epd_handler_.transfer_spi_data(white_basemap ? black_data_ : white_data_, DISPLAY_BUFFER_SIZE, transaction_guard.transaction_id()); // Send all white data (0xFF)
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Failed to send all white data for old data: %s", esp_err_to_name(err));
|
ESP_LOGE(TAG, "Failed to send all white data for old data: %s", esp_err_to_name(err));
|
||||||
return err;
|
return err;
|
||||||
@@ -449,7 +463,7 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_fr
|
|||||||
esp_err_t EInkDisplayHandler::clear_display(void) {
|
esp_err_t EInkDisplayHandler::clear_display(void) {
|
||||||
ESP_LOGV(TAG, "Clearing display to all white...");
|
ESP_LOGV(TAG, "Clearing display to all white...");
|
||||||
|
|
||||||
esp_err_t err = full_write(white_data, false);
|
esp_err_t err = full_write(white_data_, false);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Failed to clear display: %s", esp_err_to_name(err));
|
ESP_LOGE(TAG, "Failed to clear display: %s", esp_err_to_name(err));
|
||||||
return err;
|
return err;
|
||||||
|
|||||||
@@ -90,9 +90,11 @@ private:
|
|||||||
esp_lcd_panel_io_handle_t tp_io_handle_ = nullptr;
|
esp_lcd_panel_io_handle_t tp_io_handle_ = nullptr;
|
||||||
esp_lcd_touch_handle_t tp_handle_ = nullptr;
|
esp_lcd_touch_handle_t tp_handle_ = nullptr;
|
||||||
|
|
||||||
// this buffer reflects the current display state (1=black, 0=white)
|
// Display buffers (1=black, 0=white)
|
||||||
uint8_t* draw_buffer_ = nullptr;
|
uint8_t* draw_buffer_ = nullptr;
|
||||||
uint8_t* old_buffer_ = nullptr;
|
uint8_t* old_buffer_ = nullptr;
|
||||||
|
uint8_t* black_data_ = nullptr; // All 0xFF (black pattern)
|
||||||
|
uint8_t* white_data_ = nullptr; // All 0x00 (white pattern)
|
||||||
RefreshArea refresh_area_ = { 0, 0, 0, 0 };
|
RefreshArea refresh_area_ = { 0, 0, 0, 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "common/constants.h"
|
#include "common/constants.h"
|
||||||
#include "esp_lcd_touch_gt911.h"
|
#include "esp_lcd_touch_gt911.h"
|
||||||
#include <driver/i2c.h>
|
#include <driver/i2c.h>
|
||||||
|
#include <esp_cache.h>
|
||||||
#define TAG "EPDHandler"
|
#define TAG "EPDHandler"
|
||||||
|
|
||||||
#define BUSY_ACTIVE_LEVEL 0 // BUSY pin is active low
|
#define BUSY_ACTIVE_LEVEL 0 // BUSY pin is active low
|
||||||
@@ -213,10 +214,29 @@ esp_err_t EPDHandler::transfer_spi_data(const uint8_t* data, const size_t& lengt
|
|||||||
size_t remaining = length;
|
size_t remaining = length;
|
||||||
gpio_set_level(PIN_DC, 1); // Data mode
|
gpio_set_level(PIN_DC, 1); // Data mode
|
||||||
|
|
||||||
// Allocate a temporary buffer for inverted data (only if inverted)
|
// Check if data is in PSRAM (needs cache sync and staging buffer)
|
||||||
|
bool data_in_psram = (esp_ptr_external_ram((void*)data) != 0);
|
||||||
|
|
||||||
|
if (data_in_psram) {
|
||||||
|
// Flush cache to ensure DMA sees the latest data in PSRAM
|
||||||
|
esp_err_t cache_err = esp_cache_msync((void*)data, length, ESP_CACHE_MSYNC_FLAG_DIR_C2M);
|
||||||
|
if (cache_err != ESP_OK) {
|
||||||
|
ESP_LOGW(TAG, "Cache sync failed: %s", esp_err_to_name(cache_err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use staging buffer in internal DMA-capable RAM
|
||||||
|
// PSRAM cannot be allocated with MALLOC_CAP_DMA, so we always use a staging buffer
|
||||||
|
uint8_t* staging_buffer = (uint8_t*)heap_caps_malloc(DMA_TRANSFER_CHUNK_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
|
||||||
|
if (staging_buffer == nullptr) {
|
||||||
|
ESP_LOGE(TAG, "Failed to allocate DMA staging buffer");
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Additional buffer needed only for inverted data
|
||||||
uint8_t* temp_transfer_buffer = nullptr;
|
uint8_t* temp_transfer_buffer = nullptr;
|
||||||
if (inverted) {
|
if (inverted) {
|
||||||
temp_transfer_buffer = (uint8_t*)heap_caps_malloc(DMA_TRANSFER_CHUNK_SIZE, MALLOC_CAP_DMA);
|
temp_transfer_buffer = (uint8_t*)heap_caps_malloc(DMA_TRANSFER_CHUNK_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
|
||||||
if (temp_transfer_buffer == nullptr) {
|
if (temp_transfer_buffer == nullptr) {
|
||||||
ESP_LOGE(TAG, "Failed to allocate memory for inverted data transfer buffer");
|
ESP_LOGE(TAG, "Failed to allocate memory for inverted data transfer buffer");
|
||||||
ESP_LOGI(TAG, "Current free heap size: %u bytes", esp_get_free_heap_size());
|
ESP_LOGI(TAG, "Current free heap size: %u bytes", esp_get_free_heap_size());
|
||||||
@@ -229,31 +249,27 @@ esp_err_t EPDHandler::transfer_spi_data(const uint8_t* data, const size_t& lengt
|
|||||||
while (remaining > 0) {
|
while (remaining > 0) {
|
||||||
size_t transfer_size = (remaining < DMA_TRANSFER_CHUNK_SIZE) ? remaining : DMA_TRANSFER_CHUNK_SIZE;
|
size_t transfer_size = (remaining < DMA_TRANSFER_CHUNK_SIZE) ? remaining : DMA_TRANSFER_CHUNK_SIZE;
|
||||||
|
|
||||||
const uint8_t* transfer_buffer = nullptr;
|
// Copy data to DMA-capable staging buffer
|
||||||
|
// Required because PSRAM cannot be allocated with MALLOC_CAP_DMA
|
||||||
if (inverted) {
|
if (inverted) {
|
||||||
// Invert only the current chunk into the temporary buffer
|
// Invert while copying
|
||||||
for (size_t i = 0; i < transfer_size; ++i) {
|
for (size_t i = 0; i < transfer_size; ++i) {
|
||||||
temp_transfer_buffer[i] = ~data[offset + i];
|
staging_buffer[i] = ~data[offset + i];
|
||||||
}
|
}
|
||||||
transfer_buffer = temp_transfer_buffer;
|
|
||||||
} else {
|
} else {
|
||||||
transfer_buffer = data + offset;
|
// Straight copy from PSRAM to internal DMA buffer
|
||||||
|
memcpy(staging_buffer, data + offset, transfer_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
spi_transaction_t t = {};
|
spi_transaction_t t = {};
|
||||||
t.length = transfer_size * 8; // Length in bits
|
t.length = transfer_size * 8; // Length in bits
|
||||||
t.tx_buffer = transfer_buffer;
|
t.tx_buffer = staging_buffer;
|
||||||
|
|
||||||
esp_err_t ret = spi_device_polling_transmit(spi_, &t);
|
esp_err_t ret = spi_device_polling_transmit(spi_, &t);
|
||||||
if (ret != ESP_OK) {
|
if (ret != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "Failed to send SPI chunk at offset %zu: %s", offset, esp_err_to_name(ret));
|
ESP_LOGE(TAG, "Failed to send SPI chunk at offset %zu: %s", offset, esp_err_to_name(ret));
|
||||||
if (ret == ESP_ERR_NO_MEM) {
|
heap_caps_free(staging_buffer);
|
||||||
ESP_LOGE(TAG, "Current free heap size: %u bytes", esp_get_free_heap_size());
|
|
||||||
ESP_LOGE(TAG, "Current free DMA-capable memory size: %u bytes",
|
|
||||||
heap_caps_get_free_size(MALLOC_CAP_DMA));
|
|
||||||
}
|
|
||||||
if (inverted && temp_transfer_buffer != nullptr) {
|
if (inverted && temp_transfer_buffer != nullptr) {
|
||||||
// Free the temporary inverted buffer
|
|
||||||
heap_caps_free(temp_transfer_buffer);
|
heap_caps_free(temp_transfer_buffer);
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
@@ -269,8 +285,8 @@ esp_err_t EPDHandler::transfer_spi_data(const uint8_t* data, const size_t& lengt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
heap_caps_free(staging_buffer);
|
||||||
if (inverted && temp_transfer_buffer != nullptr) {
|
if (inverted && temp_transfer_buffer != nullptr) {
|
||||||
// Free the temporary inverted buffer
|
|
||||||
heap_caps_free(temp_transfer_buffer);
|
heap_caps_free(temp_transfer_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ esp_err_t NVSStorageHandler::process_all(KeyValueProcessor processor, void* arg)
|
|||||||
// call the processor with the key and value
|
// call the processor with the key and value
|
||||||
std::string key_str = info.key;
|
std::string key_str = info.key;
|
||||||
processor(arg, key_str, this->get(key_str));
|
processor(arg, key_str, this->get(key_str));
|
||||||
|
nvs_close(temp_handle);
|
||||||
}
|
}
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
}
|
}
|
||||||
@@ -156,6 +157,7 @@ esp_err_t NVSStorageHandler::process_filtered(const std::string& key_prefix, Key
|
|||||||
}
|
}
|
||||||
// call the processor with the key and value
|
// call the processor with the key and value
|
||||||
processor(arg, std::string(info.key), this->get(std::string(info.key)));
|
processor(arg, std::string(info.key), this->get(std::string(info.key)));
|
||||||
|
nvs_close(temp_handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
@@ -186,6 +188,7 @@ esp_err_t NVSStorageHandler::process_filtered(FilterFunc filter_func, KeyValuePr
|
|||||||
}
|
}
|
||||||
// call the processor with the key and value
|
// call the processor with the key and value
|
||||||
processor(arg, key_str, this->get(key_str));
|
processor(arg, key_str, this->get(key_str));
|
||||||
|
nvs_close(temp_handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ESP_OK;
|
return ESP_OK;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "string.h"
|
#include "string.h"
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
esp_err_t http_event_handler(esp_http_client_event_t *evt) {
|
esp_err_t http_event_handler(esp_http_client_event_t *evt) {
|
||||||
HttpHandler* handler = static_cast<HttpHandler*>(evt->user_data);
|
HttpHandler* handler = static_cast<HttpHandler*>(evt->user_data);
|
||||||
@@ -10,7 +11,14 @@ esp_err_t http_event_handler(esp_http_client_event_t *evt) {
|
|||||||
switch (evt->event_id) {
|
switch (evt->event_id) {
|
||||||
case HTTP_EVENT_ON_DATA:
|
case HTTP_EVENT_ON_DATA:
|
||||||
if (handler && evt->data_len > 0) {
|
if (handler && evt->data_len > 0) {
|
||||||
char* new_buffer = new char[handler->response_size + evt->data_len + 1];
|
// Pre-allocate with some extra capacity to reduce reallocations
|
||||||
|
size_t new_capacity = handler->response_size + evt->data_len + 1;
|
||||||
|
// Double capacity if we already have data, to amortize reallocation cost
|
||||||
|
if (handler->response_size > 0) {
|
||||||
|
new_capacity = std::max(new_capacity, (handler->response_size * 2) + 1);
|
||||||
|
new_capacity = std::min(new_capacity, (size_t)65536); // Cap at 64KB
|
||||||
|
}
|
||||||
|
char* new_buffer = new char[new_capacity];
|
||||||
if (handler->response_buffer && handler->response_size > 0) {
|
if (handler->response_buffer && handler->response_size > 0) {
|
||||||
memcpy(new_buffer, handler->response_buffer, handler->response_size);
|
memcpy(new_buffer, handler->response_buffer, handler->response_size);
|
||||||
delete[] handler->response_buffer;
|
delete[] handler->response_buffer;
|
||||||
@@ -19,6 +27,7 @@ esp_err_t http_event_handler(esp_http_client_event_t *evt) {
|
|||||||
handler->response_size += evt->data_len;
|
handler->response_size += evt->data_len;
|
||||||
new_buffer[handler->response_size] = '\0';
|
new_buffer[handler->response_size] = '\0';
|
||||||
handler->response_buffer = new_buffer;
|
handler->response_buffer = new_buffer;
|
||||||
|
handler->response_capacity = new_capacity;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -55,4 +55,5 @@ private:
|
|||||||
WifiHandler* wifiHandler;
|
WifiHandler* wifiHandler;
|
||||||
char* response_buffer;
|
char* response_buffer;
|
||||||
size_t response_size;
|
size_t response_size;
|
||||||
|
size_t response_capacity = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "ui/ui_handler.h"
|
#include "ui/ui_handler.h"
|
||||||
#include "ui/apps/registry.h"
|
#include "ui/apps/registry.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#define TAG "UIHandler"
|
#define TAG "UIHandler"
|
||||||
|
|
||||||
@@ -11,6 +12,11 @@ struct AppClickUserData {
|
|||||||
|
|
||||||
UIHandler::~UIHandler() {
|
UIHandler::~UIHandler() {
|
||||||
deinit();
|
deinit();
|
||||||
|
// Clean up all allocated AppClickUserData
|
||||||
|
for (void* data : app_click_user_data_) {
|
||||||
|
delete static_cast<AppClickUserData*>(data);
|
||||||
|
}
|
||||||
|
app_click_user_data_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t UIHandler::init(void) {
|
esp_err_t UIHandler::init(void) {
|
||||||
@@ -267,6 +273,10 @@ esp_err_t UIHandler::create_main_screen_(lv_obj_t* parent) {
|
|||||||
// Center the icon container
|
// Center the icon container
|
||||||
lv_obj_center(app_icon_container);
|
lv_obj_center(app_icon_container);
|
||||||
|
|
||||||
|
// Create and track user data for the callback
|
||||||
|
auto* click_data = new AppClickUserData { this, name };
|
||||||
|
app_click_user_data_.push_back(click_data);
|
||||||
|
|
||||||
// Register click event to switch to the app
|
// Register click event to switch to the app
|
||||||
lv_obj_add_event_cb(app_icon_container,
|
lv_obj_add_event_cb(app_icon_container,
|
||||||
[](lv_event_t* e) {
|
[](lv_event_t* e) {
|
||||||
@@ -282,7 +292,7 @@ esp_err_t UIHandler::create_main_screen_(lv_obj_t* parent) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
LV_EVENT_CLICKED,
|
LV_EVENT_CLICKED,
|
||||||
new AppClickUserData { this, name }
|
click_data
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "ui/interaction_handler.h"
|
#include "ui/interaction_handler.h"
|
||||||
#include "lvgl.h"
|
#include "lvgl.h"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief UI Handler - manages app lifecycle and rendering
|
* @brief UI Handler - manages app lifecycle and rendering
|
||||||
@@ -115,4 +116,7 @@ private:
|
|||||||
lv_obj_t* main_screen_ = nullptr; ///< Root screen
|
lv_obj_t* main_screen_ = nullptr; ///< Root screen
|
||||||
RootLayout root_layout_; ///< Main screen layout manager
|
RootLayout root_layout_; ///< Main screen layout manager
|
||||||
AppDescriptor* active_descriptor_ = nullptr; ///< Currently active app descriptor (managed by AppRegistry)
|
AppDescriptor* active_descriptor_ = nullptr; ///< Currently active app descriptor (managed by AppRegistry)
|
||||||
|
|
||||||
|
// Track allocated user data for cleanup
|
||||||
|
std::vector<void*> app_click_user_data_;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user