Slint 打印机触摸屏 Demo(printerdemo)深入解析:从多语言 UI 设计到跨平台实现
【免费下载链接】slintSlint is an open-source declarative GUI toolkit to build native user interfaces for Rust, C++, JavaScript, or Python apps.项目地址: https://gitcode.com/GitHub_Trending/sl/slint
导读
demos/printerdemo是 Slint 开源仓库中的一个虚构打印机触摸屏界面示例,由 Matthias Grimme 设计原始界面。它以一份声明式.slint设计文件为核心,通过 Rust、C++、JavaScript(Node.js)、Python 以及 Zephyr RTOS 五种实现方式,完整展示了 Slint 框架"一次设计、多语言复用"的核心理念。本文将以该示例为主线,深入剖析其 UI 组件结构、主题切换机制、数据绑定与回调体系,并结合各语言后端源码与 Zephyr 嵌入式部署流程,说明如何在真实项目中复现这套"设计即代码"的开发范式。
示例概览:一个虚构的打印机控制面板
printerdemo模拟了一台打印机的触摸屏控制界面,涵盖打印、扫描、复印、USB 读取四个核心功能入口,以及打印队列、墨量显示、系统设置等配套页面。仓库中该示例的目录布局如下:
- demos/printerdemo/ui:全部
.slint设计文件、字体与图片资源,是整个示例的"设计源"; - demos/printerdemo/rust:Rust 实现(
lib.rs+main.rs); - demos/printerdemo/cpp 与 demos/printerdemo/cpp_interpreted:原生 C++ 与解释式 C++ 实现;
- demos/printerdemo/node:Node.js 实现;
- demos/printerdemo/python:Python 实现;
- demos/printerdemo/zephyr:面向 Zephyr RTOS 的嵌入式实现(含 README.md 与 README_NXP.md);
- demos/printerdemo/lang:法语(fr)翻译文件(
.po/.mo),用于演示运行时国际化。
设计文件的总入口是 demos/printerdemo/ui/printerdemo.slint,它通过import语句把侧边栏、各功能页面与全局状态聚合为一个MainWindow:
import { DemoPalette, PrinterState, InkLevel, Label, Page } from "common.slint"; import { HomePage, InkPage, SettingsPage, PrinterSettings } from "./pages/pages.slint"; import { PrinterQueue } from "./pages/printer_queue.slint"; import { Sidebar } from "components/sidebar.slint"; // re-export for the native code export { PrinterQueue, PrinterSettings, PrinterState, InkLevel } export component MainWindow inherits Window { callback quit(); min-width: 772px; min-height: 504px; title: @tr("Slint printer demo"); default-font-family: DemoPalette.medium-weight; default-font-size: DemoPalette.base-font-size; Rectangle { border-radius: 24px; clip: true; background: white; HorizontalLayout { side-bar := Sidebar { } main-view := Rectangle { if PrinterState.active-page == Page.home: home-page := HomePage { } if PrinterState.active-page == Page.settings: settings-page := SettingsPage { } if PrinterState.active-page == Page.ink: ink-page := InkPage { } } } } }从这里可以看到 Slint 声明式 UI 的几个核心语法特征:inherits Window继承内建窗口组件、callback声明对外回调、@tr()标记可翻译文本、if条件渲染页面,以及export { ... }把组件与类型导出给 Rust/C++/JS 等宿主语言代码使用。
页面结构与导航:从 Sidebar 到四个功能子页
MainWindow通过HorizontalLayout将界面分为左侧固定侧边栏(Sidebar)和右侧主视图区。主视图依据全局状态PrinterState.active-page(值为Page.home/Page.settings/Page.ink枚举)条件渲染三个一级页面。
首页(HomePage)与功能入口
ui/pages/home_page.slint 中定义了ActionButton组件,四个功能按钮(Print / Scan / Copy / USB)通过for action[index] in [...]循环生成。每个按钮的图标、文案、背景与文字颜色均来自DemoPalette主题系统,点击后设置PrinterState.active-subpage以切换子页面。子页面(PrintPage、ScanPage、CopyPage、UsbPage)通过x坐标偏移与animate x { duration: 125ms; easing: ease; }实现滑入/滑出动画,back回调将active-subpage重置为SubPage.none返回首页。这里体现了 Slint 中"属性绑定 + 内建动画"组合实现页面转场的典型写法。
打印队列视图的宽窄两态
ui/pages/printer_queue.slint 定义了PrinterQueueItem结构体与全局PrinterQueue(含默认的 4 个示例任务、start-job/cancel-job/pause-job三个回调,以及statusString纯函数)。队列在窄屏首页使用NarrowPrinterQueueList,在宽屏打印页使用WidePrinterQueueList,两者共用PrintDetails、ProgressBar等组件,体现了同一数据模型在不同布局下的复用。ProgressBar组件内注释说明了 Slint 布局的一个细节:为保持 6px 设计高度而引入中间层Rectangle,避免max-height同时影响宽度计算。
主题系统:DemoPalette 与四种主题模式
common.slint中的DemoPalette全局对象是本例最有代表性的设计系统实现。它定义了Theme枚举(Gradient、Minimalist、Vibrant、Glass)与ModeColors结构体,每个主题通过约 40 个颜色/画刷属性描述背景、主色、按钮渐变、弹出菜单配色等全部视觉变量:
export global DemoPalette { in-out property <Theme> theme: Theme.Gradient; property <ModeColors> current-theme: { if theme == Theme.Gradient { return gradient; } else if theme == Theme.Minimalist { return minimalist; } else if theme == Theme.Vibrant { return vibrant; } else { return glass; } } property <ModeColors> gradient: { background: #FFFFFF, primary: #0E133F, secondary: #E2E8F0, primary-button: #2b7fff, destructive-button: #fa2c37, color-button-gradient: @linear-gradient(90deg, #ff3b30 0%, #ff9500 33%, #ffd60a 66%, #af52de 100%), print-button-bg: @linear-gradient(135deg, #2B7FFF 0%, #155DFC 100%), ... }; ... }current-theme是一个带if/else的属性绑定,切换theme属性即可全局换肤;ComponentTheme全局对象中的side-bar属性同样按主题返回不同的SideBarTheme结构体。所有页面组件都通过DemoPalette.text-primary、DemoPalette.button-width这类"设计令牌"取色取尺寸,从而保证换主题时整个应用(含侧边栏、按钮、打印队列、弹出菜单)同步刷新,无需改动任何业务代码。
数据绑定与宿主语言互操作:以打印队列为例
printerdemo的打印队列演示了 Slint 中 UI 与宿主语言之间最经典的双向数据流模式:.slint里声明global PrinterQueue(含默认数据与回调),各语言后端在启动时用可变模型替换默认队列,并注册回调实现业务逻辑。
Rust 实现
demos/printerdemo/rust/lib.rs 中:
- 用
slint::include_modules!()把编译期生成的 UI 代码引入 Rust; - 通过
main_window.global::<PrinterState>().set_ink_levels([...])注入墨量数据; - 用
slint::VecModel包装队列,通过on_start_job/on_cancel_job注册回调; - 用
slint::Timer(TimerMode::Repeated,间隔 1 秒)模拟打印进度:top_item.progress += 1,超过 100 时移除队首任务; main.rs仅为转发入口:fn main() { printerdemo_lib::main(); },同时兼容桌面、wasm 与 Android(android_main入口)三种目标。
C++ 实现
demos/printerdemo/cpp/main.cpp 展示等价的 C++ 写法:自定义InkLevelModel继承slint::Model<InkLevel>实现墨量模型;slint::VectorModel<PrinterQueueItem>管理队列;slint::Timer驱动进度;并通过printer_demo->global<PrinterQueue>().on_start_job(={...})注册回调。语言切换使用 gettext(HAVE_GETTEXT分支)或slint::select_bundled_translation(捆绑翻译分支)。
Node.js 实现
demos/printerdemo/node/main.js 使用动态加载 API:slint.loadFile(new URL("../ui/printerdemo.slint", import.meta.url))直接加载设计文件,new demo.MainWindow()实例化窗口,appWindow.PrinterState.ink_levels = [...]与appWindow.PrinterQueue.printer_queue = new slint.ArrayModel(...)注入数据,setInterval每 1 秒推进打印进度。运行时initTranslations加载 lang 目录的翻译文件。
Python 实现
demos/printerdemo/python/main.py 与 Node 版本结构类似,通过slint.load_file加载设计文件,为 Python 开发者提供同一套设计资源的动态解释执行入口。
国际化:@tr 与运行时语言切换
示例中所有用户可见文本都包裹在@tr()中(如@tr("Print")、@tr("Printing Queue")),仓库通过 demos/printerdemo/printerdemo.pot 与 demos/printerdemo/lang/fr/LC_MESSAGES/printerdemo.po 提供法语翻译。设置页的"Language"下拉框触发PrinterSettings.change_language(index)回调:
- Rust 桌面版在 lib.rs 中调用
slint::init_translations!(...)并用std::env::set_var("LANGUAGE", lang)切换后重新初始化;wasm/Android 版则调用slint::select_bundled_translation(lang); - C++ 版在 gettext 可用时
setenv("LANGUAGE", ...)后调用slint::update_all_translations(),否则走select_bundled_translation。
这展示了一套"设计期用@tr标记、构建期提取.pot、运行期按环境选择.mo"的完整国际化链路。
跨平台矩阵:桌面、Web、移动端与 Zephyr 嵌入式
桌面与 Web
Rust 版本同时面向桌面与 wasm:#[cfg_attr(target_arch = "wasm32", wasm_bindgen(start))]使编译为 wasm 时可直接作为 WebAssembly 入口运行,配套 rust/index.html 提供浏览器宿主页面;Android 通过#[cfg(target_os = "android")]的android_main入口对接 Slint 的 Android 平台支持。Node 版本可配合 electron 等工具打包桌面应用。
Zephyr RTOS 嵌入式部署
demos/printerdemo/zephyr/README.md 详细记录了把同一份 UI 部署到 Zephyr 实时操作系统上的完整流程:
前置条件:安装 nightly 版 Rust(rustup toolchain install nightly)、Zephyr 依赖、在虚拟环境中安装 West(python3 -m venv ../.venv && pip install west)、通过west init -l --mf demos/zephyr-common/west.yaml ./slint拉取 Zephyr 源码、执行west zephyr-export导出 CMake 包、安装 Zephyr SDK。
模拟器运行:
west build -b native_sim/native/64 -p always slint/demos/printerdemo/zephyr ./build/zephyr/zephyr.exe-p always强制执行干净构建,Zephyr 官方文档建议新手使用。
真机部署:该示例已在 NXP MIMXRT1170-EVKB(搭配 RK055HDMIPI4MA0 MIPI 屏)与 Renesas EK-RZ/A3M(5 英寸 720×1280 MIPI 屏)上验证。MIMXRT1170 构建命令:
west build -b mimxrt1170_evk@B/mimxrt1176/cm7 -p always slint/demos/printerdemo/zephyr -- -DSHIELD=rk055hdmipi4ma0 -DCMAKE_BUILD_TYPE=Release west flashEK-RZ/A3M 需改用 Renesas 的 Zephyr 分支(west-rza3m.yaml)与对应 shield:
west build -b rza3m_ek -p always slint/demos/printerdemo/zephyr -- -DSHIELD=rtkapplcdms02001be -DCMAKE_BUILD_TYPE=Release west flash已知限制(README 明确列出):其一,与 Espressif 集成不同,Slint C++ API 未提供 Zephyr 平台集成,输入事件回调需开发者基于 Device Tree 设备节点自行注册;其二,由于测试用的模拟器与硬件均不支持双缓冲,示例未启用 double buffering。
资源与运行指引
UI 层除.slint文件外还包含自带的 Inter 24pt 字体(ui/fonts,含convert.sh转换脚本)、侧边栏图标(ui/images/sidebar)、动物照片素材(cat.jpg、dog.jpg等,均附.license文件)以及为 MCU 场景优化的图片集(ui/images/mcu)。各语言实现的依赖与构建方式分别记录于 rust/Cargo.toml、cpp/CMakeLists.txt、node/package.json 与 python/pyproject.toml。
若想快速体验,可将ui/printerdemo.slint载入 Slint 在线编辑器预览,或参考 examples 目录中其他示例了解 Slint 的更多能力;整个打印队列与主题切换逻辑的完整源码均可在上文列出的对应路径中直接查阅。
【免费下载链接】slintSlint is an open-source declarative GUI toolkit to build native user interfaces for Rust, C++, JavaScript, or Python apps.项目地址: https://gitcode.com/GitHub_Trending/sl/slint
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考