Rust+Slint实现动态启动界面源码分享

Rust+Slint实现动态启动界面源码分享

  • 一、效果展示
  • 二、源码分享
    • 1、工程结构
    • 2、main.rs
    • 3、build.rs
    • 4、main.slint
    • 5、Cargo.toml
  • 三、实现原理
    • 1、整体数据流
    • 2、动画机制
      • 2.1 发射动画
      • 2.2 旋转动画
      • 2.3 淡出效果
    • 3、资源管理
      • 3.1 图片资源
      • 3.2 模型增删
    • 4、关键设计要点

一、效果展示



二、源码分享

1、工程结构

2、main.rs

slint::include_modules!();useslint::{ModelRc,Timer,TimerMode,VecModel};usestd::rc::Rc;usestd::time::{Duration,Instant};/// 简易伪随机数生成器(LCG),返回 0.0 ~ 1.0fnpseudo_random(seed:i32)->f32{letx=((seedasi64).wrapping_mul(1_103_515_245).wrapping_add(12_345))&0x7fff_ffff;xasf32/0x7fff_ffffasf32}fnmain(){letui=MainWindow::new().expect("无法创建主窗口");// ── 浮动图片模型 ──letmodel:Rc<VecModel<FloatingImage>>=Rc::new(VecModel::from(vec![]));ui.set_floating_images(ModelRc::new(model.clone()));// ── 内部状态:每张图片的创建时间(用于清理) ──letcreated_times:Rc<std::cell::RefCell<Vec<Instant>>>=Rc::new(std::cell::RefCell::new(Vec::new()));letcounter=Rc::new(std::cell::Cell::new(0_i32));// ── 注册 spawn-image 回调:Slint Timer 每 2 秒调用一次 ──{letmodel=model.clone();letcreated_times=created_times.clone();letcounter=counter.clone();ui.on_spawn_image(move||{letid=counter.get();counter.set(id+1);letstop_x_ratio=pseudo_random(id);model.push(FloatingImage{id,stop_x_ratio});created_times.borrow_mut().push(Instant::now());});}// ── 清理定时器:每秒清理一次超过 15.5 秒的图片 ──{letmodel=model.clone();letcreated_times=created_times.clone();letcleanup_timer=Timer::default();cleanup_timer.start(TimerMode::Repeated,Duration::from_secs(1),move||{letnow=Instant::now();letmuttimes=created_times.borrow_mut();// 找出过期的索引(从高到低,避免 remove 时索引偏移)// 动画总时长 8 秒,留 500ms 缓冲letmutexpired:Vec<usize>=times.iter().enumerate().filter(|(_,t)|now.duration_since(**t)>=Duration::from_millis(8_500)).map(|(i,_)|i).collect();expired.sort_unstable_by(|a,b|b.cmp(a));// 逐个移除,不重建整个模型(保留其他元素的动画状态)foridxinexpired{times.remove(idx);model.remove(idx);}},);std::mem::forget(cleanup_timer);}ui.run().expect("运行 UI 失败");}

3、build.rs

fnmain(){slint_build::compile("ui/main.slint").unwrap()}

4、main.slint

// ==================== 动态背景 ====================// 浮动图片数据结构exportstructFloatingImage{id:int,// 目标位置占屏幕宽度的比例 (0.0 ~ 1.0),由 Rust 随机生成stop-x-ratio:float,}// 图片资源数组(在 Slint 中加载)globalImageResources{inproperty<[image]>images:[@image-url("../images/apple.svg"),@image-url("../images/banana.svg"),@image-url("../images/watermelon.svg"),@image-url("../images/peach.svg"),@image-url("../images/mango.svg"),@image-url("../images/strawberry.svg"),];}export componentMainWindowinheritsWindow{title:"动态背景";preferred-width:800px;preferred-height:600px;// 浮动图片模型,由 Rust 端管理(增删)inproperty<[FloatingImage]>floating-images;// 回调:请求 Rust 添加新图片到模型callback spawn-image();// 渐变背景(与 QML 一致:从上到下)background:@linear-gradient(180deg,#4bb6ac0%,#3edabd50%,#24efc0100%);// ==================== 生成定时器 ====================// 每 2 秒触发一次,调用 Rust 回调添加新图片Timer{interval:2000ms;running:true;triggered()=>{spawn-image();}}// ==================== 浮动图片层 ====================forimginfloating-images:Rectangle{width:100px;height:100px;// 动画状态:是否已发射property<bool>launched:false;// 旋转角度(0 ~ 360 循环)property<float>rotation-angle:0;// 起始位置(float 计算,最终转 length)property<float>start-x-px:root.width/1px/2-50;property<float>start-y-px:root.height/1px;// 终止位置property<float>stop-x-px:(root.width/1px-100)*img.stop-x-ratio;property<float>stop-y-px:-150;// 当前位置:根据 launched 状态决定x:(launched?stop-x-px:start-x-px)*1px;y:(launched?stop-y-px:start-y-px)*1px;// 旋转:使用 transform-rotationtransform-rotation:rotation-angle*1deg;// 动画:x 和 y 变化时使用 animate(加速到 8 秒)animate x,y{duration:8000ms;easing:linear;}// 透明度动画:整个 8 秒内逐渐淡出opacity:launched?0.0:1.0;animate opacity{duration:8000ms;easing:linear;}// 发射定时器:创建后 10ms 触发,将 launched 设为 true 以启动动画Timer{interval:10ms;running:!launched;triggered()=>{launched=true;}}// 旋转定时器:每 50ms 增加 6 度(3 秒一圈)Timer{interval:50ms;running:true;triggered()=>{rotation-angle=Math.mod(rotation-angle+6,360);}}// 图片渲染(从全局资源数组中按 id 取模获取)Image{width:100px;height:100px;source:ImageResources.images[Math.mod(img.id,6)];}}// 清理逻辑由 Rust 端处理(每秒检查并移除超过 15.5 秒的图片)// ==================== 中央文字 ====================Text{x:(root.width-self.width)/2;y:(root.height-self.height)/2;text:"正在启动中...请稍后!";color:white;font-size:30px;font-weight:700;horizontal-alignment:center;vertical-alignment:center;}}

