1. 花了几天时间基于lvgl8.3封装了lvgl_cpp,写了一些基本需要的控件,支持链式调用
还存在一点点bug,不难fix 2. 增加了中文字库,支持中文显示 3. 修复和优化了一些地方
This commit is contained in:
@@ -5,3 +5,4 @@ log
|
||||
managed_components
|
||||
.idea
|
||||
.venv
|
||||
.vscode
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "PetBaseClass.h"
|
||||
#include "PetDao.h"
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
|
||||
void testPetSystem() {
|
||||
std::cout << "Test point1" << std::endl;
|
||||
@@ -431,10 +432,273 @@ void createWebSocket() {
|
||||
std::thread websocket_thread = ThreadManager::createThread(websocket_config, websocket_task);
|
||||
websocket_thread.detach();
|
||||
}
|
||||
#include "LVGLRender.h"
|
||||
#include <lvgl.h>
|
||||
#define TAG "BIN_TEST"
|
||||
|
||||
/* 从 SD 卡读任意 .bin 到堆 → 直接显示 */
|
||||
void test_show_bin(const char* fileName)
|
||||
{
|
||||
ESP_LOGI(TAG, "=== fast bin test: %s ===", fileName);
|
||||
|
||||
/* 1. 读文件 */
|
||||
std::string path = "/sdcard/" + std::string(fileName);
|
||||
std::string data = SDFileManager::getInstance()->readFileSync(path.c_str());
|
||||
if (data.empty()) {
|
||||
ESP_LOGE(TAG, "read fail");
|
||||
return;
|
||||
}
|
||||
size_t sz = data.size();
|
||||
ESP_LOGI(TAG, "file size = %zu", sz);
|
||||
ESP_LOG_BUFFER_HEX(TAG, data.data(), 16); // 头 16 字节
|
||||
|
||||
/* 2. 拷到堆(LVGL 长期持有)*/
|
||||
void* buf = heap_caps_malloc(sz, MALLOC_CAP_8BIT);
|
||||
memcpy(buf, data.data(), sz);
|
||||
|
||||
/* 3. 离线量好的尺寸(先填 320×240 测试,不对再改)*/
|
||||
uint32_t w = 720;
|
||||
uint32_t h = 720;
|
||||
if (sz != w * h * 2) { // RGB565 每像素 2 字节
|
||||
ESP_LOGW(TAG, "size mismatch, expect %ld, got %zu", w * h * 2, sz);
|
||||
}
|
||||
|
||||
/* 4. 一次性描述符 */
|
||||
static lv_img_dsc_t dsc;
|
||||
dsc.data = static_cast<const uint8_t *>(buf);
|
||||
dsc.header.cf = LV_IMG_CF_TRUE_COLOR;
|
||||
dsc.header.w = w;
|
||||
dsc.header.h = h;
|
||||
dsc.data_size = sz;
|
||||
|
||||
/* 5. 直接显示(不经过 PNG 解码器)*/
|
||||
lv_obj_t* img = lv_img_create(lv_scr_act());
|
||||
lv_img_set_src(img, &dsc);
|
||||
lv_obj_center(img);
|
||||
|
||||
ESP_LOGI(TAG, "bin displayed, w=%ld h=%ld", w, h);
|
||||
}
|
||||
|
||||
#include "lvpp.h"
|
||||
LV_FONT_DECLARE(SiYuanHeiTiGoogleBan);
|
||||
// 使用全局智能指针管理主要对象
|
||||
static std::shared_ptr<lvgl_cpp::Screen> g_screen;
|
||||
using namespace lvgl_cpp;
|
||||
struct AppCalc : BaseApp {
|
||||
using BaseApp::BaseApp;
|
||||
void onShow() override {
|
||||
Label(*this).text("Calculator").center();
|
||||
}
|
||||
};
|
||||
struct AppMusic : BaseApp {
|
||||
using BaseApp::BaseApp;
|
||||
void onShow() override {
|
||||
btn_.emplace(*this);
|
||||
gif.emplace(*this);
|
||||
btn_->size(180, 60)
|
||||
.align(LV_ALIGN_CENTER, nullptr, 0, 80)
|
||||
.on(LV_EVENT_CLICKED, [&](lv_event_t*) {
|
||||
/* 居中 GIF,背景黑 */
|
||||
lv_obj_set_style_bg_color(this->raw(), lv_color_black(), 0);
|
||||
lv_obj_set_style_bg_opa(this->raw(), LV_OPA_COVER, 0);
|
||||
gif->src("small_-min.gif")
|
||||
.center();
|
||||
});
|
||||
|
||||
/* 按钮文字 */
|
||||
lv_obj_t* label = lv_label_create(btn_->raw());
|
||||
lv_obj_set_style_text_font(label, &SiYuanHeiTiGoogleBan, LV_PART_MAIN);
|
||||
lv_label_set_text(label, "Gif测试");
|
||||
lv_obj_center(label);
|
||||
}
|
||||
private:
|
||||
std::optional<lvgl_cpp::Button> btn_; // c++17 轻量 RAII
|
||||
std::optional<lvgl_cpp::Gif> gif;
|
||||
};
|
||||
|
||||
class ButtonApp : public BaseApp {
|
||||
public:
|
||||
using BaseApp::BaseApp; // 继承 exit 按钮机制
|
||||
|
||||
void onShow() override {
|
||||
/* 居中按钮:180×60 px,圆角,现代蓝 */
|
||||
btn_.emplace(*this);
|
||||
btn_->size(180, 60)
|
||||
.align(LV_ALIGN_CENTER, nullptr, 0, 80)
|
||||
.on(LV_EVENT_CLICKED, [](lv_event_t*) {
|
||||
lvgl_cpp::Toast::show("咕咕嘎嘎!",
|
||||
lvgl_cpp::Toast::Type::INFO,
|
||||
1000);
|
||||
});
|
||||
LV_FONT_DECLARE(SiYuanHeiTiGoogleBan);
|
||||
/* 按钮文字 */
|
||||
lv_obj_t* label = lv_label_create(btn_->raw());
|
||||
lv_obj_set_style_text_font(label, &SiYuanHeiTiGoogleBan, LV_PART_MAIN);
|
||||
lv_label_set_text(label, "按我");
|
||||
lv_obj_center(label);
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<lvgl_cpp::Button> btn_; // c++17 轻量 RAII
|
||||
};
|
||||
static std::shared_ptr<HomePage> g_home;
|
||||
static std::shared_ptr<AppMenu> g_menu;
|
||||
void Cpp_Hand() {
|
||||
// testMIC();
|
||||
// testPetSystem();
|
||||
SDFileManager::getInstance()->tryInitSDCard();
|
||||
LVGLRender::getInstance()->tryToInitRenderGif(); // 初始化lvgl驱动(包括任务循环)
|
||||
LVGLRender::setFps(60); // 设置lvgl刷新频率
|
||||
|
||||
/* LVGL 已经初始化,屏幕驱动已注册 */
|
||||
// auto scr = lvgl_cpp::Screen{}; // RAII,离开作用域自动 del
|
||||
// lv_scr_load(scr.raw()); // 让 LVGL 把它当活动屏幕
|
||||
|
||||
// lvgl_cpp::Image img(scr);
|
||||
// img.bin("pic_no_alp_swap.bin", 720, 720)
|
||||
// .center();
|
||||
//
|
||||
// /* 1. 创建按钮 */
|
||||
// /* 1. 黑色文字样式 */
|
||||
// static lvgl_cpp::Style txt_style;
|
||||
// txt_style.text_color(lv_color_black());
|
||||
//
|
||||
// /* 2. 按钮 */
|
||||
// lvgl_cpp::Button btn{scr};
|
||||
// btn.size(150, 60).pos(40, 40);
|
||||
// /* 3. 先创建 Label 对象并保存,再链式调 */
|
||||
// lvgl_cpp::Label lbl{btn}; // 一定要存实例
|
||||
// lbl.text("Click Me")
|
||||
// .add_style(&txt_style.s, LV_PART_MAIN); // 样式作用到文字本身
|
||||
// /* 3. 注册点击事件 */
|
||||
// btn.on(LV_EVENT_CLICKED, [](lv_event_t*){
|
||||
// lvgl_cpp::Toast::show("Saved successfully !");
|
||||
// /* 2. 警告,3 s,顶部 */
|
||||
// lvgl_cpp::Toast::show("SD card missing", lvgl_cpp::Toast::Type::WARN, 3000, LV_ALIGN_TOP_MID, 20);
|
||||
//
|
||||
// /* 3. 错误,1.5 s,底部右侧 */
|
||||
// lvgl_cpp::Toast::show("Network error", lvgl_cpp::Toast::Type::ERROR, 1500, LV_ALIGN_BOTTOM_RIGHT, -30);
|
||||
// ESP_LOGI("BTN", "pressed");
|
||||
// });
|
||||
//
|
||||
// /* 1. 消息框 */
|
||||
// // const char* btns[] = {"Yes", "No", ""};
|
||||
// // auto mbox = lvgl_cpp::MsgBox::create("Hint", "Delete file ?", btns);
|
||||
//
|
||||
// /* 3. 进度条(双向 + 动画) */
|
||||
// lvgl_cpp::Bar bar(scr);
|
||||
// bar.range(0, 100)
|
||||
// .start_value(20) // 左端
|
||||
// .value(80, LV_ANIM_ON) // 右端带动画
|
||||
// .anim_time(500)
|
||||
// .size(200, 15)
|
||||
// .align(LV_ALIGN_CENTER, nullptr, 0, 40);
|
||||
//
|
||||
// /* 4. 折线:画一个 △ */
|
||||
// std::vector<lv_point_t> triangle = {{0,0}, {40,0}, {20,40}, {0,0}};
|
||||
// lvgl_cpp::Line line(scr);
|
||||
// line.points(triangle)
|
||||
// .y_invert(false)
|
||||
// .align(LV_ALIGN_CENTER, nullptr, 0, 100);
|
||||
//
|
||||
// lvgl_cpp::Battery bat(scr);
|
||||
// bat.size(60, 30) // 手机经典尺寸
|
||||
// .percent(true) // 显示 50%
|
||||
// .onRead([]() -> uint8_t { // 替换成你的 ADC/INA219 回调
|
||||
// static uint8_t v = 100;
|
||||
// if (v) --v;
|
||||
// return v;
|
||||
// })
|
||||
// .align(LV_ALIGN_TOP_RIGHT, -10, 10); // 九宫格对齐
|
||||
//
|
||||
// /* 3. 日期时间 */
|
||||
// lvgl_cpp::DateTime dt(scr);
|
||||
// dt.format("%m/%d %a %H:%M")
|
||||
// .onRead([](char* buf, size_t len){
|
||||
// /* 这里用 SNTP / RTC 填充,示例直接给假时间 */
|
||||
// snprintf(buf, len, "06/25 Tue 14:30");
|
||||
// })
|
||||
// .align(LV_ALIGN_BOTTOM_MID, nullptr, 0, -10);
|
||||
|
||||
|
||||
// lvgl_cpp::ColorPicker cp(scr);
|
||||
// cp.size(120, 120)
|
||||
// .align(LV_ALIGN_BOTTOM_MID, nullptr, 0, -20)
|
||||
// .on(LV_EVENT_VALUE_CHANGED, [&](lv_event_t*){
|
||||
// lv_color_t c = cp.color();
|
||||
// ESP_LOGI("CP", "rgb=%d,%d,%d", c.ch.red, c.ch.green_h, c.ch.blue);
|
||||
// });
|
||||
|
||||
// 1. 创建屏幕
|
||||
g_screen = std::make_shared<lvgl_cpp::Screen>();
|
||||
lv_scr_load(g_screen->raw());
|
||||
|
||||
// 2. 延迟创建界面组件
|
||||
auto init_timer = std::make_unique<lvgl_cpp::Timer>([&]() {
|
||||
ESP_LOGI("MAIN", "Initializing UI components...");
|
||||
|
||||
// 创建主页
|
||||
g_home = std::make_shared<HomePage>(*g_screen);
|
||||
g_home->bg("pic360.bin", 360, 360)
|
||||
.onBattery([]() -> uint8_t {
|
||||
static uint8_t v = 100;
|
||||
if (v) --v;
|
||||
return 88;
|
||||
})
|
||||
.onDateTime([](char* buf, size_t len) {
|
||||
snprintf(buf, len, "06/25 Tue 14:30");
|
||||
});
|
||||
|
||||
// 创建菜单
|
||||
g_menu = std::make_shared<AppMenu>(*g_screen);
|
||||
g_menu->addItem("Calcu", 100, 50)
|
||||
.addItem("Gif测试", 100, 50)
|
||||
.addItem("Button", 100, 50)
|
||||
.onClick([](const char* name) {
|
||||
ESP_LOGI("APP", "Launching: %s", name);
|
||||
|
||||
/* 1. 让工厂创建子应用 */
|
||||
auto app = AppFactory::create(name, *g_screen);
|
||||
if (!app) return;
|
||||
|
||||
/* 2. 隐藏菜单,显示应用 */
|
||||
lv_obj_add_flag(g_menu->raw(), LV_OBJ_FLAG_HIDDEN);
|
||||
app->onShow();
|
||||
|
||||
/* 3. 应用退出时回到菜单 */
|
||||
app->onExit([app_ptr = app.release()]() { // 转移所有权到 lambda
|
||||
lv_obj_clear_flag(g_menu->raw(), LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_del(app_ptr->raw()); // 自毁
|
||||
});
|
||||
});
|
||||
|
||||
lv_obj_add_flag(g_menu->raw(), LV_OBJ_FLAG_HIDDEN);
|
||||
|
||||
// 安全的事件连接
|
||||
g_home->onOpenMenu([]() {
|
||||
if (g_home && g_menu) {
|
||||
lv_obj_add_flag(g_home->raw(), LV_OBJ_FLAG_HIDDEN);
|
||||
lv_obj_clear_flag(g_menu->raw(), LV_OBJ_FLAG_HIDDEN);
|
||||
}
|
||||
});
|
||||
|
||||
ESP_LOGI("MAIN", "UI initialization complete");
|
||||
}, 1000, true); // 1秒后执行一次
|
||||
|
||||
// 注册应用
|
||||
AppFactory::registerApp("Calcu", [](Obj& p) {
|
||||
return std::make_unique<AppCalc>(p);
|
||||
});
|
||||
AppFactory::registerApp("Gif测试", [](Obj& p) {
|
||||
return std::make_unique<AppMusic>(p);
|
||||
});
|
||||
AppFactory::registerApp("Button", [](lvgl_cpp::Obj& p) {
|
||||
return std::make_unique<ButtonApp>(p);
|
||||
});
|
||||
|
||||
// test_show_bin("pic_no_alp_swap.bin");
|
||||
|
||||
// LVGLRender::getInstance()->RenderGif("sequence02mmm.gif");
|
||||
|
||||
ESP_LOGI("CppHandle::Cpp_Hand", "当前固件版本 %s:", ToolsClass::getDeviceVersion().c_str());
|
||||
|
||||
@@ -442,18 +706,19 @@ void Cpp_Hand() {
|
||||
ESP_LOGI("CppHandle::Cpp_Hand", "当前设备固件序列号 %s:", ToolsClass::getChipSerialNumber().c_str());
|
||||
|
||||
// 连接wifi
|
||||
WifiConnectors::getInstance()->connectWifi("Misaki-2.4G", "88888888", 5);
|
||||
// WifiConnectors::getInstance()->connectWifi("Misaki-2.4G", "88888888", 5);
|
||||
|
||||
// 创建WebSocket
|
||||
createWebSocket();
|
||||
// createWebSocket();
|
||||
|
||||
// 设置OTA回调
|
||||
setupOtaCallbacks();
|
||||
// setupOtaCallbacks();
|
||||
|
||||
while (true) { // 主线程线程循环
|
||||
// ThreadManager::print_sys_memory(); // 打印系统内存使用情况
|
||||
// ThreadManager::stats_task(); // 打印任务统计信息
|
||||
|
||||
ESP_LOGI("APP_TASK", "Battery is:%ld", ToolsClass::getInstance()->getBatteryPer());
|
||||
ESP_LOGI("APP_TASK", "Battery is:%f", ToolsClass::getInstance()->getBatteryVolts());
|
||||
std::this_thread::sleep_for(sleep_time); // 休眠5秒
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "LVGL_Driver.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <ToolsClass.h>
|
||||
|
||||
LVGLRender* LVGLRender::LVGLRenderInstance = nullptr;
|
||||
std::mutex LVGLRender::instance_mutex;
|
||||
@@ -30,11 +31,13 @@ LVGLRender* LVGLRender::getInstance() {
|
||||
|
||||
void LVGLRender::LVGL_Update() {
|
||||
while (true) {
|
||||
vTaskDelay(LVGL_DELAY_FROM_FPS(LVGLRender::fps));
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(mtx); // lvgl并非线程安全,需要加锁
|
||||
lv_timer_handler();
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(fps));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 构造函数
|
||||
LVGLRender::LVGLRender() {
|
||||
@@ -63,14 +66,24 @@ LVGLRender::~LVGLRender() {
|
||||
|
||||
}
|
||||
|
||||
void LVGLRender::log() {
|
||||
ESP_LOGI("LVGL_Render", "LVGL_Render log...");
|
||||
void LVGLRender::setFps(uint16_t fps_) {
|
||||
fps = fps_;
|
||||
}
|
||||
|
||||
// 静态:拼出完整路径
|
||||
std::string LVGLRender::makeFullPath(const std::string& filename)
|
||||
{
|
||||
return "/sdcard/" + filename;
|
||||
uint16_t LVGLRender::getFps() {
|
||||
return fps;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void LVGLRender::tryToInitRenderGif() {
|
||||
ESP_LOGI("LVGL_Render", "尝试初始化 LVGL 底层驱动...");
|
||||
}
|
||||
|
||||
|
||||
void LVGLRender::log() {
|
||||
ESP_LOGI("LVGL_Render", "LVGL_Render log...");
|
||||
}
|
||||
|
||||
#include "SDFileManager.h"
|
||||
@@ -118,15 +131,15 @@ void LVGLRender::renderGifInternal(const std::vector<uint8_t>& data,
|
||||
gif_desc.header.cf = LV_IMG_CF_RAW_CHROMA_KEYED;
|
||||
gif_desc.header.always_zero = 0;
|
||||
gif_desc.header.reserved = 0;
|
||||
gif_desc.header.w = (lv_coord_t)w;
|
||||
gif_desc.header.h = (lv_coord_t)h;
|
||||
gif_desc.header.w = static_cast<lv_coord_t>(w);
|
||||
gif_desc.header.h = static_cast<lv_coord_t>(h);
|
||||
gif_desc.data_size = current_gif_data.size();
|
||||
gif_desc.data = current_gif_data.data();
|
||||
|
||||
// 创建新的 GIF 对象
|
||||
current_gif_obj = lv_gif_create(lv_scr_act());
|
||||
lv_gif_set_src(current_gif_obj, &gif_desc);
|
||||
lv_obj_center(current_gif_obj);
|
||||
current_gif_obj = lv_gif_create(lv_scr_act()); // copy到当前gif对象
|
||||
lv_gif_set_src(current_gif_obj, &gif_desc); // 设置源
|
||||
lv_obj_center(current_gif_obj); // 居中
|
||||
|
||||
ESP_LOGI("LVGLRender", "GIF 已渲染并循环播放");
|
||||
}
|
||||
@@ -141,7 +154,7 @@ void LVGLRender::RenderGif(const std::string &filename) {
|
||||
lv_obj_set_style_bg_color(lv_scr_act(), lv_color_black(), 0); // 背景黑色
|
||||
lv_obj_set_style_bg_opa(lv_scr_act(), LV_OPA_COVER, 0); // 透明度
|
||||
|
||||
std::string fullPath = makeFullPath(filename);
|
||||
std::string fullPath = ToolsClass::makeFullPath(filename);
|
||||
std::vector<uint8_t> gifBin = readWholeFile(fullPath);
|
||||
if (gifBin.empty()) return;
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ class LVGLRender {
|
||||
public:
|
||||
LVGLRender(LVGLRender const&) = delete; // 禁止拷贝构造函数
|
||||
LVGLRender& operator=(LVGLRender const&) = delete;// 禁止赋值运算符
|
||||
public:
|
||||
static std::string makeFullPath(const std::string& filename);
|
||||
|
||||
public:
|
||||
static LVGLRender* getInstance();
|
||||
@@ -32,6 +30,12 @@ public:
|
||||
*/
|
||||
void RenderGif(const std::string &filename);
|
||||
|
||||
static void setFps(uint16_t fps_);
|
||||
|
||||
static uint16_t getFps();
|
||||
|
||||
void tryToInitRenderGif();
|
||||
|
||||
void log();
|
||||
|
||||
// gif渲染
|
||||
@@ -59,6 +63,8 @@ private:
|
||||
lv_obj_t* current_gif_obj = nullptr; /// 当前GIF对象
|
||||
std::vector<uint8_t> current_gif_data; /// 当前GIF数据
|
||||
std::string last_gif_filename; /// 最后一次渲染的GIF文件名
|
||||
|
||||
std::mutex mtx;
|
||||
};
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
//
|
||||
// Created by misaki on 2025/9/26.
|
||||
//
|
||||
|
||||
#include "lvpp.h"
|
||||
@@ -0,0 +1,849 @@
|
||||
//
|
||||
// Created by misaki on 2025/9/26.
|
||||
//
|
||||
#pragma once
|
||||
#include <esp_heap_caps.h>
|
||||
#include <esp_log.h>
|
||||
#include <lvgl.h>
|
||||
#include <font/lv_font.h>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include "SDFileManager.h"
|
||||
#include "ToolsClass.h"
|
||||
LV_FONT_DECLARE(SiYuanHeiTiGoogleBan); // 思源黑体 Google版
|
||||
namespace lvgl_cpp {
|
||||
|
||||
// 前向声明
|
||||
class Obj;
|
||||
class Screen;
|
||||
class Label;
|
||||
class Button;
|
||||
class Arc;
|
||||
class Slider;
|
||||
class Switch;
|
||||
class Roller;
|
||||
class Dropdown;
|
||||
class List;
|
||||
class TextArea;
|
||||
class Image;
|
||||
class IconButton;
|
||||
|
||||
// 基础封装
|
||||
class Obj {
|
||||
public:
|
||||
Obj() = default;
|
||||
explicit Obj(lv_obj_t* raw) : ptr_(raw) {}
|
||||
virtual ~Obj() { if (ptr_) lv_obj_del(ptr_); }
|
||||
|
||||
Obj(Obj&& o) noexcept : ptr_(std::exchange(o.ptr_, nullptr)) {}
|
||||
Obj& operator=(Obj&& o) noexcept {
|
||||
if (this != &o) { reset(); ptr_ = std::exchange(o.ptr_, nullptr); }
|
||||
return *this;
|
||||
}
|
||||
Obj(const Obj&) = delete;
|
||||
Obj& operator=(const Obj&) = delete;
|
||||
|
||||
[[nodiscard]] lv_obj_t* raw() const { return ptr_; }
|
||||
void reset() { if (ptr_) { lv_obj_del(ptr_); ptr_ = nullptr; } }
|
||||
|
||||
// 通用链式
|
||||
Obj& size(const lv_coord_t w, const lv_coord_t h) { lv_obj_set_size(ptr_, w, h); return *this; }
|
||||
Obj& pos(const lv_coord_t x, const lv_coord_t y) { lv_obj_set_pos(ptr_, x, y); return *this; }
|
||||
Obj& center() { lv_obj_center(ptr_); return *this; }
|
||||
Obj& align(const lv_align_t align, const lv_obj_t* base = nullptr, const lv_coord_t x = 0, const lv_coord_t y = 0) {
|
||||
if (base) { // 如果有 base
|
||||
lv_obj_align_to(ptr_, base, align, x, y);
|
||||
}else {
|
||||
lv_obj_align(ptr_, align, x, y);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
Obj& add_flag(const lv_obj_flag_t f) { lv_obj_add_flag(ptr_, f); return *this; }
|
||||
Obj& clear_flag(const lv_obj_flag_t f) { lv_obj_clear_flag(ptr_, f); return *this; }
|
||||
Obj& add_style(lv_style_t* style, const lv_style_selector_t sel = LV_PART_MAIN) {
|
||||
lv_obj_add_style(ptr_, style, sel); return *this;
|
||||
}
|
||||
|
||||
// 事件转发
|
||||
using EventCb = std::function<void(lv_event_t*)>;
|
||||
Obj& on(const lv_event_code_t code, EventCb cb) {
|
||||
auto* ctx = new EventCb(std::move(cb));
|
||||
lv_obj_add_event_cb(ptr_, event_trampoline, code, ctx);
|
||||
return *this;
|
||||
}
|
||||
protected: // 改为 protected 以允许派生类访问
|
||||
lv_obj_t* ptr_ = nullptr;
|
||||
private:
|
||||
static void event_trampoline(lv_event_t* e) {
|
||||
const auto* ctx = static_cast<EventCb*>(lv_event_get_user_data(e));
|
||||
(*ctx)(e);
|
||||
}
|
||||
};
|
||||
|
||||
// 样式构造器
|
||||
struct Style {
|
||||
lv_style_t s{};
|
||||
Style() { lv_style_init(&s); }
|
||||
~Style() { lv_style_reset(&s); }
|
||||
Style& bg(lv_color_t c, lv_opa_t o = LV_OPA_COVER) {
|
||||
lv_style_set_bg_color(&s, c);
|
||||
lv_style_set_bg_opa(&s, o); return *this;
|
||||
}
|
||||
Style& radius(const lv_coord_t r) { lv_style_set_radius(&s, r); return *this; }
|
||||
Style& pad_all(const lv_coord_t v) { lv_style_set_pad_all(&s, v); return *this; }
|
||||
Style& border(const lv_color_t c, const lv_coord_t w = 1) {
|
||||
lv_style_set_border_color(&s, c);
|
||||
lv_style_set_border_width(&s, w); return *this;
|
||||
}
|
||||
Style& text_color(const lv_color_t c) { lv_style_set_text_color(&s, c); return *this; }
|
||||
Style& shadow(const lv_coord_t w, const lv_color_t c) {
|
||||
lv_style_set_shadow_width(&s, w);
|
||||
lv_style_set_shadow_color(&s, c); return *this;
|
||||
}
|
||||
explicit operator lv_style_t*() { return &s; }
|
||||
};
|
||||
|
||||
// 动画助手
|
||||
template<typename T>
|
||||
struct Anim {
|
||||
lv_anim_t a{};
|
||||
Anim(T* var, const int32_t start, const int32_t end, const uint32_t time = 300, const lv_anim_path_cb_t path = lv_anim_path_ease_out) {
|
||||
lv_anim_init(&a);
|
||||
lv_anim_set_var(&a, var);
|
||||
lv_anim_set_values(&a, start, end);
|
||||
lv_anim_set_time(&a, time);
|
||||
lv_anim_set_path_cb(&a, path);
|
||||
}
|
||||
Anim& exec_cb(const lv_anim_exec_xcb_t cb) { lv_anim_set_exec_cb(&a, cb); return *this; }
|
||||
Anim& ready_cb(const lv_anim_ready_cb_t cb) { lv_anim_set_ready_cb(&a, cb); return *this; }
|
||||
void start() const { lv_anim_start(&a); }
|
||||
};
|
||||
|
||||
// 具体控件
|
||||
class Screen : public Obj {
|
||||
public:
|
||||
Screen() : Obj(lv_obj_create(nullptr)) {}
|
||||
};
|
||||
|
||||
class Label : public Obj {
|
||||
public:
|
||||
explicit Label(const Obj& parent) : Obj(lv_label_create(parent.raw())) {}
|
||||
Label& text(const char* txt) { lv_label_set_text(raw(), txt); return *this; }
|
||||
};
|
||||
|
||||
class Button : public Obj {
|
||||
public:
|
||||
explicit Button(const Obj& parent) : Obj(lv_btn_create(parent.raw())) {}
|
||||
};
|
||||
|
||||
class Arc : public Obj {
|
||||
public:
|
||||
explicit Arc(const Obj& parent) : Obj(lv_arc_create(parent.raw())) {}
|
||||
Arc& value(const int16_t v) { lv_arc_set_value(raw(), v); return *this; }
|
||||
Arc& range(const int16_t min, const int16_t max) { lv_arc_set_range(raw(), min, max); return *this; }
|
||||
};
|
||||
|
||||
class Slider : public Obj {
|
||||
public:
|
||||
explicit Slider(const Obj& parent) : Obj(lv_slider_create(parent.raw())) {}
|
||||
Slider& value(const int32_t v) { lv_slider_set_value(raw(), v, LV_ANIM_OFF); return *this; }
|
||||
Slider& range(const int32_t min, const int32_t max) { lv_slider_set_range(raw(), min, max); return *this; }
|
||||
};
|
||||
|
||||
class Switch : public Obj {
|
||||
public:
|
||||
explicit Switch(const Obj& parent) : Obj(lv_switch_create(parent.raw())) {}
|
||||
};
|
||||
|
||||
class Roller : public Obj {
|
||||
public:
|
||||
explicit Roller(const Obj& parent) : Obj(lv_roller_create(parent.raw())) {}
|
||||
Roller& options(const char* opts, const lv_roller_mode_t m = LV_ROLLER_MODE_NORMAL) {
|
||||
lv_roller_set_options(raw(), opts, m); return *this;
|
||||
}
|
||||
};
|
||||
|
||||
class Dropdown : public Obj {
|
||||
public:
|
||||
explicit Dropdown(const Obj& parent) : Obj(lv_dropdown_create(parent.raw())) {}
|
||||
Dropdown& options(const char* opts) { lv_dropdown_set_options(raw(), opts); return *this; }
|
||||
};
|
||||
|
||||
class List : public Obj {
|
||||
public:
|
||||
explicit List(const Obj& parent) : Obj(lv_list_create(parent.raw())) {}
|
||||
lv_obj_t* add_btn(const void* img, const char* txt) const { return lv_list_add_btn(raw(), img, txt); }
|
||||
};
|
||||
|
||||
class TextArea : public Obj {
|
||||
public:
|
||||
explicit TextArea(const Obj& parent) : Obj(lv_textarea_create(parent.raw())) {}
|
||||
TextArea& text(const char* txt) { lv_textarea_set_text(raw(), txt); return *this; }
|
||||
TextArea& placeholder(const char* txt) { lv_textarea_set_placeholder_text(raw(), txt); return *this; }
|
||||
};
|
||||
|
||||
class Image : public Obj {
|
||||
public:
|
||||
explicit Image(const Obj& parent) : Obj(lv_img_create(parent.raw())) {}
|
||||
|
||||
// 读 bin → PSRAM → 一次性 dsc → 显示
|
||||
Image& bin(const char* fileName, const uint32_t w, const uint32_t h)
|
||||
{
|
||||
// 读文件
|
||||
const std::string data = SDFileManager::getInstance()->readFileSync(ToolsClass::makeFullPath(fileName));
|
||||
if (data.empty()) {
|
||||
ESP_LOGE("Image", "bin read fail: %s", fileName);
|
||||
return *this;
|
||||
}
|
||||
size_t sz = data.size();
|
||||
if (sz != w * h * 2) { // RGB565 检查
|
||||
ESP_LOGW("Image", "size mismatch, expect %lu, got %zu", w * h * 2, sz);
|
||||
}
|
||||
|
||||
// 拷到 PSRAM(LVGL 长期持有)
|
||||
void* buf = heap_caps_malloc(sz, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
if (!buf) {
|
||||
ESP_LOGE("Image", "PSRAM malloc failed");
|
||||
return *this;
|
||||
}
|
||||
memcpy(buf, data.data(), sz);
|
||||
|
||||
// 构造静态描述符(生命周期跟随 Image 对象)
|
||||
dsc_ = std::unique_ptr<lv_img_dsc_t, DscDeleter>(new lv_img_dsc_t{});
|
||||
dsc_->data = static_cast<const uint8_t*>(buf);
|
||||
dsc_->header.cf = LV_IMG_CF_TRUE_COLOR;
|
||||
dsc_->header.w = w;
|
||||
dsc_->header.h = h;
|
||||
dsc_->data_size = sz;
|
||||
|
||||
// 交给 LVGL
|
||||
lv_img_set_src(raw(), dsc_.get());
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
// 用 unique_ptr 托管,Image 析构时自动释放 PSRAM 与 dsc
|
||||
struct DscDeleter {
|
||||
void operator()(const lv_img_dsc_t* p) const {
|
||||
if (p && p->data) heap_caps_free(const_cast<uint8_t*>(p->data));
|
||||
delete p;
|
||||
}
|
||||
};
|
||||
std::unique_ptr<lv_img_dsc_t, DscDeleter> dsc_;
|
||||
};
|
||||
|
||||
class IconButton : public Button {
|
||||
public:
|
||||
explicit IconButton(const Obj& parent) : Button(parent) {}
|
||||
|
||||
IconButton& icon(const char* fileName, const uint32_t w, const uint32_t h)
|
||||
{
|
||||
// 图标作为独立子 Image 创建
|
||||
img_ = std::make_unique<Image>(*this);
|
||||
img_->bin(fileName, w, h);
|
||||
lv_obj_center(img_->raw());
|
||||
return *this;
|
||||
}
|
||||
private:
|
||||
std::unique_ptr<Image> img_; // 跟随 IconButton 生命周期
|
||||
};
|
||||
|
||||
|
||||
// 消息框 MsgBox
|
||||
class MsgBox : public Obj {
|
||||
public:
|
||||
// 静态创建:模态、居中、自动大小
|
||||
static MsgBox create(const char* title,
|
||||
const char* txt,
|
||||
const char* btns[] = nullptr) // nullptr -> 默认 "OK"
|
||||
{
|
||||
lv_obj_t* mbox = lv_msgbox_create(nullptr, title, txt, btns, true);
|
||||
lv_obj_center(mbox);
|
||||
return MsgBox{mbox};
|
||||
}
|
||||
// 关闭并删除自身
|
||||
void close() { lv_msgbox_close(raw()); ptr_ = nullptr; }
|
||||
private:
|
||||
explicit MsgBox(lv_obj_t* p) : Obj(p) {}
|
||||
};
|
||||
|
||||
// 进度条 Bar
|
||||
class Bar : public Obj {
|
||||
public:
|
||||
explicit Bar(const Obj& parent) : Obj(lv_bar_create(parent.raw())) {}
|
||||
Bar& range(const int32_t min, const int32_t max) { lv_bar_set_range(raw(), min, max); return *this; }
|
||||
Bar& value(const int32_t v, const lv_anim_enable_t anim = LV_ANIM_OFF) {
|
||||
lv_bar_set_value(raw(), v, anim); return *this;
|
||||
}
|
||||
// 双向模式
|
||||
Bar& start_value(const int32_t v, const lv_anim_enable_t anim = LV_ANIM_OFF) {
|
||||
lv_bar_set_start_value(raw(), v, anim); return *this;
|
||||
}
|
||||
Bar& anim_time(const uint32_t ms) { lv_obj_set_style_anim_time(raw(), ms, LV_PART_MAIN); return *this; }
|
||||
};
|
||||
|
||||
// 折线 Line
|
||||
class Line : public Obj {
|
||||
public:
|
||||
explicit Line(const Obj& parent) : Obj(lv_line_create(parent.raw())) {}
|
||||
/* 直接传 std::vector<lv_point_t> 更安全 */
|
||||
Line& points(const std::vector<lv_point_t>& v) {
|
||||
lv_line_set_points(raw(), v.data(), v.size()); return *this;
|
||||
}
|
||||
Line& y_invert(const bool en) { lv_line_set_y_invert(raw(), en); return *this; }
|
||||
};
|
||||
|
||||
// 颜色选择器 ColorPicker
|
||||
class ColorPicker : public Obj {
|
||||
public:
|
||||
explicit ColorPicker(const Obj& parent) : Obj(lv_colorwheel_create(parent.raw(), true)) {}
|
||||
// 获取/设置 HSV
|
||||
[[nodiscard]] lv_color_t color() const { return lv_colorwheel_get_rgb(raw()); }
|
||||
ColorPicker& color(const lv_color_t c) { lv_colorwheel_set_rgb(raw(), c); return *this; }
|
||||
// 事件回调:LV_EVENT_VALUE_CHANGED 即可拿到新颜色
|
||||
};
|
||||
|
||||
// Timer 封装(C++ lambda 版)
|
||||
class Timer {
|
||||
public:
|
||||
using Cb = std::function<void()>;
|
||||
// 构造即创建,默认循环模式
|
||||
explicit Timer(Cb cb, const uint32_t period_ms, const bool once = false)
|
||||
: cb_(new Cb(std::move(cb)))
|
||||
{
|
||||
t_ = lv_timer_create(timer_trampoline, period_ms, cb_.get());
|
||||
if (once) lv_timer_set_repeat_count(t_, 1);
|
||||
}
|
||||
~Timer() { // RAII:自动反注册、释放
|
||||
if (t_) lv_timer_del(t_);
|
||||
}
|
||||
// 手动停止/重启
|
||||
void pause() const { lv_timer_pause(t_); }
|
||||
void resume() const { lv_timer_resume(t_); }
|
||||
void reset() const { lv_timer_reset(t_); }
|
||||
// 修改周期
|
||||
Timer& period(const uint32_t ms) { lv_timer_set_period(t_, ms); return *this; }
|
||||
private:
|
||||
lv_timer_t* t_ = nullptr;
|
||||
std::unique_ptr<Cb> cb_; // 托管 lambda 生命周期
|
||||
static void timer_trampoline(lv_timer_t* tm) {
|
||||
const auto* f = static_cast<Cb*>(tm->user_data);
|
||||
(*f)();
|
||||
}
|
||||
};
|
||||
|
||||
// Toast 消息泡泡
|
||||
class Toast {
|
||||
public:
|
||||
enum class Type : uint8_t { INFO, WARN, ERROR };
|
||||
|
||||
// 一键弹出,默认底部居中 2 s
|
||||
static void show(const char* txt,
|
||||
const Type tp = Type::INFO,
|
||||
const uint32_t duration_ms = 2000,
|
||||
const lv_align_t align = LV_ALIGN_BOTTOM_MID,
|
||||
const lv_coord_t offset_y = -60)
|
||||
{
|
||||
// 确保在 LVGL 线程里执行
|
||||
if (!lv_is_initialized()) return;
|
||||
lv_async_call(
|
||||
[](void* p) {
|
||||
auto* ctx = static_cast<Context*>(p);
|
||||
create_internal(ctx);
|
||||
delete ctx;
|
||||
},
|
||||
new Context{txt, tp, duration_ms, align, offset_y});
|
||||
}
|
||||
|
||||
private:
|
||||
struct Context {
|
||||
const char* txt;
|
||||
Type tp;
|
||||
uint32_t dur;
|
||||
lv_align_t align;
|
||||
lv_coord_t off_y;
|
||||
};
|
||||
|
||||
static void create_internal(const Context* ctx)
|
||||
{
|
||||
// 根容器(透明全屏)
|
||||
lv_obj_t* parent = lv_layer_top(); // 顶层 overlay
|
||||
lv_obj_t* toast = lv_obj_create(parent);
|
||||
lv_obj_set_size(toast, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
||||
lv_obj_set_scrollbar_mode(toast, LV_SCROLLBAR_MODE_OFF);
|
||||
lv_obj_clear_flag(toast, LV_OBJ_FLAG_SCROLLABLE);
|
||||
// 去掉边框 / 背景 / 阴影
|
||||
lv_obj_set_style_border_width(toast, 0, 0);
|
||||
lv_obj_set_style_bg_opa(toast, LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_shadow_width(toast, 0, 0);
|
||||
|
||||
// 消息标签
|
||||
lv_obj_t* label = lv_label_create(toast);
|
||||
lv_label_set_text(label, ctx->txt);
|
||||
lv_obj_set_style_pad_hor(label, 12, 0);
|
||||
lv_obj_set_style_pad_ver(label, 8, 0);
|
||||
lv_obj_set_style_radius(label, 8, 0);
|
||||
lv_obj_set_style_text_color(label, lv_color_white(), 0);
|
||||
|
||||
lv_obj_set_style_text_font(label, &SiYuanHeiTiGoogleBan, LV_PART_MAIN);
|
||||
|
||||
// 根据类型配色
|
||||
switch (ctx->tp) {
|
||||
case Type::INFO:
|
||||
lv_obj_set_style_bg_color(label, lv_color_hex(0x2196F3), 0);
|
||||
break;
|
||||
case Type::WARN:
|
||||
lv_obj_set_style_bg_color(label, lv_color_hex(0xFF9800), 0);
|
||||
break;
|
||||
case Type::ERROR:
|
||||
lv_obj_set_style_bg_color(label, lv_color_hex(0xF44336), 0);
|
||||
break;
|
||||
}
|
||||
lv_obj_set_style_bg_opa(label, LV_OPA_COVER, 0);
|
||||
|
||||
// 定位
|
||||
lv_obj_align(toast, ctx->align, 0, ctx->off_y);
|
||||
|
||||
// 进场动画:向上淡入
|
||||
lv_obj_set_style_translate_y(toast, 30, 0);
|
||||
lv_obj_set_style_opa(toast, LV_OPA_0, 0);
|
||||
lv_anim_t a;
|
||||
lv_anim_init(&a);
|
||||
lv_anim_set_var(&a, toast);
|
||||
lv_anim_set_time(&a, 250);
|
||||
lv_anim_set_values(&a, LV_OPA_0, LV_OPA_COVER);
|
||||
lv_anim_set_exec_cb(&a, [](void* obj, int32_t v) {
|
||||
lv_obj_set_style_opa(static_cast<lv_obj_t *>(obj), v, 0);
|
||||
lv_obj_set_style_translate_y(static_cast<lv_obj_t *>(obj), 30 - (v * 30) / 255, 0);
|
||||
});
|
||||
lv_anim_start(&a);
|
||||
|
||||
// 定时器:到达时间后淡出并删除
|
||||
lv_timer_create(
|
||||
[](lv_timer_t* t) {
|
||||
auto* toast = static_cast<lv_obj_t *>(t->user_data);
|
||||
/* 淡出 */
|
||||
lv_anim_t a2;
|
||||
lv_anim_init(&a2);
|
||||
lv_anim_set_var(&a2, toast);
|
||||
lv_anim_set_time(&a2, 200);
|
||||
lv_anim_set_values(&a2, LV_OPA_COVER, LV_OPA_0);
|
||||
lv_anim_set_exec_cb(&a2, [](void* obj, int32_t v) {
|
||||
lv_obj_set_style_opa(static_cast<lv_obj_t *>(obj), v, 0);
|
||||
});
|
||||
lv_anim_set_ready_cb(&a2, [](lv_anim_t* a) {
|
||||
lv_obj_del(static_cast<lv_obj_t *>(a->var));
|
||||
});
|
||||
lv_anim_start(&a2);
|
||||
lv_timer_del(t); // 自毁
|
||||
},
|
||||
ctx->dur, toast);
|
||||
}
|
||||
};
|
||||
|
||||
// PhoneStyle-Battery
|
||||
class Battery : public Obj {
|
||||
public:
|
||||
explicit Battery(const Obj& parent) : Obj(lv_obj_create(parent.raw())) {
|
||||
// 主容器:圆角 + 边框 + 阴影
|
||||
lv_obj_set_style_radius(raw(), 6, 0);
|
||||
lv_obj_set_style_bg_color(raw(), lv_color_hex(0x9E9E9E), 0);
|
||||
lv_obj_set_style_bg_opa(raw(), LV_OPA_COVER, 0);
|
||||
lv_obj_set_style_border_width(raw(), 2, 0);
|
||||
lv_obj_set_style_border_color(raw(), lv_color_hex(0x616161), 0);
|
||||
lv_obj_set_style_shadow_width(raw(), 8, 0);
|
||||
lv_obj_set_style_shadow_ofs_y(raw(), 4, 0);
|
||||
lv_obj_set_style_shadow_color(raw(), lv_color_hex(0x9E9E9E), 0);
|
||||
lv_obj_set_style_pad_all(raw(), 2, 0); // 内容区留白 2 px
|
||||
lv_obj_set_size(raw(), LV_SIZE_CONTENT, LV_SIZE_CONTENT);
|
||||
|
||||
// 电量柱(动态宽/高)
|
||||
fill_ = lv_obj_create(raw());
|
||||
lv_obj_set_style_radius(fill_, 5, 0);
|
||||
lv_obj_set_style_bg_opa(fill_, LV_OPA_COVER, 0);
|
||||
lv_obj_set_size(fill_, 0, LV_PCT(100)); // 初始 0 %
|
||||
applyGradient(); // 渐变刷一次
|
||||
|
||||
// 百分比文字(默认显示)
|
||||
label_ = lv_label_create(raw());
|
||||
lv_label_set_text(label_, "0%");
|
||||
lv_obj_set_style_text_color(label_, lv_color_white(), 0);
|
||||
lv_obj_set_style_text_font(label_, &lv_font_montserrat_14, 0);
|
||||
lv_obj_center(label_);
|
||||
}
|
||||
|
||||
// 设置整体尺寸(w×h)→ 自动计算内容区
|
||||
Battery& size(const lv_coord_t w, const lv_coord_t h) {
|
||||
lv_obj_set_size(raw(), w, h);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 显示/隐藏百分比文字
|
||||
Battery& percent(const bool show) {
|
||||
if (show)
|
||||
lv_obj_clear_flag(label_, LV_OBJ_FLAG_HIDDEN);
|
||||
else
|
||||
lv_obj_add_flag(label_, LV_OBJ_FLAG_HIDDEN);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 注册电量回调 0~100,并启动 5 s 定时器
|
||||
Battery& onRead(std::function<uint8_t()> provider) {
|
||||
provider_ = std::move(provider);
|
||||
update(); // 立即一次
|
||||
if (!timer_) {
|
||||
timer_ = lv_timer_create([](lv_timer_t* t){
|
||||
auto* self = static_cast<Battery *>(t->user_data);
|
||||
self->update();
|
||||
}, 5000, this);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 手动刷新一次(公开)
|
||||
Battery& update() {
|
||||
if (!provider_) return *this;
|
||||
pct_ = provider_();
|
||||
pct_ = pct_ > 100 ? 100 : pct_;
|
||||
|
||||
// 更新电量条长度(保留 2 px 内边距)
|
||||
const lv_coord_t content = lv_obj_get_content_width(raw()); // 去掉边框+pad
|
||||
const auto fill_w = static_cast<lv_coord_t>((static_cast<int>(content) * pct_) / 100);
|
||||
lv_obj_set_width(fill_, fill_w);
|
||||
|
||||
// 变色 + 渐变重刷
|
||||
if (pct_ > 60) mainColor_ = lv_color_hex(0x4CAF50); // 绿
|
||||
else if (pct_ > 20) mainColor_ = lv_color_hex(0xFFEB3B); // 黄
|
||||
else mainColor_ = lv_color_hex(0xF44336); // 红
|
||||
applyGradient();
|
||||
|
||||
// 文字
|
||||
lv_label_set_text_fmt(label_, "%" LV_PRId32 "%%", static_cast<int32_t>(pct_));
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 链式对齐
|
||||
Battery& align(const lv_align_t align_type, const lv_coord_t x_ofs = 0, const lv_coord_t y_ofs = 0) {
|
||||
lv_obj_align(raw(), align_type, x_ofs, y_ofs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
void applyGradient() const {
|
||||
// 构造一次性渐变描述符(浅→主→深)
|
||||
static lv_grad_dsc_t grad;
|
||||
grad.dir = LV_GRAD_DIR_HOR;
|
||||
grad.stops_count = 3;
|
||||
grad.stops[0].color = lv_color_lighten(mainColor_, LV_OPA_30);
|
||||
grad.stops[0].frac = 0;
|
||||
grad.stops[1].color = mainColor_;
|
||||
grad.stops[1].frac = 128;
|
||||
grad.stops[2].color = lv_color_darken(mainColor_, LV_OPA_30);
|
||||
grad.stops[2].frac = 255;
|
||||
lv_obj_set_style_bg_grad(fill_, &grad, 0);
|
||||
}
|
||||
|
||||
uint8_t pct_ = 50;
|
||||
lv_color_t mainColor_ = lv_color_hex(0x4CAF50);
|
||||
lv_obj_t* fill_;
|
||||
lv_obj_t* label_;
|
||||
lv_timer_t* timer_ = nullptr;
|
||||
std::function<uint8_t()> provider_;
|
||||
};
|
||||
|
||||
// DateTime 日期时间
|
||||
class DateTime : public Obj {
|
||||
public:
|
||||
explicit DateTime(const Obj& parent) : Obj(lv_label_create(parent.raw())) {
|
||||
lv_label_set_text(raw(), "--/--/-- --:--:--");
|
||||
}
|
||||
// 设置格式串,LVGL 语法,如 " %m/%d/%y %H:%M:%S"
|
||||
DateTime& format(const char* fmt) {
|
||||
fmt_ = fmt; refresh(); return *this;
|
||||
}
|
||||
// 注册数据源回调:外部把格式化好的字符串写进 buf
|
||||
DateTime& onRead(std::function<void(char* buf, size_t)> cb) {
|
||||
readCb_ = std::move(cb);
|
||||
refresh();
|
||||
// 1 s 定时刷新
|
||||
timer_ = lv_timer_create([](lv_timer_t* t){
|
||||
auto* self = static_cast<DateTime *>(t->user_data);
|
||||
self->refresh();
|
||||
}, 1000, this);
|
||||
return *this;
|
||||
}
|
||||
// 立即刷新一次
|
||||
DateTime& refresh() {
|
||||
if (!readCb_) return *this;
|
||||
readCb_(buf_, sizeof(buf_));
|
||||
lv_label_set_text(raw(), buf_);
|
||||
return *this;
|
||||
}
|
||||
private:
|
||||
lv_timer_t* timer_ = nullptr;
|
||||
std::function<void(char*, size_t)> readCb_;
|
||||
std::string fmt_ = "%Y/%m/%d %H:%M:%S";
|
||||
char buf_[64] = {0};
|
||||
};
|
||||
|
||||
// GIF 显示控件
|
||||
class Gif final : public Obj {
|
||||
public:
|
||||
explicit Gif(const Obj& parent) : Obj(lv_gif_create(parent.raw())) {}
|
||||
|
||||
bool getGifWH(const uint8_t* raw, uint32_t& w, uint32_t& h)
|
||||
{
|
||||
if (!raw || memcmp(raw, "GIF", 3) != 0) {
|
||||
ESP_LOGE("LVGLRender", "不是合法 GIF 文件头");
|
||||
return false;
|
||||
}
|
||||
// GIF87a/89a 宽高偏移 6~9 字节,小端
|
||||
w = raw[6] | (raw[7] << 8);
|
||||
h = raw[8] | (raw[9] << 8);
|
||||
ESP_LOGI("LVGLRender", "GIF 尺寸: %lu x %lu", w, h);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 从 SD 卡加载 GIF 文件(完整路径)
|
||||
Gif& src(const char* full_path) {
|
||||
// 读整个文件到 PSRAM
|
||||
std::string content = SDFileManager::getInstance()->readFileSync(ToolsClass::makeFullPath(full_path));
|
||||
if (content.empty()) {
|
||||
ESP_LOGE("Gif", "read %s fail", full_path);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 解析宽高
|
||||
uint32_t w = 0, h = 0;
|
||||
if (!getGifWH(reinterpret_cast<const uint8_t*>(content.data()), w, h)) {
|
||||
ESP_LOGE("Gif", "bad GIF header");
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 把数据拷到 PSRAM(LVGL 长期持有)
|
||||
void* buf = heap_caps_malloc(content.size(), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
|
||||
if (!buf) {
|
||||
ESP_LOGE("Gif", "PSRAM malloc fail");
|
||||
return *this;
|
||||
}
|
||||
memcpy(buf, content.data(), content.size());
|
||||
|
||||
// 构造一次性描述符(跟随 Gif 对象生命周期)
|
||||
dsc_ = std::unique_ptr<lv_img_dsc_t, DscDeleter>(new lv_img_dsc_t{});
|
||||
dsc_->header.cf = LV_IMG_CF_RAW_CHROMA_KEYED;
|
||||
dsc_->header.always_zero = 0;
|
||||
dsc_->header.reserved = 0;
|
||||
dsc_->header.w = static_cast<lv_coord_t>(w);
|
||||
dsc_->header.h = static_cast<lv_coord_t>(h);
|
||||
dsc_->data_size = content.size();
|
||||
dsc_->data = static_cast<const uint8_t*>(buf);
|
||||
|
||||
// 交给 LVGL
|
||||
lv_gif_set_src(raw(), dsc_.get());
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 控制播放/暂停
|
||||
Gif& play() { lv_gif_restart(raw()); return *this; }
|
||||
Gif& pause() { /*lv_gif_pause(raw());*/ return *this; }
|
||||
|
||||
private:
|
||||
// 描述符 + PSRAM 一并释放
|
||||
struct DscDeleter {
|
||||
void operator()(lv_img_dsc_t* p) const {
|
||||
if (p && p->data) heap_caps_free(const_cast<uint8_t*>(p->data));
|
||||
delete p;
|
||||
}
|
||||
};
|
||||
std::unique_ptr<lv_img_dsc_t, DscDeleter> dsc_;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// 子应用抽象
|
||||
class BaseApp : public Obj {
|
||||
public:
|
||||
explicit BaseApp(const Obj& parent) : Obj(lv_obj_create(parent.raw())), btn_exit_(parent) {
|
||||
lv_obj_set_size(raw(), 360, 360);
|
||||
// 退出按钮
|
||||
btn_exit_.size(40, 40)
|
||||
.align(LV_ALIGN_CENTER, nullptr, 0, -130) // 居中
|
||||
.on(LV_EVENT_CLICKED, [this](lv_event_t *) { this->exit(); });
|
||||
label_.align(LV_ALIGN_CENTER, nullptr, 0, 0);
|
||||
label_.text(LV_SYMBOL_CLOSE);
|
||||
}
|
||||
|
||||
// 子类重写:界面内容
|
||||
virtual void onShow() {} // 进入时调用
|
||||
virtual void onHide() {} // 退出时调用
|
||||
|
||||
// 主动退出
|
||||
void exit() {
|
||||
onHide();
|
||||
// 禁止自己再被点击
|
||||
lv_obj_clear_flag(raw(), LV_OBJ_FLAG_CLICKABLE);
|
||||
|
||||
// 异步自毁:让 LVGL 在下一轮循环再删我们
|
||||
lv_async_call([](void* self) {
|
||||
lv_obj_del(static_cast<lv_obj_t*>(self));
|
||||
}, raw());
|
||||
// 通知工厂回到主页 → 由工厂/菜单负责
|
||||
if (exit_cb_) exit_cb_();
|
||||
}
|
||||
|
||||
// 注册退出通知回调(由 AppFactory 设置)
|
||||
void onExit(std::function<void()> cb) { exit_cb_ = std::move(cb); }
|
||||
|
||||
protected:
|
||||
Button btn_exit_{*this}; // 退出按钮
|
||||
Label label_{btn_exit_}; // 退出图标
|
||||
private:
|
||||
std::function<void()> exit_cb_;
|
||||
};
|
||||
|
||||
// 子应用工厂
|
||||
class AppFactory {
|
||||
public:
|
||||
using Creator = std::function<std::unique_ptr<BaseApp>(Obj& parent)>;
|
||||
// 注册新应用:名字 + lambda 创建器
|
||||
static void registerApp(const char* name, Creator&& creator) {
|
||||
registry()[name] = std::move(creator);
|
||||
}
|
||||
// 创建实例:parent → 传入当前屏幕
|
||||
static std::unique_ptr<BaseApp> create(const char* name, Obj& parent) {
|
||||
auto it = registry().find(name);
|
||||
if (it == registry().end()) return nullptr;
|
||||
return it->second(parent);
|
||||
}
|
||||
private:
|
||||
static std::unordered_map<std::string, Creator>& registry() {
|
||||
static std::unordered_map<std::string, Creator> inst;
|
||||
return inst;
|
||||
}
|
||||
};
|
||||
|
||||
// 应用菜单(网格)
|
||||
class AppMenu : public Obj {
|
||||
public:
|
||||
explicit AppMenu(const Obj& parent) : Obj(lv_obj_create(parent.raw())) {
|
||||
lv_obj_set_size(raw(), 360, 360);
|
||||
lv_obj_set_style_bg_opa(raw(), LV_OPA_TRANSP, 0);
|
||||
lv_obj_set_style_border_width(raw(), 0, 0);
|
||||
lv_obj_clear_flag(raw(), LV_OBJ_FLAG_SCROLLABLE); // 圆形屏建议不要滚动
|
||||
lv_obj_set_style_pad_top(raw(), 60, 0); // ← 新增
|
||||
lv_obj_set_style_pad_bottom(raw(),60, 0); // ← 新增
|
||||
lv_obj_set_style_pad_left(raw(), 20, 0);
|
||||
lv_obj_set_style_pad_right(raw(), 20, 0);
|
||||
|
||||
// 横向 2 列,居中排列
|
||||
lv_obj_set_flex_flow(raw(), LV_FLEX_FLOW_ROW_WRAP);
|
||||
lv_obj_set_flex_align(raw(), LV_FLEX_ALIGN_SPACE_EVENLY,
|
||||
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START);
|
||||
}
|
||||
|
||||
// 添加一个应用图标:图标 bin + 名字
|
||||
AppMenu& addItem(const char* name, // 应用名
|
||||
lv_coord_t w = 140, // 按钮宽
|
||||
lv_coord_t h = 50) // 按钮高
|
||||
{
|
||||
// 每项:一个文字按钮
|
||||
auto* btn = lv_btn_create(raw());
|
||||
lv_obj_set_size(btn, w, h);
|
||||
lv_obj_set_style_radius(btn, 8, 0);
|
||||
|
||||
lv_obj_t* label = lv_label_create(btn);
|
||||
lv_label_set_text(label, name);
|
||||
lv_obj_set_style_text_font(label, &SiYuanHeiTiGoogleBan, LV_PART_MAIN);
|
||||
lv_obj_center(label);
|
||||
|
||||
// 点击 -> 回调
|
||||
lv_obj_add_event_cb(btn, [](lv_event_t* e){
|
||||
const auto* self = static_cast<AppMenu *>(lv_event_get_user_data(e));
|
||||
if (self && self->click_cb_) {
|
||||
const char* txt = lv_label_get_text(
|
||||
lv_obj_get_child(lv_event_get_target(e), 0));
|
||||
self->click_cb_(txt);
|
||||
}
|
||||
}, LV_EVENT_CLICKED, this);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 注册点击回调:返回应用名
|
||||
AppMenu& onClick(std::function<void(const char*)> cb) {
|
||||
click_cb_ = std::move(cb);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
std::function<void(const char*)> click_cb_;
|
||||
};
|
||||
|
||||
// 主页
|
||||
class HomePage : public Obj {
|
||||
public:
|
||||
explicit HomePage(const Obj& parent) : Obj(lv_obj_create(parent.raw())) {
|
||||
lv_obj_set_size(raw(), 360, 360);
|
||||
lv_obj_set_style_radius(raw(), LV_RADIUS_CIRCLE, 0); // 圆形裁切
|
||||
lv_obj_set_style_clip_corner(raw(), true, 0);
|
||||
lv_obj_set_style_border_width(raw(), 0, 0);
|
||||
// 彻底禁止滚动
|
||||
lv_obj_clear_flag(raw(), LV_OBJ_FLAG_SCROLLABLE); // 禁止滚动
|
||||
// lv_obj_set_scroll_dir(raw(), LV_DIR_NONE);
|
||||
|
||||
// 背景图片(默认空,可热切换)
|
||||
bg_img_ = std::make_unique<Image>(*this);
|
||||
|
||||
// 顶栏电池
|
||||
battery_ = std::make_unique<Battery>(*this);
|
||||
battery_->size(60, 30)
|
||||
.percent(true)
|
||||
.align(LV_ALIGN_CENTER, -10, -150);
|
||||
|
||||
// 底栏时间
|
||||
datetime_ = std::make_unique<DateTime>(*this);
|
||||
datetime_->format("%m/%d %a %H:%M")
|
||||
.align(LV_ALIGN_BOTTOM_MID, nullptr, 0, -10);
|
||||
|
||||
// 长按识别:按住 500 ms 即进入应用菜单
|
||||
lv_obj_add_flag(raw(), LV_OBJ_FLAG_CLICKABLE); // 必须置位,否则收不到输入事件
|
||||
on(LV_EVENT_LONG_PRESSED, [this](lv_event_t*) {
|
||||
if (menu_open_cb_) menu_open_cb_();
|
||||
});
|
||||
}
|
||||
|
||||
// 热切换背景
|
||||
HomePage& bg(const char* bin_path, const uint32_t w, const uint32_t h) {
|
||||
bg_img_->bin(bin_path, w, h);
|
||||
bg_img_->center();
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 注册电量回调
|
||||
HomePage& onBattery(std::function<uint8_t()> cb) {
|
||||
battery_->onRead(std::move(cb));
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 注册时间回调
|
||||
HomePage& onDateTime(std::function<void(char*, size_t)> cb) {
|
||||
datetime_->onRead(std::move(cb));
|
||||
return *this;
|
||||
}
|
||||
|
||||
// 注册右滑 → 打开菜单的回调
|
||||
HomePage& onOpenMenu(std::function<void()> cb) {
|
||||
menu_open_cb_ = std::move(cb);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<Image> bg_img_;
|
||||
std::unique_ptr<Battery> battery_;
|
||||
std::unique_ptr<DateTime> datetime_;
|
||||
std::function<void()> menu_open_cb_;
|
||||
};
|
||||
|
||||
} // namespace lvgl
|
||||
|
||||
@@ -92,6 +92,39 @@ std::string SDFileManager::readFileSync(const char* path) {
|
||||
return content;
|
||||
}
|
||||
|
||||
std::string SDFileManager::readFileSync(const std::string& path) {
|
||||
std::lock_guard<std::mutex> lock(file_operation_mutex);
|
||||
|
||||
if (!is_initialized) {
|
||||
ESP_LOGE("SDFileManager", "SD card not initialized");
|
||||
return "";
|
||||
}
|
||||
|
||||
FILE* file = fopen(path.c_str(), "r");
|
||||
if (file == nullptr) {
|
||||
ESP_LOGE("SDFileManager", "Failed to open file: %s", path.c_str());
|
||||
return "";
|
||||
}
|
||||
|
||||
// 获取文件大小
|
||||
fseek(file, 0, SEEK_END);
|
||||
long size = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
// 读取文件内容
|
||||
std::string content;
|
||||
content.resize(size);
|
||||
size_t bytes_read = fread(&content[0], 1, size, file);
|
||||
fclose(file);
|
||||
|
||||
if (bytes_read != static_cast<size_t>(size)) {
|
||||
ESP_LOGE("SDFileManager", "Failed to read entire file: %s", path.c_str());
|
||||
return "";
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
std::vector<std::string> SDFileManager::listFilesSync(const char* directory, const char* extension) {
|
||||
std::lock_guard<std::mutex> lock(file_operation_mutex);
|
||||
|
||||
|
||||
@@ -44,6 +44,13 @@ public:
|
||||
*/
|
||||
std::string readFileSync(const char* path);
|
||||
|
||||
/**
|
||||
* 同步读取文件(std::string)
|
||||
* @param path 文件路径
|
||||
* @return 文件内容
|
||||
*/
|
||||
std::string readFileSync(const std::string& path);
|
||||
|
||||
/**
|
||||
* 同步列出目录下的文件
|
||||
* @param directory 目录路径
|
||||
|
||||
@@ -12,9 +12,46 @@
|
||||
#include <iomanip>
|
||||
#include <algorithm>
|
||||
|
||||
#include "BAT_Driver.h"
|
||||
#include "PWR_Key.h"
|
||||
|
||||
static const char *TAG = "ToolsClass";
|
||||
|
||||
|
||||
ToolsClass* ToolsClass::instance;
|
||||
std::mutex ToolsClass::instance_mutex;
|
||||
ToolsClass *ToolsClass::getInstance() {
|
||||
std::lock_guard<std::mutex> lock(instance_mutex);
|
||||
if (!instance) {
|
||||
instance = new ToolsClass();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
ToolsClass::ToolsClass() {
|
||||
ESP_LOGI(TAG, "ToolsClass init");
|
||||
PWR_Init();
|
||||
BAT_Init(); // 电池驱动初始化
|
||||
}
|
||||
|
||||
float ToolsClass::getBatteryVolts() {
|
||||
return BAT_Get_Volts();
|
||||
}
|
||||
|
||||
|
||||
int32_t ToolsClass::getBatteryPer() {
|
||||
return BAT_Get_Percent();
|
||||
}
|
||||
|
||||
// 静态:拼出完整路径
|
||||
std::string ToolsClass::makeFullPath(const std::string& filename)
|
||||
{
|
||||
return "/sdcard/" + filename;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
std::string ToolsClass::getChipSerialNumber() {
|
||||
// 获取芯片序列号(17 字节长的 "Chip ID" (乐鑫官方唯一序列号))
|
||||
std::array<uint8_t, 17> id{};
|
||||
|
||||
@@ -3,12 +3,25 @@
|
||||
//
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
/**
|
||||
* 本模块提供各种杂项工具类,基本都来源于对底层驱动的封装
|
||||
*
|
||||
*
|
||||
*/
|
||||
class ToolsClass {
|
||||
public:
|
||||
static ToolsClass* getInstance();
|
||||
|
||||
private:
|
||||
ToolsClass();
|
||||
~ToolsClass() = default;
|
||||
|
||||
|
||||
private:
|
||||
static ToolsClass* instance;
|
||||
static std::mutex instance_mutex;
|
||||
|
||||
public:
|
||||
/**
|
||||
* 获取当前时间
|
||||
@@ -37,6 +50,25 @@ public:
|
||||
*/
|
||||
static std::string GenerateSN(const std::string& mac, const std::string& chipID);
|
||||
|
||||
/**
|
||||
* 获取电池电压
|
||||
* @return 电池电压
|
||||
*/
|
||||
float getBatteryVolts();
|
||||
|
||||
/**
|
||||
* 获取电池电量
|
||||
* @return 电池电量百分比(0 ~ 100)
|
||||
*/
|
||||
int32_t getBatteryPer();
|
||||
|
||||
/**
|
||||
* 拼接文件路径(for sd card)
|
||||
* @param filename 文件名
|
||||
* @return 拼接后的文件路径
|
||||
*/
|
||||
static std::string makeFullPath(const std::string& filename);
|
||||
|
||||
/**
|
||||
* 获取设备版本
|
||||
* @return 设备版本
|
||||
|
||||
@@ -115,3 +115,46 @@ float BAT_Get_Volts(void)
|
||||
}
|
||||
return BAT_analogVolts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 滞回锁定百分比(±1 % 死区)
|
||||
* @param pct:实时算出的 0-100
|
||||
* @retval 对外发布的稳定百分比
|
||||
*/
|
||||
int32_t BAT_Percent_Lock(int32_t pct)
|
||||
{
|
||||
static int32_t out = 0; // 上次对外值
|
||||
if (pct >= out + 1) out = pct; // 上升门槛
|
||||
if (pct <= out - 1) out = pct; // 下降门槛
|
||||
return out;
|
||||
}
|
||||
|
||||
static float lpf = 0.0f; // 上一次滤波结果
|
||||
static bool first = true; // 首次赋值标志
|
||||
float BAT_Get_Volts_Filtered(void)
|
||||
{
|
||||
float raw = BAT_Get_Volts(); // 你的原始函数
|
||||
|
||||
if (first) { lpf = raw; first = false; } // 第一次直接赋值
|
||||
lpf = lpf + 0.05f * (raw - lpf); // α=0.05 越小刚越稳
|
||||
return lpf;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @brief 返回电池剩余电量百分比(0~100)
|
||||
* @note 基于 200 k + 100 k 分压,ADC 满量程 1.1 V
|
||||
* @retval 0 已放空(≤ 3.0 V)
|
||||
* 100 满电(≥ 4.2 V) 这里给到4.1V 以放置电池达不到100%的电量
|
||||
* 中间线性
|
||||
*/
|
||||
int32_t BAT_Get_Percent(void)
|
||||
{
|
||||
const float volts = BAT_Get_Volts_Filtered(); // 3.0 ~ 4.2 V -> 0 ~ 100 做映射
|
||||
// 限幅
|
||||
if (volts <= 3.0f) return 0;
|
||||
if (volts >= 4.1f) return 100;
|
||||
// 滞回比较器 + 线性映射
|
||||
return BAT_Percent_Lock((int32_t)((volts - 3.0f) / (4.2f - 3.0f) * 100.0f + 0.5f)); // +0.5 四舍五入
|
||||
}
|
||||
@@ -24,6 +24,8 @@ extern float BAT_analogVolts;
|
||||
void BAT_Init(void);
|
||||
float BAT_Get_Volts(void);
|
||||
|
||||
int32_t BAT_Get_Percent(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -38,6 +38,8 @@ idf_component_register(SRCS "Bionic_sphere.c"
|
||||
"../Bionic_Core/ToolsClass/ToolsClass.cpp" # 工具类库
|
||||
"../Bionic_Core/ToolsClass/AudioOutput/AudioOutput.cpp" # 音频输出类库
|
||||
"../Bionic_Core/ToolsClass/LVGL_Render/LVGLRender.cpp" # LVGL渲染类库
|
||||
"../Bionic_Core/ToolsClass/LVGL_Render/Lvpp/SiYuanHeiTiGoogleBan.c" # 中文字体库
|
||||
"../Bionic_Core/ToolsClass/LVGL_Render/Lvpp/lvpp.cpp"
|
||||
"../Bionic_Core/ToolsClass/SDFileManager/SDFileManager.cpp" # SD文件管理类库
|
||||
"../Bionic_Core/ToolsClass/SpeechRecognizer/SpeechRecognizer.cpp" # 语音识别类库
|
||||
"../Bionic_Core/ToolsClass/WifiConnectors/WifiConnectors.cpp" # WIFI连接类库
|
||||
@@ -76,6 +78,7 @@ idf_component_register(SRCS "Bionic_sphere.c"
|
||||
"../Bionic_Core/ToolsClass"
|
||||
"../Bionic_Core/ToolsClass/AudioOutput"
|
||||
"../Bionic_Core/ToolsClass/LVGL_Render"
|
||||
"../Bionic_Core/ToolsClass/LVGL_Render/Lvpp"
|
||||
"../Bionic_Core/ToolsClass/SDFileManager"
|
||||
"../Bionic_Core/ToolsClass/SpeechRecognizer"
|
||||
"../Bionic_Core/ToolsClass/WifiConnectors"
|
||||
@@ -92,6 +95,7 @@ idf_component_register(SRCS "Bionic_sphere.c"
|
||||
unity
|
||||
esp_lcd
|
||||
lvgl__lvgl
|
||||
lvgl
|
||||
fatfs
|
||||
pthread
|
||||
app_update
|
||||
|
||||
@@ -2775,7 +2775,7 @@ CONFIG_LV_ATTRIBUTE_MEM_ALIGN_SIZE=1
|
||||
CONFIG_LV_FONT_MONTSERRAT_12=y
|
||||
CONFIG_LV_FONT_MONTSERRAT_14=y
|
||||
CONFIG_LV_FONT_MONTSERRAT_16=y
|
||||
# CONFIG_LV_FONT_MONTSERRAT_18 is not set
|
||||
CONFIG_LV_FONT_MONTSERRAT_18=y
|
||||
# CONFIG_LV_FONT_MONTSERRAT_20 is not set
|
||||
# CONFIG_LV_FONT_MONTSERRAT_22 is not set
|
||||
# CONFIG_LV_FONT_MONTSERRAT_24 is not set
|
||||
@@ -2794,7 +2794,7 @@ CONFIG_LV_FONT_MONTSERRAT_16=y
|
||||
# CONFIG_LV_FONT_MONTSERRAT_12_SUBPX is not set
|
||||
# CONFIG_LV_FONT_MONTSERRAT_28_COMPRESSED is not set
|
||||
# CONFIG_LV_FONT_DEJAVU_16_PERSIAN_HEBREW is not set
|
||||
# CONFIG_LV_FONT_SIMSUN_16_CJK is not set
|
||||
CONFIG_LV_FONT_SIMSUN_16_CJK=y
|
||||
# CONFIG_LV_FONT_UNSCII_8 is not set
|
||||
# CONFIG_LV_FONT_UNSCII_16 is not set
|
||||
# CONFIG_LV_FONT_CUSTOM is not set
|
||||
@@ -2802,7 +2802,7 @@ CONFIG_LV_FONT_MONTSERRAT_16=y
|
||||
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_8 is not set
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12 is not set
|
||||
CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14=y
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14 is not set
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_16 is not set
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_18 is not set
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_20 is not set
|
||||
@@ -2823,7 +2823,7 @@ CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14=y
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12_SUBPX is not set
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28_COMPRESSED is not set
|
||||
# CONFIG_LV_FONT_DEFAULT_DEJAVU_16_PERSIAN_HEBREW is not set
|
||||
# CONFIG_LV_FONT_DEFAULT_SIMSUN_16_CJK is not set
|
||||
CONFIG_LV_FONT_DEFAULT_SIMSUN_16_CJK=y
|
||||
# CONFIG_LV_FONT_DEFAULT_UNSCII_8 is not set
|
||||
# CONFIG_LV_FONT_DEFAULT_UNSCII_16 is not set
|
||||
# CONFIG_LV_FONT_FMT_TXT_LARGE is not set
|
||||
@@ -2840,7 +2840,10 @@ CONFIG_LV_TXT_ENC_UTF8=y
|
||||
CONFIG_LV_TXT_BREAK_CHARS=" ,.;:-_"
|
||||
CONFIG_LV_TXT_LINE_BREAK_LONG_LEN=0
|
||||
CONFIG_LV_TXT_COLOR_CMD="#"
|
||||
# CONFIG_LV_USE_BIDI is not set
|
||||
CONFIG_LV_USE_BIDI=y
|
||||
# CONFIG_LV_BIDI_DIR_LTR is not set
|
||||
# CONFIG_LV_BIDI_DIR_RTL is not set
|
||||
CONFIG_LV_BIDI_DIR_AUTO=y
|
||||
# CONFIG_LV_USE_ARABIC_PERSIAN_CHARS is not set
|
||||
# end of Text Settings
|
||||
|
||||
@@ -2920,7 +2923,7 @@ CONFIG_LV_USE_GRID=y
|
||||
# CONFIG_LV_USE_FS_WIN32 is not set
|
||||
# CONFIG_LV_USE_FS_FATFS is not set
|
||||
# CONFIG_LV_USE_FS_LITTLEFS is not set
|
||||
# CONFIG_LV_USE_PNG is not set
|
||||
CONFIG_LV_USE_PNG=y
|
||||
# CONFIG_LV_USE_BMP is not set
|
||||
# CONFIG_LV_USE_SJPG is not set
|
||||
CONFIG_LV_USE_GIF=y
|
||||
|
||||
+13
-8
@@ -777,7 +777,8 @@ CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2
|
||||
# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set
|
||||
CONFIG_COMPILER_HIDE_PATHS_MACROS=y
|
||||
# CONFIG_COMPILER_CXX_EXCEPTIONS is not set
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=0
|
||||
CONFIG_COMPILER_CXX_RTTI=y
|
||||
CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y
|
||||
# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set
|
||||
@@ -2774,7 +2775,7 @@ CONFIG_LV_ATTRIBUTE_MEM_ALIGN_SIZE=1
|
||||
CONFIG_LV_FONT_MONTSERRAT_12=y
|
||||
CONFIG_LV_FONT_MONTSERRAT_14=y
|
||||
CONFIG_LV_FONT_MONTSERRAT_16=y
|
||||
# CONFIG_LV_FONT_MONTSERRAT_18 is not set
|
||||
CONFIG_LV_FONT_MONTSERRAT_18=y
|
||||
# CONFIG_LV_FONT_MONTSERRAT_20 is not set
|
||||
# CONFIG_LV_FONT_MONTSERRAT_22 is not set
|
||||
# CONFIG_LV_FONT_MONTSERRAT_24 is not set
|
||||
@@ -2793,7 +2794,7 @@ CONFIG_LV_FONT_MONTSERRAT_16=y
|
||||
# CONFIG_LV_FONT_MONTSERRAT_12_SUBPX is not set
|
||||
# CONFIG_LV_FONT_MONTSERRAT_28_COMPRESSED is not set
|
||||
# CONFIG_LV_FONT_DEJAVU_16_PERSIAN_HEBREW is not set
|
||||
# CONFIG_LV_FONT_SIMSUN_16_CJK is not set
|
||||
CONFIG_LV_FONT_SIMSUN_16_CJK=y
|
||||
# CONFIG_LV_FONT_UNSCII_8 is not set
|
||||
# CONFIG_LV_FONT_UNSCII_16 is not set
|
||||
# CONFIG_LV_FONT_CUSTOM is not set
|
||||
@@ -2801,7 +2802,7 @@ CONFIG_LV_FONT_MONTSERRAT_16=y
|
||||
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_8 is not set
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12 is not set
|
||||
CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14=y
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14 is not set
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_16 is not set
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_18 is not set
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_20 is not set
|
||||
@@ -2822,7 +2823,7 @@ CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14=y
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12_SUBPX is not set
|
||||
# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28_COMPRESSED is not set
|
||||
# CONFIG_LV_FONT_DEFAULT_DEJAVU_16_PERSIAN_HEBREW is not set
|
||||
# CONFIG_LV_FONT_DEFAULT_SIMSUN_16_CJK is not set
|
||||
CONFIG_LV_FONT_DEFAULT_SIMSUN_16_CJK=y
|
||||
# CONFIG_LV_FONT_DEFAULT_UNSCII_8 is not set
|
||||
# CONFIG_LV_FONT_DEFAULT_UNSCII_16 is not set
|
||||
# CONFIG_LV_FONT_FMT_TXT_LARGE is not set
|
||||
@@ -2839,7 +2840,10 @@ CONFIG_LV_TXT_ENC_UTF8=y
|
||||
CONFIG_LV_TXT_BREAK_CHARS=" ,.;:-_"
|
||||
CONFIG_LV_TXT_LINE_BREAK_LONG_LEN=0
|
||||
CONFIG_LV_TXT_COLOR_CMD="#"
|
||||
# CONFIG_LV_USE_BIDI is not set
|
||||
CONFIG_LV_USE_BIDI=y
|
||||
# CONFIG_LV_BIDI_DIR_LTR is not set
|
||||
# CONFIG_LV_BIDI_DIR_RTL is not set
|
||||
CONFIG_LV_BIDI_DIR_AUTO=y
|
||||
# CONFIG_LV_USE_ARABIC_PERSIAN_CHARS is not set
|
||||
# end of Text Settings
|
||||
|
||||
@@ -2919,7 +2923,7 @@ CONFIG_LV_USE_GRID=y
|
||||
# CONFIG_LV_USE_FS_WIN32 is not set
|
||||
# CONFIG_LV_USE_FS_FATFS is not set
|
||||
# CONFIG_LV_USE_FS_LITTLEFS is not set
|
||||
# CONFIG_LV_USE_PNG is not set
|
||||
CONFIG_LV_USE_PNG=y
|
||||
# CONFIG_LV_USE_BMP is not set
|
||||
# CONFIG_LV_USE_SJPG is not set
|
||||
CONFIG_LV_USE_GIF=y
|
||||
@@ -2995,7 +2999,8 @@ CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y
|
||||
# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set
|
||||
# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set
|
||||
CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2
|
||||
# CONFIG_CXX_EXCEPTIONS is not set
|
||||
CONFIG_CXX_EXCEPTIONS=y
|
||||
CONFIG_CXX_EXCEPTIONS_EMG_POOL_SIZE=0
|
||||
CONFIG_STACK_CHECK_NONE=y
|
||||
# CONFIG_STACK_CHECK_NORM is not set
|
||||
# CONFIG_STACK_CHECK_STRONG is not set
|
||||
|
||||
Reference in New Issue
Block a user