4. 第一个Slint应用

本章的目标不是“看懂一段示例代码”,而是完整走通一条最小可用链路:创建工程、理解 .slint 文件结构、在桌面端运行与预览、最后把同一个应用交叉编译并部署到嵌入式 Linux 或 RTOS 目标板。走通这条链路后,后续所有复杂界面都可以视为在它之上的增量扩展。

4.1 核心原理:Slint 应用的三层结构

一个 Slint 工程在运行时由三层协作构成:宿主程序负责启动事件循环、加载界面、连接业务逻辑;.slint 界面文件用声明式语法描述组件树、属性、回调与状态;平台后端负责把渲染结果输出到窗口系统或嵌入式显示设备。理解这三层的边界,是排查“界面不显示”“点击无响应”“目标板黑屏”等问题的前提。

Slint 应用运行链路:源码 → 编译 → 桌面预览 → 目标板部署 ① 源码层 main.rs / main.cpp ui/app-window.slint Cargo.toml / CMakeLists.txt ② 编译与后端 slint-build 代码生成 渲染后端选择 winit / linuxkms / qt ③ 运行目标 桌面窗口预览 嵌入式 Linux + DRM RTOS + Framebuffer .slint 文件结构解剖 导入区 import 引入标准控件 引入自定义组件 引入图标与字体 组件区 component 根元素与尺寸 子元素嵌套 布局 Layout 属性与状态 property 属性 states 状态 动画 animate 交互与逻辑 callback 回调 if / for 条件循环 Rust/JS 绑定

4.2 方法流程:创建、运行、预览、部署

推荐使用官方模板快速起步,避免手工拼装构建脚本带来的版本错配。以 Rust 宿主为例,标准流程如下:

# 1. 安装 Slint 模板工具
cargo install cargo-generate

# 2. 基于模板创建工程
cargo generate --git https://github.com/slint-ui/slint-rust-template.git \
  --name hello-slint

# 3. 进入工程并运行桌面预览
cd hello-slint
cargo run

# 4. 使用实时预览工具,边改 .slint 边看效果
cargo install slint-viewer
slint-viewer ui/app-window.slint

桌面端跑通后,再进入交叉编译阶段。嵌入式 Linux 目标板通常需要指定目标三元组,并选择与设备匹配的渲染后端:

# 以 ARM64 嵌入式 Linux 为例
rustup target add aarch64-unknown-linux-gnu

# 指定后端为 linuxkms(无桌面环境,直接走 DRM/KMS)
SLINT_BACKEND=linuxkms \
cargo build --release --target aarch64-unknown-linux-gnu

# 将产物与资源拷贝到目标板
scp target/aarch64-unknown-linux-gnu/release/hello-slint root@192.168.1.50:/opt/app/
ssh root@192.168.1.50 "/opt/app/hello-slint"
若目标板没有 GPU 或 DRM 驱动,可改用 SLINT_BACKEND=linuxfb 走 Framebuffer;若运行在 RTOS 上,则需使用 Slint 的 C++ 或 Rust no_std 集成方式,由 RTOS 提供帧缓冲与输入事件。

4.3 案例:Hello World 的 .slint 文件逐段解读

下面是一个最小但结构完整的界面文件,它同时覆盖了导入、组件、属性、回调与布局五类要素:

import { Button, VerticalBox } from "std-widgets.slint";

export component AppWindow inherits Window {
    title: "Hello Slint";
    width: 320px;
    height: 200px;

    in-out property <int> counter: 0;

    callback reset-clicked();

    VerticalBox {
        alignment: center;
        Text {
            text: "Hello, Slint!";
            font-size: 22px;
            horizontal-alignment: center;
        }
        Text {
            text: "点击次数: " + counter;
            horizontal-alignment: center;
        }
        Button {
            text: "点我 +1";
            clicked => { counter += 1; }
        }
        Button {
            text: "重置";
            clicked => { root.reset-clicked(); }
        }
    }
}

宿主程序只需加载该组件并注册回调,即可完成业务逻辑对接:

slint::include_modules!();

fn main() -> Result<(), slint::PlatformError> {
    let ui = AppWindow::new()?;
    let weak = ui.as_weak();
    ui.on_reset_clicked(move || {
        if let Some(ui) = weak.upgrade() {
            ui.set_counter(0);
        }
    });
    ui.run()
}

4.4 关键参数与后端选择对照

配置项 取值示例 适用场景 注意事项
SLINT_BACKEND winit 桌面开发与调试 需要窗口系统,启动最快
SLINT_BACKEND linuxkms 无桌面嵌入式 Linux 需 DRM/KMS 驱动与权限
SLINT_BACKEND linuxfb 仅 Framebuffer 的设备 刷新率与双缓冲受限
target aarch64-unknown-linux-gnu ARM64 Linux 板卡 需匹配交叉工具链
target armv7-unknown-linux-gnueabihf ARM32 Linux 板卡 注意浮点 ABI 一致性
SLINT_SCALE_FACTOR 1.5 高 DPI 或小尺寸屏 影响所有逻辑像素尺寸

4.5 误区与复盘

  • 只改 .slint 不重新构建:嵌入式部署时界面文件会被编译进二进制,修改后必须重新交叉编译,不能只替换单个文件。
  • 后端与设备不匹配:在无桌面环境的目标板上使用 winit 后端,会直接启动失败;应优先确认 DRM 或 Framebuffer 是否可用。
  • 忽略资源路径:图片、字体等资源若使用相对路径,部署后工作目录变化会导致加载失败,建议使用 @image-url 或嵌入资源。
  • 回调未注册:界面中声明了 callback 但宿主未绑定,点击后无任何反应,且不会报错,需通过日志或断点确认绑定是否生效。
  • 尺寸写死:在目标板上直接使用固定像素尺寸,遇到不同分辨率屏幕会出现裁切或留白,应结合布局与缩放因子处理。
复盘建议:把“桌面预览通过”和“目标板运行通过”作为两个独立验收点。桌面通过只证明界面逻辑正确,目标板通过才证明后端、驱动、资源与交叉编译链路全部打通。