- 嵌入式
- 物联网
- 智能硬件
- 嵌入式GUI
- 硬件开发
【免费下载链接】HoloCubic
带网络功能的伪全息透明显示桌面站
导读
HoloCubic 是一款带网络功能的伪全息透明显示桌面站,其图形界面基于 LVGL 构建。在开发过程中,硬件交互并不总依赖触摸屏——LVGL 本身支持通过键盘(Keypad)与编码器(Encoder)来操作全部控件,而本仓库LvglSimulator子项目中的lv_demo_keypad_encoder正是这一能力的完整演示:它展示了按钮、复选框、下拉列表、滚动轮、滑块、开关、微调框、列表与文本输入框如何在完全没有触摸设备的情况下被逐项聚焦、选中与编辑。阅读本文后,你将掌握该 Demo 的启用方式、核心交互模型(对象组 Group、导航/编辑双模式、虚拟键盘联动),并能把同一套机制复用到 HoloCubic 嵌入式固件或 PC 模拟器中。
一、Demo 是什么:无触摸交互的完整控件集演示
1.1 官方文档的定位
关联文档 README.md 明确指出:
LVGL allows you to control the widgets with keypad and/or encoder without touchpad. This demo shows how to handle buttons, drop-down lists, rollers, sliders, switches and text inputs without touchpad.
即:LVGL 允许在没有触摸屏的前提下,仅靠键盘和/或编码器控制控件;本 Demo 覆盖按钮、下拉列表、滚动轮、滑块、开关和文本输入等控件的无触摸操作方式。它属于官方 lv_examples 仓库的示例之一,被完整收纳进本仓库的 PC 模拟器工程。
1.2 Demo 的运行截图
下图是该 Demo 在 800×480 模拟器窗口中的实际运行效果,界面顶部是 "Selectors" 与 "Text input" 两个标签页,分别承载各类选择型控件与文本输入控件:
1.3 Demo 覆盖的控件清单
从源码 lv_demo_keypad_encoder.c 的结构体定义可以看到,本 Demo 共演示8 类选择型控件 + 2 个文本输入框:
- Button(按钮):
selector_objs.btn - Checkbox(复选框):
selector_objs.cb - Slider(滑块):
selector_objs.slider,范围被设置为 0~10(lv_slider_set_range(slider, 0, 10)) - Switch(开关):
selector_objs.sw - Spinbox(微调框):
selector_objs.spinbox - Dropdown(下拉列表):
selector_objs.dropdown - Roller(滚动轮):
selector_objs.roller - List(列表):
selector_objs.list,内含 7 个带图标按钮的列表项(Apply、Close、Show、Hide、Delete、Copy、Paste,图标取自LV_SYMBOL_OK、LV_SYMBOL_CLOSE等内置符号) - Textarea(文本框):
textinput_objs.ta1与textinput_objs.ta2,其中 ta1 设置为单行、隐藏光标、带 "Type something" 占位符
这些控件被组织在lv_tabview(标签视图)的两个标签页中:第一页 "Selectors" 承载前 8 类选择型控件,第二页 "Text input" 承载两个文本框,见 lv_demo_keypad_encoder.c。
二、如何运行这个 Demo
2.1 启用宏:lv_ex_conf.h
按官方文档要求,在 lv_ex_conf.h 中开启:
/*Demonstrate the usage of encoder and keyboard*/ #define LV_USE_DEMO_KEYPAD_AND_ENCODER 1同时,为了让 Demo 中的键盘与"编码器"驱动真正生效,还需要确保同一配置文件中两个输入设备支持宏也为 1(当前仓库默认均已开启):
#define LV_EX_KEYBOARD 1 /*Add PC keyboard support to some examples (`lv_drivers` repository is required)*/ #define LV_EX_MOUSEWHEEL 1 /*Add 'encoder' (mouse wheel) support to some examples (`lv_drivers` repository is required)*/对应地,驱动层配置 lv_drv_conf.h 中USE_KEYBOARD与USE_MOUSEWHEEL同样默认为 1,二者相互配合才能把 PC 键盘与鼠标滚轮桥接为 LVGL 输入设备。
2.2 调用入口:lv_demo_keypad_encoder()
官方文档要求:"Afterlv_init()and initializing the drivers calllv_demo_keypad_encoder()"。在模拟器主程序 lvgl_similator.cpp 中,可以看到它在lv_init()与hal_init()之后调用该函数(其余 Demo 均被注释):
lv_init(); hal_init(); //lv_demo_widgets(); //lv_demo_benchmark(); lv_demo_keypad_encoder(); //lv_demo_printer(); //lv_demo_stress();hal_init()内部完成了显示驱动(monitor_init(),SDL 窗口模拟显示屏)、鼠标指针输入设备、键盘输入设备(USE_KEYBOARD分支下注册keyboard_read)、以及每 5ms 触发一次的lv_tick_inc(5)心跳线程的初始化。Demo 的声明位于 lv_demo_keypad_encoder.h,整体实现包在#if LV_USE_DEMO_KEYPAD_AND_ENCODER条件编译中。
2.3 演示中的操作方式
- 键盘:方向键在控件间移动焦点;
Enter确认/按下;Esc取消;Backspace删除(由 SDL 键盘驱动映射)。 - 滚轮(模拟编码器):滚动滚轮 = 编码器旋转,用于在控件间切换焦点或调整数值;按下鼠标中键= 编码器按钮(Enter)。
- 文本输入:聚焦文本框后会自动弹出 LVGL 虚拟键盘(on-screen keyboard),通过键盘/编码器按键进行输入,
Apply或Cancel后关闭。
三、核心机制一:对象组(Group)——键盘/编码器导航的基础
3.1 为什么需要 Group
触摸输入靠坐标命中控件,而键盘/编码器没有坐标,只能"按顺序"在控件间移动焦点。LVGL 用Group(对象组)解决这个问题:只有加入某个 Group 的控件,才会被该 Group 关联的输入设备遍历、聚焦和接收按键。
Demo 在 lv_demo_keypad_encoder.c 中创建了全局组g,并把两个输入设备都绑定到它:
g = lv_group_create(); lv_group_set_focus_cb(g, focus_cb); #if LV_EX_KEYBOARD lv_indev_drv_t kb_drv; lv_indev_drv_init(&kb_drv); kb_drv.type = LV_INDEV_TYPE_KEYPAD; /*键盘输入设备*/ kb_drv.read_cb = keyboard_read; /*从 PC 键盘读取按键*/ lv_indev_t* kb_indev = lv_indev_drv_register(&kb_drv); lv_indev_set_group(kb_indev, g); /*绑定到组 g*/ #endif #if LV_EX_MOUSEWHEEL lv_indev_drv_t enc_drv; lv_indev_drv_init(&enc_drv); enc_drv.type = LV_INDEV_TYPE_ENCODER; /*编码器输入设备*/ enc_drv.read_cb = mousewheel_read; /*从鼠标滚轮读取旋量*/ lv_indev_t* enc_indev = lv_indev_drv_register(&enc_drv); lv_indev_set_group(enc_indev, g); #endif启用前提是LV_USE_GROUP在 lv_conf.h 中被置为 1(当前仓库已开启),否则lv_group_t相关 API 不会编译。
3.2 控件如何入组
Demo 采用"按当前标签页动态清空/重建组"的策略。标签页切换事件回调tv_event_cb会先把所有对象移出组:
lv_group_remove_all_objs(g); /*清空组*/ ... uint16_t tab = lv_tabview_get_tab_act(tv); /*取当前活动标签页*/ if (tab == 0) { size = sizeof(selector_objs); objs = (lv_obj_t**)&selector_objs; } else if (tab == 1) { size = sizeof(textinput_objs); objs = (lv_obj_t**)&textinput_objs; } lv_group_add_obj(g, tv); /*先加入 tabview 本身*/ for (i = 0; i < size / sizeof(lv_obj_t*); i++) /*再逐个加入该页控件*/ { if (objs[i] == NULL) continue; lv_group_add_obj(g, objs[i]); }见 lv_demo_keypad_encoder.c。这样一来,每个标签页的焦点序列就是"tabview → 页内全部控件",切换页后焦点集合自动更新,避免了把所有控件塞进一个组导致的焦点顺序混乱。
3.3 焦点回调focus_cb:自动滚动页面
组设置焦点回调lv_group_set_focus_cb(g, focus_cb),当焦点移动到组内某对象时触发。Demo 的focus_cb根据当前标签页把对应控件滚动到可视区域(带动画):
static void focus_cb(lv_group_t* group) { lv_obj_t* obj = lv_group_get_focused(group); if (obj != tv) { uint16_t tab = lv_tabview_get_tab_act(tv); switch (tab) { case 0: lv_page_focus(t1, obj, LV_ANIM_ON); break; /*Selectors 页*/ case 1: lv_page_focus(t2, obj, LV_ANIM_ON); break; /*Text input 页*/ } } }见 lv_demo_keypad_encoder.c。这在控件数量超过一屏时尤为重要——焦点落到屏幕外的控件时,页面会自动滚动跟随,保证用户始终能看到当前聚焦项。
四、核心机制二:导航模式与编辑模式(navigate/edit mode)
4.1 双模式的定义
编码器交互天然需要两种操作语义:
- 导航模式(navigate):旋转编码器 = 在控件之间移动焦点(
lv_group_focus_next/prev)。 - 编辑模式(edit):旋转编码器 = 调整当前控件的值(向左/向右发送
LV_KEY_LEFT/RIGHT),例如滑块的数值、滚轮的选项。
这两个模式由组的editing标志控制,对应 API 为 lv_group_set_editing 与 lv_group_get_editing。
4.2 模式切换与按键处理的内核实现
LVGL 核心在处理编码器输入设备时(lv_indev.c)实现了完整的状态机:
- 短按 Enter:对可编辑控件,如果当前不在编辑模式,则进入编辑模式(
lv_group_set_editing(g, true),见 lv_indev.c);对不可编辑控件,直接发送LV_EVENT_CLICKED等点击事件。 - 长按 Enter:在编辑模式与导航模式之间来回切换(
lv_group_set_editing(g, lv_group_get_editing(g) ? false : true),见 lv_indev.c)。 - 旋转编码器:编辑模式下将每个 tick 转为
LV_KEY_LEFT/RIGHT发送给焦点控件;导航模式下执行lv_group_focus_next/prev移动焦点(见 lv_indev.c)。 - 键盘左/右键:在编码器处理逻辑中还会被转换为
enc_diff--/enc_diff++,即用键盘方向键也能模拟编码器旋转(见 lv_indev.c)。
此外,LV_KEY_ENTER按下时会先查询控件的可编辑性(LV_SIGNAL_GET_EDITABLE),再决定是直接"按下"还是进入编辑模式,这保证了 Button、Switch 等控件与 Slider、Spinbox 等控件在按键语义上的正确区分。
4.3 Demo 对编辑模式的使用
Demo 在弹出欢迎消息框时,针对编码器场景直接置为编辑模式并冻结焦点导航:
lv_group_add_obj(g, mbox); lv_group_focus_obj(mbox); #if LV_EX_MOUSEWHEEL lv_group_set_editing(g, true); /*进入编辑模式*/ #endif lv_group_focus_freeze(g, true); /*冻结焦点移动,只能操作当前对象*/见 lv_demo_keypad_encoder.c。lv_group_focus_freeze(g, true)对应内核函数 lv_group_focus_freeze,用于在模态弹窗期间锁死焦点,避免滚轮误切焦点。用户选择 "Ok" 或 "Cancel" 后,msgbox_event_cb删除消息框、恢复lv_layer_top()样式,并向 tabview 发送LV_EVENT_REFRESH重新建立焦点集合(见 lv_demo_keypad_encoder.c)。
五、核心机制三:聚焦文本框时自动弹出虚拟键盘
5.1 交互流程
"Text input" 标签页演示了无触摸环境下的文本输入:聚焦文本框后自动创建 LVGL 虚拟键盘控件(lv_keyboard_create),并与之联动。关键代码在ta_event_cb(lv_demo_keypad_encoder.c):
if (e == LV_EVENT_FOCUSED) { lv_textarea_set_cursor_hidden(ta, false); /*显示光标*/ if (lv_group_get_editing(g)) { /*处于编辑模式*/ if (textinput_objs.kb == NULL) { textinput_objs.kb = lv_keyboard_create(lv_scr_act(), NULL); /*创建虚拟键盘*/ lv_group_add_obj(g, textinput_objs.kb); /*键盘也加入组,可被焦点导航*/ lv_obj_set_event_cb(textinput_objs.kb, kb_event_cb); lv_obj_set_height(tv, LV_VER_RES - lv_obj_get_height(textinput_objs.kb)); /*压缩 tabview 高度*/ } lv_keyboard_set_textarea(textinput_objs.kb, ta); /*键盘与文本框绑定*/ lv_group_focus_obj(textinput_objs.kb); /*焦点跳到键盘*/ lv_group_set_editing(g, true); lv_page_focus(t2, lv_textarea_get_label(ta), LV_ANIM_ON); } }这里体现了两个设计要点:
- 虚拟键盘本身也是一个 LVGL 控件,加入组
g后即可被编码器/键盘导航与操作; - 弹出键盘时把 tabview 高度压缩为
LV_VER_RES - 键盘高度,避免键盘遮挡文本区域。
5.2 键盘的关闭
kb_event_cb在用户点击虚拟键盘的 Apply / Cancel 时销毁键盘并恢复 tabview 高度:
static void kb_event_cb(lv_obj_t* kb, lv_event_t e) { lv_keyboard_def_event_cb(kb, e); /*先走默认处理*/ if (e == LV_EVENT_APPLY || e == LV_EVENT_CANCEL) { lv_group_focus_obj(lv_keyboard_get_textarea(kb)); /*焦点还给文本框*/ lv_obj_del(kb); textinput_objs.kb = NULL; lv_obj_set_height(tv, LV_VER_RES); /*恢复高度*/ } }见 lv_demo_keypad_encoder.c。
六、驱动层与映射细节:PC 键盘、鼠标滚轮如何变成 LVGL 输入设备
6.1 键盘驱动:SDL 按键 → LVGL 按键码
驱动 keyboard.c 由 SDL 事件线程调用keyboard_handler()记录按键与按下状态,LVGL 周期性调用keyboard_read()读取。关键映射在keycode_to_ascii()(keyboard.c):
| SDL 按键 | LVGL 键码 | 用途 |
|---|---|---|
SDLK_RIGHT/SDLK_KP_PLUS | LV_KEY_RIGHT | 焦点右移 / 数值增大 |
SDLK_LEFT/SDLK_KP_MINUS | LV_KEY_LEFT | 焦点左移 / 数值减小 |
SDLK_UP | LV_KEY_UP | 焦点上移 |
SDLK_DOWN | LV_KEY_DOWN | 焦点下移 |
SDLK_ESCAPE | LV_KEY_ESC | 取消/退出 |
SDLK_BACKSPACE | LV_KEY_BACKSPACE | 删除 |
SDLK_DELETE | LV_KEY_DEL | 删除 |
SDLK_KP_ENTER/\r | LV_KEY_ENTER | 确认/按下 |
普通字符键则原样透传(default: return sdl_key;),从而可以直接在文本框中输入字母与数字。
6.2 滚轮驱动:鼠标滚轮 → 编码器
驱动 mousewheel.c 维护一个enc_diff累计旋量与按下状态,mousewheel_read()每次读取后将其清零(enc_diff = 0)。事件处理:
case SDL_MOUSEWHEEL: enc_diff = -event->wheel.y; /*向下滚动 = 正向旋转*/ break; case SDL_MOUSEBUTTONDOWN: if (event->button.button == SDL_BUTTON_MIDDLE) state = LV_INDEV_STATE_PR; /*中键=编码器按钮*/ break; case SDL_MOUSEBUTTONUP: if (event->button.button == SDL_BUTTON_MIDDLE) state = LV_INDEV_STATE_REL; break;见 mousewheel.c。在嵌入式环境中,把这段逻辑替换为读取真实旋转编码器 GPIO(例如定时器计数增量)与按键状态即可,接口完全一致(enc_diff+state)。
6.3 与触摸输入的共存
模拟器hal_init()同时注册了鼠标指针输入设备(LV_INDEV_TYPE_POINTER)与键盘输入设备。指针设备不依赖 Group,而键盘/编码器输入设备依赖 Group;两者可共存于同一应用——触摸点击与按键导航并行工作,这是 LVGL 输入设备模型的多设备特性。
七、把 Demo 机制移植到 HoloCubic 固件的要点
HoloCubic 的嵌入式固件位于2.Firmware/HoloCubic-fw,其输入系统同样基于本仓库配套的 LVGL 库(见lib/lvgl),因此本 Demo 的交互模型可直接借鉴:
- 创建 Group 并绑定输入设备:固件端 lv_port_indev.c 注册触摸/按键输入设备后,为需要键盘导航的界面创建
lv_group_create()并调用lv_indev_set_group()。 - 控件入组:把可导航控件逐个
lv_group_add_obj();多页面场景参考 Demo 的"切页重建组"策略,避免焦点落在不可见控件上。 - 设置焦点回调:结合滚动容器使用
lv_page_focus(),让焦点始终可见。 - 处理编辑模式:若硬件只有旋转编码器,依赖内核默认的"短按进入编辑、长按切换模式"即可;若要固定某一控件处于编辑状态,调用
lv_group_set_editing(g, true)。 - 文本输入:需要输入时创建
lv_keyboard并与 textarea 绑定,输入完成销毁,流程与ta_event_cb/kb_event_cb一致。
需要注意,本仓库模拟器使用的 LVGL 为v7.6.1时代接口(lv_drv_conf.h中标注 "Configuration file for v7.6.1"),函数签名如lv_obj_set_event_cb、lv_group_focus_freeze等与 v8/v9 存在差异;移植到新版本时需对照新版 API 调整。
八、小结
lv_demo_keypad_encoder以不到 340 行源码完整演示了 LVGL 无触摸交互的全部关键机制:
- Group 对象组:定义焦点遍历序列,
lv_group_add_obj/lv_group_focus_next/prev/lv_group_set_focus_cb构成导航骨架; - 导航/编辑双模式:编码器在移动焦点与调整数值间切换,内核在 lv_indev.c 中统一实现,短按/长按/旋转三种输入被映射为不同语义;
- 动态焦点重建与模态冻结:标签页切换时重建组、弹窗期间
lv_group_focus_freeze,保证焦点行为可控; - 虚拟键盘联动:文本框聚焦时弹出、Apply/Cancel 时销毁,兼顾编码器与键盘两种输入源。
对 HoloCubic 这类桌面显示站而言,若硬件方案包含旋转编码器或实体按键,这套机制即可作为界面导航与输入的现成范式;即便在 PC 上做 UI 调试,也可以像本仓库 lvgl_similator.cpp 一样,用鼠标滚轮 + 键盘精确复现真实硬件的操作手感。
- 嵌入式
- 物联网
- 智能硬件
- 嵌入式GUI
- 硬件开发
【免费下载链接】HoloCubic
带网络功能的伪全息透明显示桌面站
相关推荐
wechat-bot 让微信 AI 自动回复:从扫码登录到群值班的 20 分钟教程
wechat bot 让微信 AI 自动回复:从扫码登录到群值班的 20 分钟教程 五百人的工作群里,一条重要通知一分钟就被刷走,等你打开手机,话题已经翻篇十楼
AI 应用人工智能AI Agent即时通讯LVGL 无触摸屏控件导航:Keypad and Encoder 演示工程源码解析与移植参考
LVGL 无触摸屏控件导航:Keypad and Encoder 演示工程源码解析与移植参考 导读 LVGL(本仓库内为 v7.6.1)并不强制要求触摸屏才能操
嵌入式物联网智能硬件嵌入式GUI硬件开发Cangjie-SIG/RGF_CJ触摸支持:触摸屏的交互实现
Cangjie SIG/RGF_CJ触摸支持:触摸屏的交互实现 还在为Windows桌面应用的触摸交互开发而烦恼吗?RGF_CJ框架提供了完整的触摸事件支持,让
图形学桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考