Slint Widgets Gallery 示例应用全解析:Widget 控件体系、TableView 数据流与多语言国际化实战
Slint Widgets Gallery 示例应用全解析Widget 控件体系、TableView 数据流与多语言国际化实战【免费下载链接】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导读examples/gallery是 Slint 官方仓库中最具代表性的控件橱窗示例——它在一份声明式.slint界面中集中展示了 Slint 内置 Widget 组件库std-widgets.slint的几乎所有常用控件并配套提供了 Rust、C 两套宿主代码与 WebAssembly 在线演示版本。阅读并运行本示例你将掌握Slint 如何用纯声明式语法组装菜单栏、侧边导航与页面切换如何用StandardTableView搭配模型Model实现过滤与排序以及如何借助tr()、gettext 和slint-tr-extractor工具链完成应用的多语言国际化。本文以 examples/gallery/README.md 为主线结合仓库内源码逐层剖析其实现细节。一、示例定位一个能跑的 Widget 大全按 examples/gallery/README.md 的定位gallery 是A simple application showing the different widgets——一个用 Slint 编写的、用于集中展示各种内置控件的小型桌面应用。它既是 Slint 新手的入门教材也是团队评估 Slint 内置控件外观与交互行为的最佳参考。该示例在仓库中的完整目录结构如下examples/gallery/ ├── gallery.slint # 主界面App 组件菜单栏 侧边栏 页面容器 ├── main.rs # Rust 宿主代码含 TableView 数据与过滤排序回调 ├── main.cpp # C 宿主代码与 Rust 版本功能对等 ├── CMakeLists.txt # C 构建脚本 ├── Cargo.toml # Rust 构建配置含 wasm 构建注释模板 ├── index.html # WebAssembly 在线演示页面 ├── update_translations.sh # 翻译提取/合并脚本 ├── gallery.pot # 翻译模板文件 ├── lang/ # 已编译的多语言目录de/fr/ja └── ui/ # 各演示页面与可复用组件 ├── gallery_settings.slint ├── side_bar.slint └── pages/ # 9 个功能演示页面说明README 中引用的三张 Windows / Linux / Mac 运行截图托管于外部站点仓库内并无对应图片文件同时仓库内唯一的图片资源 thumbsup.png 是菜单项图标160×160并非界面截图故本文不配图以源码与文字说明为准。二、三种方式运行与体验 gallery2.1 Rust 原生运行在examples/gallery目录下执行cargo run --releaseCargo.toml 中声明了唯一的二进制目标[[bin]] path main.rs name gallery依赖方面gallery 直接以仓库内路径引用 Slint 核心库并开启std与gettext特性以支持桌面运行与翻译加载slint { path ../../api/rs/slint, default-features false, features [compat-1-18, std, gettext] }2.2 C 原生运行使用 CMake最低版本 3.21CMakeLists.txt 展示了 Slint 的 C 集成三件套find_package(Slint REQUIRED) add_executable(gallery main.cpp) target_link_libraries(gallery PRIVATE Slint::Slint) slint_target_sources(gallery gallery.slint)其中slint_target_sources负责把.slint文件编译为 C 代码并生成gallery.h与 main.cpp 顶部的#include gallery.h对应。此外CMake 脚本会探测系统的Intlgettext库若存在则定义HAVE_GETTEXT并链接Intl::Intlfind_package(Intl) if(Intl_FOUND) target_compile_definitions(gallery PRIVATE HAVE_GETTEXT SRC_DIR${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(gallery PRIVATE Intl::Intl) endif()2.3 在线体验README 提供了两条在线入口一条是在线模拟器在浏览器中直接运行已编译的 WASM 版本另一条是在线代码编辑器打开后可直接编辑 gallery.slint 并实时预览。这也是 Slint 官方文档站点中 gallery 演示的标准嵌入方式对应的自托管入口是仓库内的 index.html其构建方法见本文第七节。三、主界面骨架一份.slint文件描述整个应用打开 gallery.slint整个应用的主界面只用了约 110 行声明式代码。其顶层结构是App (Window) ├── MenuBarFile / Edit 两个菜单 └── HorizontalLayout ├── SideBar侧边导航9 个页面入口 └── VerticalLayout ├── ControlsPage / InputPage / SelectionPage / ... └── 由 side-bar.current-item 条件切换3.1 窗口与资源export component App inherits Window { preferred-width: 700px; preferred-height: 500px; title: tr(Slint Widgets Gallery); icon: image-url(../../logo/slint-logo-small-light.png);preferred-width/preferred-height声明窗口首选尺寸布局系统会据此计算实际窗口大小title使用了tr()翻译宏——这是 Slint 国际化的核心入口见第六节icon通过image-url()引用仓库根目录 logo/slint-logo-small-light.png 作为窗口图标相对路径从examples/gallery/出发上溯两级。3.2 菜单栏MenuBar / Menu / MenuItemApp内直接内联了一个MenuBar展示 Slint 菜单体系的三类元素MenuItem普通菜单项如Open、Copy、Paste可用shortcut: keys(Control O)绑定快捷键子菜单Menu可以嵌套如File - Open Recent内部用for x in 10:循环生成 10 个编号子项Recent {}菜单分割线MenuSeparator { }带图标的菜单项icon: image-url(thumbsup.png)引用的正是仓库内的 thumbsup.png带勾选的菜单项checkable: true; checked: true;。这段代码同时展示了 Slint 菜单的完整能力边界——从快捷键到循环生成、从图标到勾选状态全部由声明式属性驱动。3.3 侧边导航与页面切换if条件渲染页面切换的实现堪称 Slint 声明式 UI 的教科书写法——左侧SideBar暴露一个out property int current-item右侧内容区用 9 个if表达式做条件渲染HorizontalLayout { side-bar : SideBar { accessible-role: navigation; accessible-label: tr(Sidebar); title: tr(Slint Widgets Gallery); model: [ tr(Menu Controls), tr(Menu Input), tr(Menu Selection), tr(Menu TabWidget), tr(Menu ListView), tr(Menu TableView), tr(Menu Easings), tr(Menu StyledText), tr(Menu About) ]; } VerticalLayout { accessible-role: main; accessible-label: tr(Content); if(side-bar.current-item 0): ControlsPage { } if(side-bar.current-item 1): InputPage { } if(side-bar.current-item 2): SelectionPage { } if(side-bar.current-item 3): TabWidgetPage { } if(side-bar.current-item 4): ListViewPage { } if(side-bar.current-item 5): TableViewPage { } if(side-bar.current-item 6): EasingsPage { } if(side-bar.current-item 7): StyledTextPage { } if(side-bar.current-item 8): AboutPage { } } }SideBar的model是一个字符串数组与页面序号一一对应点击侧边栏某项时current-item更新右侧立即重新渲染对应页面。这种模型 条件渲染的模式也是 Slint 应用中做多视图切换的最常见套路。所有 9 个页面组件统一从 ui/pages/pages.slint 导入该文件是一个纯转发模块export { AboutPage } from about_page.slint; export { ControlsPage } from controls_page.slint; // ... TableViewPage, TableViewPageAdapter, InputPage 等注意其中TableViewPageAdapter也被显式导出export { TableViewPage, TableViewPageAdapter }并在gallery.slint中再次export { TableViewPageAdapter }这是为了把该全局单例暴露给 Rust/C 宿主代码——详见第五节。3.4 无障碍支持gallery.slint在导航与内容区域都声明了无障碍属性accessible-role: navigation/main、accessible-labelSideBar内部的SideBarItem也设置了accessible-role: tab、accessible-item-selected、accessible-action-default等见 ui/side_bar.slint。这些属性让应用可以被屏幕阅读器等辅助工具正确解析说明 gallery 同时承担了 Slint 无障碍 API 的示范职责。四、侧边栏组件源码剖析状态、动效与键盘导航ui/side_bar.slint 中定义了SideBarItem与SideBar两个组件是理解 Slint 组件封装与交互细节的好素材。SideBarItem是一个可点击的矩形条目component SideBarItem inherits Rectangle { in property int tab-index; in property bool selected; in property bool has-focus; in-out property string text label.text; callback clicked touch.clicked; ... states [ pressed when touch.pressed : { state.opacity: 0.8; } hover when touch.has-hover : { state.opacity: 0.6; } selected when root.selected : { state.opacity: 1; } focused when root.has-focus : { state.opacity: 0.8; } ] state : Rectangle { opacity: 0; background: Palette.background; animate opacity { duration: 150ms; } } }关键点in-out property string text label.text使用双向绑定把组件属性绑定到内部Text的text这是 Slint 封装自定义组件的标准手法callback clicked touch.clicked把内部TouchArea的点击事件重新暴露为组件的对外回调states [ ... ]依据按压、悬停、选中、聚焦四种状态切换透明度并通过animate opacity { duration: 150ms; }实现 150ms 的平滑过渡——这展示了 Slint 状态机与动画系统的最小完整用例。SideBar组件本身实现了完整的键盘导航fs : FocusScope { key-pressed(event) { if (event.text \n) { root.current-item root.current-focused; return accept; } if (event.text Key.UpArrow) { self.focused-tab Math.max(self.focused-tab - 1, 0); return accept; } if (event.text Key.DownArrow) { self.focused-tab Math.min(self.focused-tab 1, root.model.length - 1); return accept; } return reject; } key-released(event) { if (event.text ) { root.current-item root.current-focused; return accept; } return reject; } property int focused-tab: 0; ... }FocusScope的key-pressed/key-released回调接收按键事件返回accept表示消费该事件、reject表示放行。这里实现的行为是方向键上下移动焦点回车或空格确认选中。forward-focus: fs将组件焦点转发给内部的FocusScope保证键盘操作在窗口加载后立即可用。五、九大演示页面与 TableView 数据流所有页面组件继承自公共的Page基类ui/pages/page.slint统一提供标题、描述与启用开关等布局骨架。九页内容覆盖了 Slint 内置控件的主要类别页面核心演示内容来源文件Controls按钮、复选框、滑块、进度条等基础控件controls_page.slintInput文本框、密码框、数字输入等input_page.slintSelection下拉框、组合框等选择类控件selection_page.slintTabWidget标签页容器tab_widget_page.slintListView列表视图list_view_page.slintTableView表格视图含过滤与排序table_view_page.slintEasings缓动函数动画曲线easings_page.slintStyledText富文本/样式化文本styled_text_page.slintAbout关于页about_page.slint其中TableView 页面是全示例中唯一需要宿主语言参与的部分其数据流最能体现 Slint 的前后端协作模式。5.1 全局单例TableViewPageAdaptertable_view_page.slint 中定义了一个global单例同时承载默认数据与过滤排序回调export global TableViewPageAdapter { in property [[StandardListViewItem]] row_data: [ [ { text: Item 1.1 }, { text: Item 1.2 }, { text: Item 1.3 }, { text: Item 1.4 }, ], // ... 默认 6 行 4 列示例数据 ]; pure callback filter_sort_model([[StandardListViewItem]], string, int, bool) - [[StandardListViewItem]]; filter_sort_model(row-data, filter, sort-index, sort-ascending) { return row-data; } }row_data的类型是[[StandardListViewItem]]行 × 列的二维模型StandardListViewItem是 Slint 内置标准列表项类型。filter_sort_model是一个pure callback——它不产生副作用可在 UI 绑定表达式中直接调用。表格通过绑定表达式串联数据流StandardTableView { columns: [ { title: tr(Header 1) }, // ... 共 4 列 ]; rows: TableViewPageAdapter.filter_sort_model( TableViewPageAdapter.row_data, filter-edit.text, root.sort-index, root.sort-ascending); }用户在filter-edit一个LineEdit中输入的文本会实时进入绑定表达式点击表头时sort-ascending(index)/sort-descending(index)回调更新root.sort-index与root.sort-ascending从而驱动模型重新过滤排序。UI 只声明数据从哪来具体逻辑全部交给宿主语言实现——这是 Slint 前后端解耦设计的典型范式。5.2 Rust 侧实现模型变换与回调注册main.rs 中宿主代码先用VecModel构造 100 行 × 4 列的数据let row_data: RcVecModelslint::ModelRcStandardListViewItem Rc::new(VecModel::default()); for r in 1..101 { let items Rc::new(VecModel::default()); for c in 1..5 { items.push(slint::format!(Item {r}.{c}).into()); } row_data.push(items.into()); }随后通过全局单例注入数据并注册回调app.global::TableViewPageAdapter().set_row_data(row_data.clone().into()); app.global::TableViewPageAdapter().on_filter_sort_model(filter_sort_model);filter_sort_model函数使用 Slint 的模型适配器ModelExt扩展实现链式变换——先filter按第一列模糊匹配再sort_by按指定列升/降序排序fn filter_sort_model( source_model: ModelRcModelRcStandardListViewItem, filter: SharedString, sort_index: i32, sort_ascending: bool, ) - ModelRcModelRcStandardListViewItem { let mut model source_model.clone(); if !filter.is_empty() { let filter filter.to_lowercase(); model Rc::new(source_model.clone().filter(move |e| { e.row_data(0).unwrap().text.to_lowercase().contains(filter.as_str()) })).into(); } if sort_index 0 { model Rc::new(model.clone().sort_by(move |r_a, r_b| { let c_a r_a.row_data(sort_index as usize).unwrap(); let c_b r_b.row_data(sort_index as usize).unwrap(); if sort_ascending { c_a.text.cmp(c_b.text) } else { c_b.text.cmp(c_a.text) } })).into(); } model }值得注意的细节filter与sort_by均来自slint::ModelExttrait返回的是惰性包装模型而非拷贝的新数据——这意味着每次表格重绘时底层行数可能不同但模型适配器会保持视图与数据同步这也是 Slint 模型体系声明式更新的底层保证。5.3 C 侧等价实现main.cpp 用 C 实现了完全相同的逻辑对照阅读可以直观体会两套 API 的对应关系auto row_data std::make_shared slint::VectorModelstd::shared_ptrslint::Modelslint::StandardListViewItem(); for (int r 1; r 101; r) { auto items std::make_sharedslint::VectorModelslint::StandardListViewItem(); for (int c 1; c 5; c) { slint::SharedString text(item); text text slint::SharedString::from_number(c) slint::SharedString(.) slint::SharedString::from_number(r); items-push_back(slint::StandardListViewItem { text }); } row_data-push_back(items); } demo-globalTableViewPageAdapter().set_row_data(row_data); demo-globalTableViewPageAdapter().on_filter_sort_model([](auto source_model, slint::SharedString filter, int sort_index, bool sort_ascending) - auto { auto model source_model; if (!filter.empty()) { auto l_filter filter.to_lowercase(); model std::make_sharedslint::FilterModel...(source_model, l_filter - bool { std::string text(e-row_data(0).value().text.to_lowercase()); return text.find(l_filter) ! std::string::npos; }); } if (sort_index 0) { model std::make_sharedslint::SortModel...(model, sort_index, sort_ascending { // ... 按列排序 }); } return model; });可以看到 Rust 的VecModel/filter/sort_by与 C 的VectorModel/FilterModel/SortModel一一对应跨语言迁移成本很低。六、国际化从tr()到.mo的完整工具链gallery 是展示 Slint 国际化机制的完整案例它同时做到了界面翻译与翻译文件构建流程的示范。6.1 界面侧tr()翻译宏gallery.slint 中所有面向用户的字符串都包裹在tr()中并针对菜单项提供上下文msgctxttitle: tr(Slint Widgets Gallery); MenuItem { title: tr(MenuBar Open); }tr(MenuBar Open)的MenuBar是翻译上下文context用于区分不同界面位置中的同名文本例如Open在菜单与工具栏中可能需要不同译法。{}占位符支持格式化如tr(MenuBar Recent {}, x 1)。6.2 工具侧提取、合并、编译三步走update_translations.sh 给出了完整的翻译流水线# 1. 从所有 .slint 文件提取待翻译字符串生成 gallery.pot 模板 find -name \*.slint | xargs cargo run -p slint-tr-extractor -- -d gallery -o gallery.pot # 2. 将新模板合并进各语言 PO 文件msgmerge再编译为 MO 二进制 for po in lang/*/LC_MESSAGES do msgmerge $po/gallery.po gallery.pot -o $po/gallery.po msgfmt $po/gallery.po -o $po/gallery.mo doneslint-tr-extractor是仓库 tools/tr-extractor 下的翻译提取工具负责解析.slint中的tr()并生成 POT 模板msgmerge与msgfmt来自 GNU gettext 工具链前者合并新词条、后者把 PO 编译成运行时加载的 MO 文件。仓库中已包含德语、法语、日语的翻译产物 lang/de/LC_MESSAGES/gallery.mo 等以及提取生成的 gallery.pot内含全部 668 行词条记录了每个字符串在源码中的行号与上下文。6.3 运行时侧加载翻译Rust 侧在main()开头加载翻译目录main.rs// For native builds, initialize gettext translations #[cfg(not(target_arch wasm32))] slint::init_translations!(concat!(env!(CARGO_MANIFEST_DIR), /lang/));init_translations!宏配合 Cargo.toml 中的gettextfeature见 Cargo.toml在启动时初始化 gettextC 侧则在main()中调用系统 APImain.cpp#ifdef HAVE_GETTEXT bindtextdomain(gallery, SRC_DIR /lang/); std::locale::global(std::locale()); #endif其中bindtextdomain(gallery, ...)的第一个参数与slint-tr-extractor -- -d gallery的-d参数一致即翻译域domain名称两处必须对应。6.4 WASM 侧的字形补充由于 WebAssembly 版本无法依赖系统字体main.rs 中额外导出了load_font_from_bytes函数#[wasm_bindgen]用于按浏览器语言ja/ko/zh等向fontique共享字体集合注册对应的 CJK 字体回退fallback脚本let scripts: [fontique::Script] match locale.id.language.as_str() { ja [Script::Hira, Script::Kana, Script::Hani], ko [Script::Hang, Script::Hani], zh [Script::Hani], _ { /* 使用 locale 自带的 script 或空 */ } };而 index.html 中的前端脚本会在 WASM 模块初始化后检测浏览器语言并拉取 Noto CJK 字体字节流、调用module.load_font_from_bytes(fontData, lang)完成注册保证日文等非拉丁字符正常渲染。七、多风格皮肤与 WebAssembly 构建Slint 的内置控件库支持多套皮肤风格Fluent / Material / Cupertino / Cosmicgallery 是验证这一能力的标准载体。7.1 本地编译时选择风格Rust 通过SLINT_STYLE环境变量在编译期选择风格例如构建 Material 风格SLINT_STYLEmaterial cargo run --release7.2 WebAssembly 版本构建Cargo.toml 中预置了完整的 WASM 构建模板默认被#wasm#注释屏蔽CI 会执行sed s/#wasm# //解除注释启用crate-type [cdylib]并追加wasm-bindgen、web-sys、js-sys、icu_locale_core等目标依赖。解除注释后index.html 的头部注释给出了打包命令——为四套风格各打一个 WASM 包SLINT_STYLEfluent wasm-pack build --release --out-dir pkg/fluent --target web SLINT_STYLEmaterial wasm-pack build --release --out-dir pkg/material --target web SLINT_STYLEcupertino wasm-pack build --release --out-dir pkg/cupertino --target web SLINT_STYLEcosmic wasm-pack build --release --out-dir pkg/cosmic --target webindex.html 前端提供了select idstyle-selection下拉框Fluent/Material/Cupertino/Cosmic通过import(./pkg/ style /gallery.js)动态加载对应风格的 WASM 模块并把渲染目标挂载到canvas contenteditable="false">【免费下载链接】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),仅供参考