完成一个主界面开发流程

This commit is contained in:
2026-09-03 23:08:16 +08:00
parent 52d7dc7d4d
commit 89462d8aa1
21 changed files with 2177 additions and 198 deletions
+104 -17
View File
@@ -7,10 +7,16 @@
* ↑/↓/←/→ → 方向键导航
* Enter → 确认/进入
* Escape → 返回
*
* 截图模式(无窗口离屏渲染,导出像素供比对):
* lvgl_sim.exe --screenshot shot.bin
* 走与 MCU 端一致的离屏软件渲染(ARGB8888 缓冲 + mono 主题),
* 渲染首帧后将 160x160 像素按 BGRA 顺序写入 shot.bin(102400 字节),随即退出。
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#ifdef _MSC_VER
#include <Windows.h>
@@ -29,13 +35,104 @@
#define SIM_SCREEN_W 160
#define SIM_SCREEN_H 160
/* 离屏截图缓冲:I1(1bpp,与 MCU/模拟器窗口一致;软件渲染器仅支持 I1),160x160/8 */
static uint8_t g_shot_buf[SIM_SCREEN_W * SIM_SCREEN_H / 8];
/*
* @brief 离屏截图用空 delay 回调
* @note 默认 lv_delay_ms 会忙等 tick 前进,但离屏渲染时 tick 不会在等待期间推进,
* 导致死循环挂死。window 模式经 SDL 设了真实 delay,离屏这里用空实现代替。
*/
static void shot_delay_cb(uint32_t ms)
{
(void)ms;
}
/*
* @brief 离屏截图 flush 回调(不写真实设备,直接标记就绪)
*/
static void shot_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p)
{
(void)area;
(void)color_p;
lv_display_flush_ready(disp);
}
/*
* @brief 无窗口离屏渲染并导出首帧像素
* @param path 输出文件(写入 160x160 BGRA 原始像素)
* @return 0 成功,非 0 失败
*/
static int run_screenshot(const char * path)
{
setvbuf(stdout, NULL, _IONBF, 0);
lv_init();
lv_delay_set_cb(shot_delay_cb);
lv_display_t *disp = lv_display_create(SIM_SCREEN_W, SIM_SCREEN_H);
if (disp == NULL) {
return 1;
}
lv_display_set_default(disp);
lv_display_set_color_format(disp, LV_COLOR_FORMAT_I1);
lv_display_set_buffers(disp, g_shot_buf, NULL, sizeof(g_shot_buf),
LV_DISPLAY_RENDER_MODE_FULL);
lv_display_set_flush_cb(disp, shot_flush_cb);
lv_group_t *g = lv_group_create();
if (g == NULL) {
return 1;
}
lv_group_set_default(g);
#if LV_USE_THEME_MONO
lv_theme_t *th = lv_theme_mono_init(disp, true, LV_FONT_DEFAULT);
if (th != NULL) {
lv_display_set_theme(disp, th);
}
#endif
if (ui_menu_init() != 0) {
return 1;
}
/* 用 snapshot 把整屏渲进独立 I1 draw_buf(避开 I1+FULL 的渲染死循环) */
lv_draw_buf_t *snap = lv_snapshot_take(lv_screen_active(), LV_COLOR_FORMAT_I1);
if (snap == NULL) {
fprintf(stderr, "[shot] snapshot failed\n");
return 1;
}
uint32_t snap_w = snap->header.w;
uint32_t snap_h = snap->header.h;
/* I1 缓冲区布局:[palette 8B][pixel data],跳过 palette 只写像素 */
uint32_t pal_bytes = LV_COLOR_INDEXED_PALETTE_SIZE(LV_COLOR_FORMAT_I1) * (uint32_t)sizeof(lv_color32_t);
uint8_t * px = snap->data + pal_bytes;
uint32_t px_bytes = (uint32_t)snap->header.stride * (uint32_t)snap_h;
FILE *fp = fopen(path, "wb");
if (fp == NULL) {
lv_draw_buf_destroy(snap);
fprintf(stderr, "fopen failed: %s\n", path);
return 1;
}
size_t n = fwrite(px, 1, px_bytes, fp);
fclose(fp);
lv_draw_buf_destroy(snap);
printf("screenshot: %s (%zu bytes, %ux%u I1)\n", path, n, snap_w, snap_h);
return 0;
}
/**
* @brief 程序入口
*/
int main(int argc, char **argv)
{
(void)argc;
(void)argv;
/* 截图模式:lvgl_sim.exe --screenshot <path> */
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--screenshot") == 0 && (i + 1) < argc) {
return run_screenshot(argv[i + 1]);
}
}
/* 初始化 SDL2(仅 timer + events,视频由 lv_sdl_window 内部初始化) */
if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_EVENTS) != 0) {
@@ -71,22 +168,12 @@ int main(int argc, char **argv)
printf("Keyboard: ↑↓←→ navigate, Enter confirm, Escape back\n");
printf("Press Ctrl+C or close window to exit\n\n");
/* 主循环:处理 SDL 事件 + 驱动 LVGL 刷新 */
/* 主循环:驱动 LVGL 定时器与刷新
* 注意:SDL 事件(键盘/窗口/退出)由 LVGL SDL 驱动内部 5ms 定时器
* (lv_sdl_window.c 的 sdl_event_handler)统一轮询分发,
* 本循环不得再调 SDL_PollEvent,否则会先消费事件队列,
* 导致键盘输入永远到不了 LVGL */
while (1) {
/* 处理 SDL 事件(窗口关闭等) */
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
printf("SDL_QUIT received, exiting...\n");
SDL_Quit();
return 0;
}
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_ESCAPE) {
/* Escape 键:如果在子页面则返回上一级 */
/* LVGL keyboard 驱动已自动处理 LV_KEY_ESC */
}
}
/* 驱动 LVGL 定时器与刷新 */
uint32_t sleep_ms = lv_timer_handler();
if (sleep_ms == LV_NO_TIMER_READY) {