5、Cargo.toml

[package]name="modbus-debug-assistant"version="0.1.0"edition="2024"[dependencies]slint="1.17.1"[build-dependencies]slint-build="1.17.1"

三、实现原理

本项目的核心思路是Rust 负责数据与逻辑、Slint 负责界面与动画,两者通过 Slint 提供的回调(callback)与模型(model)机制进行通信。下面从数据流、动画机制和资源管理三个维度拆解实现细节。

1、整体数据流

整个动态启动界面的运行流程如下:

Slint Timer 每 2 秒触发

调用 spawn-image 回调

Rust 生成随机目标位置

向 VecModel 推送 FloatingImage

Slint for 循环渲染新图片

图片发射动画 + 旋转 + 淡出

Rust 清理定时器移除过期图片

  • Slint 侧:Timer组件每 2 秒触发一次spawn-image()回调,通知 Rust 端生成新图片。
  • Rust 侧:收到回调后,用 LCG 伪随机数生成器计算目标横坐标比例,构造FloatingImage结构体并推入VecModel。
  • Slint 侧:for img in floating-images响应模型变化,自动为每条新数据创建对应的Rectangle组件实例,并启动动画。

2、动画机制

2.1 发射动画

每张图片创建后,其launched属性初始为false,此时图片停留在屏幕底部起始位置。图片内部的Timer在 10ms 后触发,将launched置为true,从而触发x、y属性的变化:

x: (launched ? stop-x-px : start-x-px) * 1px; y: (launched ? stop-y-px : start-y-px) * 1px; animate x, y { duration: 8000ms; easing: linear; }

由于animate声明了 8 秒的线性过渡,Slint 会自动在起始位置与终止位置之间插值,形成图片从底部缓缓飘向顶部的效果。

2.2 旋转动画

旋转通过独立的Timer实现,每 50ms 将rotation-angle增加 6 度,配合transform-rotation属性让图片持续旋转:

Timer { interval: 50ms; running: true; triggered() => { rotation-angle = Math.mod(rotation-angle + 6, 360); } }

2.3 淡出效果

透明度动画与位移动画同步进行,同样持续 8 秒,从完全不透明渐变为完全透明:

opacity: launched ? 0.0 : 1.0; animate opacity { duration: 8000ms; easing: linear; }

3、资源管理

3.1 图片资源

六张水果图片通过global ImageResources统一管理,在 Slint 中直接加载 SVG 资源。渲染时按图片id对 6 取模,实现循环使用不同图片:

source: ImageResources.images[Math.mod(img.id, 6)];

3.2 模型增删

Rust 端维护VecModel<FloatingImage>作为数据源,同时用Vec<Instant>记录每张图片的创建时间。清理定时器每秒检查一次,将存在超过 8.5 秒的图片索引从高到低逐个移除:

letmutexpired:Vec<usize>=times.iter().enumerate().filter(|(_,t)|now.duration_since(**t)>=Duration::from_millis(8_500)).map(|(i,_)|i).collect();expired.sort_unstable_by(|a,b|b.cmp(a));foridxinexpired{times.remove(idx);model.remove(idx);}

这里有两个关键设计:

  • 从高到低移除:避免remove时索引偏移导致误删。
  • 不重建整个模型:只移除过期元素,保留其他图片的动画状态,避免界面闪烁。

4、关键设计要点

设计点说明
数据与界面分离Rust 只负责数据增删与随机逻辑,Slint 只负责渲染与动画
回调驱动Slint 通过callback主动请求数据,而非 Rust 轮询界面
模型响应式VecModel的增删会自动同步到界面,无需手动刷新
动画状态独立每张图片的launched、rotation-angle均为独立属性,互不干扰
定时器分工生成定时器在 Slint 侧,清理定时器在 Rust 侧,各司其职

相关新闻

