Rust+Slint 实现水波纹进度条源码分享
- 一、效果展示
- 二、源码分享
- 三、实现原理
-
- 1、数据流与状态同步
- [2、波纹图像的实时渲染(Rust 端)](#2、波纹图像的实时渲染(Rust 端))
-
- [2.1 、关键参数计算](#2.1 、关键参数计算)
- [2.2、 像素着色逻辑](#2.2、 像素着色逻辑)
- [2.3 、定时动画驱动](#2.3 、定时动画驱动)
- [3、UI 布局与组件封装(Slint 端)](#3、UI 布局与组件封装(Slint 端))
-
- [3.1、 WaveProgress 自定义组件](#3.1、 WaveProgress 自定义组件)
- [3.2 、主窗口布局与响应式](#3.2 、主窗口布局与响应式)
- [4、 构建与编译流程](#4、 构建与编译流程)
- [5、 技术亮点总结](#5、 技术亮点总结)
一、效果展示


二、源码分享
1、工程结构

2、main.rs
rust
slint::include_modules!();
use slint::{SharedPixelBuffer, Rgba8Pixel, Image};
/// 渲染波纹进度图像(RGBA 位图)
fn render_wave(value: f64, wave_offset: f64, size: u32) -> Image {
let half = size as f64 / 2.0;
let radius = half - 10.0; // 留出 10px 边距
let water_level = size as f64 * (1.0 - value / 100.0);
let amplitude = 15.0;
let wave_length = 0.02;
// 颜色定义(与 QML 一致)
let bg_color = Rgba8Pixel {
r: 0xB1, g: 0xC6, b: 0xFF, a: 0xFF,
};
let wave1_color = Rgba8Pixel {
r: 0x42, g: 0x7F, b: 0xFE, a: 0xFF,
};
let wave2_color = Rgba8Pixel {
r: 0x19, g: 0x3C, b: 0xCB, a: 0xFF,
};
let transparent = Rgba8Pixel {
r: 0, g: 0, b: 0, a: 0,
};
let mut buffer = SharedPixelBuffer::<Rgba8Pixel>::new(size, size);
let slice = buffer.make_mut_slice();
for y in 0..size {
for x in 0..size {
let dx = x as f64 - half;
let dy = y as f64 - half;
let dist = (dx * dx + dy * dy).sqrt();
let idx = (y * size + x) as usize;
if dist > radius {
// 圆外:透明
slice[idx] = transparent;
continue;
}
// 计算该 x 位置两条波纹的 y 坐标
let phase1 = x as f64 * wave_length + wave_offset * 0.5;
let wave1_y = water_level + amplitude * phase1.sin();
let phase2 = x as f64 * wave_length + wave_offset;
let wave2_y = water_level + amplitude * phase2.cos();
let py = y as f64;
if py >= wave1_y && py >= wave2_y {
slice[idx] = wave2_color;
} else if py >= wave1_y {
slice[idx] = wave1_color;
} else if py >= wave2_y {
slice[idx] = wave2_color;
} else {
slice[idx] = bg_color;
}
}
}
Image::from_rgba8(buffer)
}
fn update_wave_image(ui: &MainWindow, wave_offset: f64, size: u32) {
let value = ui.get_value() as f64;
let image = render_wave(value, wave_offset, size);
ui.set_wave_image(image);
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let main_window = MainWindow::new()?;
let weak = main_window.as_weak();
let mut wave_offset: f64 = 0.0;
let image_size: u32 = 260;
// 初始化波纹图像
update_wave_image(&main_window, wave_offset, image_size);
// Slider 值变化时同步到 value 属性,并立即更新波纹
let weak_sync = main_window.as_weak();
main_window.on_sync_value(move |v| {
if let Some(ui) = weak_sync.upgrade() {
ui.set_value(v);
update_wave_image(&ui, wave_offset, image_size);
}
});
// 计时器:每 20ms 更新波纹偏移并重绘
let timer = slint::Timer::default();
timer.start(slint::TimerMode::Repeated, std::time::Duration::from_millis(20), move || {
wave_offset += 0.1;
if let Some(ui) = weak.upgrade() {
update_wave_image(&ui, wave_offset, image_size);
}
});
main_window.run()?;
Ok(())
}
3、main.slint
rust
import { Slider } from "std-widgets.slint";
// 波纹进度组件
component WaveProgress {
in property <float> value: 50;
in property <image> wave-image;
in property <length> size: 300px;
property <length> inner-diameter: 280px;
property <length> inner-radius: 140px;
Rectangle {
width: root.size;
height: root.size;
border-radius: root.size / 2px * 1px;
background: #30A80C;
border-width: 20px;
Rectangle {
width: root.size - 20px;
height: root.size - 20px;
border-radius: (root.size - 20px) / 2px * 1px;
x: 10px;
y: 10px;
background: #B1C6FF;
clip: true;
Image {
source: root.wave-image;
width: 100%;
height: 100%;
}
Text {
text: Math.round(root.value) + "%";
color: white;
font-size: root.size * 0.2;
font-weight: 700;
horizontal-alignment: center;
vertical-alignment: center;
width: 100%;
height: 100%;
}
}
}
}
// 主窗口
export component MainWindow inherits Window {
preferred-width: 640px;
preferred-height: 480px;
title: "Wave Progress";
in property <float> value: 50;
in property <image> wave-image;
// 圆直径 = min(窗口宽, 窗口高) - 50px
property <length> circle-size: min(self.width, self.height) - 150px;
callback sync-value(float);
// 上方区域:用 HorizontalLayout 弹性空间居中
VerticalLayout {
HorizontalLayout {
Rectangle { background: transparent; }
WaveProgress {
size: root.circle-size;
value: root.value;
wave-image: root.wave-image;
}
Rectangle { background: transparent; }
}
}
// 底部滑块
Slider {
x: 20px;
width: parent.width - 40px;
y: parent.height - 50px;
minimum: 0;
maximum: 100;
value: root.value;
changed => { root.sync-value(self.value); }
}
}
4、build.rs
rust
fn main(){
slint_build::compile("ui/main.slint")
.unwrap()
}
5、Cargo.toml
rust
[package]
name = "chart"
version = "0.1.0"
edition = "2024"
[dependencies]
slint = "1.16.1"
[build-dependencies]
slint-build = "1.16.1"
6、源码下载
https://download.csdn.net/download/qq_15181569/93270554
三、实现原理
本文实现的"波纹进度条"是一个结合了 Rust 后端渲染与 Slint 前端 UI 的交互式组件。其核心原理可分为数据流、图形渲染、UI 交互三个层面。
1、数据流与状态同步
整个应用的数据流围绕 value(进度值,0-100)和 wave-image(波纹图像)两个核心属性展开:
- 状态源 :
main.slint中定义的Slider组件是用户交互的入口,其value属性绑定到主窗口的root.value。 - 同步机制 :当用户拖动滑块时,触发
changed => { root.sync-value(self.value); }回调。该回调通过 Slint 的callback机制,将新的进度值传递回 Rust 后端(main.rs中的on_sync_value闭包)。 - 双向绑定 :Rust 接收到新值后,通过
ui.set_value(v)更新 UI 中的value属性,同时立即调用update_wave_image重新渲染波纹图像,并通过ui.set_wave_image(image)更新 UI 中的wave-image属性。这样就完成了 "用户交互 → Rust 计算 → UI 刷新" 的完整闭环。
2、波纹图像的实时渲染(Rust 端)
波纹效果的核心在 main.rs 的 render_wave 函数中实现。该函数根据当前进度值 value 和动态偏移 wave_offset,实时生成一张 RGBA 位图。
2.1 、关键参数计算
- 画布与圆环 :渲染在一个
size × size(默认 260×260)的正方形位图上。有效绘图区域是一个半径为radius(size/2 - 10)的圆形,外圈 10 像素为透明边距。 - 水位线 :
water_level = size * (1 - value / 100.0)。进度值value越大,水位线越低(视觉上水面上涨)。 - 波纹动画 :通过两个相位不同的正弦/余弦波叠加产生动态效果:
- 波 1:
wave1_y = water_level + amplitude * sin(x * wave_length + wave_offset * 0.5) - 波 2:
wave2_y = water_level + amplitude * cos(x * wave_length + wave_offset) - 其中
wave_offset每 20 毫秒增加 0.1,产生波纹向左滚动的动画效果。
- 波 1:
2.2、 像素着色逻辑
对圆内的每一个像素 (x, y):
- 计算其到圆心的距离
dist,若dist > radius则设为透明。 - 比较当前像素的 y 坐标
py与两条波纹的 y 坐标wave1_y、wave2_y:- 若
py同时高于两条波纹(即在水面以下),则涂上深蓝色(wave2_color,#193CCB)。 - 若
py仅高于波 1,则涂上浅蓝色(wave1_color,#427FFE)。 - 若
py仅高于波 2,也涂上深蓝色(保证波 2 在上层)。 - 若
py低于两条波纹(即在水面以上),则涂上背景色(bg_color,#B1C6FF)。
- 若
这种分层着色产生了波纹交错、深浅叠加的视觉效果。
2.3 、定时动画驱动
在 main 函数中,通过 slint::Timer 启动一个每 20 毫秒触发一次的重复定时器。每次触发时:
wave_offset += 0.1,更新波纹相位。- 调用
update_wave_image,基于最新的wave_offset和当前value重新渲染图像。 - 将新图像设置到 UI,触发界面重绘。
这样,即使滑块不动,波纹也会持续流动;滑块变动时,水位线立即更新,实现"静动结合"的动画。
3、UI 布局与组件封装(Slint 端)
3.1、 WaveProgress 自定义组件
main.slint 中定义的 WaveProgress 组件封装了进度条的视觉呈现:
- 外层圆环 :一个带绿色边框(
#30A80C)的圆形Rectangle,作为进度条外框。 - 内层圆盘 :一个略小的蓝色背景(
#B1C6FF)圆形,并通过clip: true实现圆形裁剪。 - 波纹图像 :
Image元素绑定root.wave-image,将 Rust 渲染的位图作为纹理铺满内圆。 - 进度文本 :居中显示的
Text,绑定Math.round(root.value) + "%",实时显示整数百分比。
3.2 、主窗口布局与响应式
MainWindow 采用弹性布局实现居中与自适应:
- 上方区域 :使用
VerticalLayout+HorizontalLayout组合,左右各放一个透明的Rectangle作为弹性空间,使WaveProgress始终水平居中。 - 响应式尺寸 :
circle-size: min(self.width, self.height) - 150px确保进度条大小随窗口缩放,并保持边距。 - 底部滑块 :
Slider固定在窗口底部,其value与root.value双向绑定,并通过sync-value回调将用户输入实时同步到 Rust。
4、 构建与编译流程
项目采用 Slint 的标准 Rust 项目结构:
- UI 文件编译 :
build.rs调用slint_build::compile("ui/main.slint"),将.slint文件编译为 Rust 代码(通常生成在target/目录下)。 - 代码集成 :
main.rs通过slint::include_modules!();宏将编译生成的 UI 模块(包括MainWindow结构体)引入当前作用域。 - 依赖管理 :
Cargo.toml中声明slint运行时依赖和slint-build编译时依赖。
5、 技术亮点总结
- 软硬件解耦:图形渲染(CPU 像素计算)与 UI 框架(GPU 绘制)分离,Rust 负责高性能图像生成,Slint 负责高效界面渲染与事件处理。
- 实时双向同步:通过 callback + property 机制,实现用户输入、后端状态、前端显示的毫秒级同步。
- 纯代码生成动画:波纹动画完全由算法实时生成,无需预置图片序列,节省资源且可动态调整参数(振幅、波长、速度等)。
- 响应式设计:UI 组件自动适应窗口尺寸,滑块与进度条视觉联动,提供一致的交互体验。
通过上述设计,本项目演示了如何用 Slint + Rust 构建一个兼具美观动画与实时交互的桌面 GUI 应用。