3 分钟跑通 Unity 资源提取全流程:AssetRipper 一次讲清

3 分钟跑通 Unity 资源提取全流程:AssetRipper 一次讲清

3 分钟跑通 Unity 资源提取全流程&#xff1a;AssetRipper 一次讲清 【免费下载链接】AssetRipper GUI application to analyze game files 项目地址: https://gitcode.com/GitHub_Trending/as/AssetRipper 手边有个 .assets&#xff08;游戏打包后的资源容器&#xff0…

2026/10/21 2:13:48 阅读更多 →
Brainmu-Spike代码实现原理:脉冲DAT解码与数据加载的底层逻辑

Brainmu-Spike代码实现原理:脉冲DAT解码与数据加载的底层逻辑

人工智能计算机视觉图像处理大模型微调 【免费下载链接】Brainmu-Spike 项目地址&#xff1a; https://ai.gitcode.com/BAAI/Brainmu-Spike 点击查看 免费下载 Brainmu-Spike 是由北京大学与北京智源人工智能研究院&#xff08;BAAI&#xff09;联合发布的脉冲相机影像重建项目…

2026/10/21 2:13:48 阅读更多 →
外挂 AI VS AI 原生:一文看透各家低代码厂商智能化路线差异

外挂 AI VS AI 原生:一文看透各家低代码厂商智能化路线差异

前言 2026 年&#xff0c;AI 已经成为低代码平台的标配标签。打开各家厂商官网&#xff0c;几乎所有低代码产品都打出了 “AI 生成应用、智能搭建、一句话出系统” 等宣传口号。但很多政企、IT 负责人实际落地之后才发现&#xff0c;不同平台 AI 能力最终效果天差地别&#xff…

2026/10/21 2:13:48 阅读更多 →

最新新闻

服务器磁盘管理实战:从选型、RAID到LVM与性能排障全解析

服务器磁盘管理实战:从选型、RAID到LVM与性能排障全解析

最近帮一个业务团队做新服务器选型与磁盘初始化配置时&#xff0c;发现很多开发同学对磁盘这块的知识还停留在“机械硬盘 vs 固态硬盘”的层面。真要落到服务器选型、多块硬盘怎么组合、RAID 怎么选、系统装完以后磁盘怎么规划、后续怎么扩容和排障&#xff0c;往往就缺乏一套完…

2026/10/21 2:49:01 阅读更多 →
RAG知识库问答实战:从原理、技术选型到工程落地优化

RAG知识库问答实战:从原理、技术选型到工程落地优化

1. RAG 到底解决了什么问题&#xff0c;为什么现在非学不可最近两年&#xff0c;大模型相关的技术方向非常多&#xff0c;微调、Agent、多模态、RAG……如果不深入去看&#xff0c;很容易被各种名词绕晕。尤其是 RAG&#xff08;Retrieval-Augmented Generation&#xff0c;检索…

2026/10/21 2:49:01 阅读更多 →
RAG知识库问答实战:从原理到代码落地

RAG知识库问答实战:从原理到代码落地

前一阵子在做知识库问答项目时&#xff0c;发现网上 RAG 相关的教程虽然很多&#xff0c;但大部分要么只讲原理、不给可运行代码&#xff0c;要么代码能跑但不解释为什么这么设计。对刚接触大模型应用开发的同学来说&#xff0c;很容易卡在“原理看懂了&#xff0c;一动手就报错…

2026/10/21 2:49:01 阅读更多 →
RAG入门到实战:用LangChain+Chroma搭建知识库问答系统

RAG入门到实战:用LangChain+Chroma搭建知识库问答系统

先给结论&#xff1a;RAG 是目前搭建知识库问答最稳的基线方案。RAG 的全称是 Retrieval-Augmented Generation&#xff0c;检索增强生成。它解决的问题很直接&#xff1a;大模型不知道你的私有资料、内部文档、产品手册和业务数据&#xff0c;那就先把这些资料切块、向量化、存…

2026/10/21 2:49:01 阅读更多 →
火焰数据集YOLOv5训练实战:从标注文件到可推理模型

火焰数据集YOLOv5训练实战:从标注文件到可推理模型

简介&#xff1a;火焰数据集&#xff08;含标注好的标签&#xff09;面向从事目标检测的开发者与研究者&#xff0c;尤其适合使用 YOLOv5 训练火焰识别模型的场景&#xff0c;可用于火灾预警、视频监控等实际项目。资源包共 5260 个文件&#xff0c;包含 1853 张 jpg 图像、185…

2026/10/21 2:49:01 阅读更多 →
Quartz动态设置并发数量

Quartz动态设置并发数量

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/21 2:48:00 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事&#xff1a;AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里&#xff0c;按宣传口径&#xff0c;本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”&#xff0c;而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起&#xff1a;用户到底在追求什么“封号重开”这四个字&#xff0c;放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流&#xff0c;背后是大量的时间和精力成本。正常情况下&#xff0c;没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”&#xff0c;而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时&#xff0c;就意识到&#xff1a;单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →