Rust+Slint 实现水波纹进度条源码分享

Rust+Slint 实现水波纹进度条源码分享

一、效果展示

二、源码分享

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.rsrender_wave 函数中实现。该函数根据当前进度值 value 和动态偏移 wave_offset,实时生成一张 RGBA 位图。

2.1 、关键参数计算

  • 画布与圆环 :渲染在一个 size × size(默认 260×260)的正方形位图上。有效绘图区域是一个半径为 radiussize/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,产生波纹向左滚动的动画效果。

2.2、 像素着色逻辑

对圆内的每一个像素 (x, y)

  1. 计算其到圆心的距离 dist,若 dist > radius 则设为透明。
  2. 比较当前像素的 y 坐标 py 与两条波纹的 y 坐标 wave1_ywave2_y
    • py 同时高于两条波纹(即在水面以下),则涂上深蓝色(wave2_color#193CCB)。
    • py 仅高于波 1,则涂上浅蓝色(wave1_color#427FFE)。
    • py 仅高于波 2,也涂上深蓝色(保证波 2 在上层)。
    • py 低于两条波纹(即在水面以上),则涂上背景色(bg_color#B1C6FF)。

这种分层着色产生了波纹交错、深浅叠加的视觉效果。

2.3 、定时动画驱动

main 函数中,通过 slint::Timer 启动一个每 20 毫秒触发一次的重复定时器。每次触发时:

  1. wave_offset += 0.1,更新波纹相位。
  2. 调用 update_wave_image,基于最新的 wave_offset 和当前 value 重新渲染图像。
  3. 将新图像设置到 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 固定在窗口底部,其 valueroot.value 双向绑定,并通过 sync-value 回调将用户输入实时同步到 Rust。

4、 构建与编译流程

项目采用 Slint 的标准 Rust 项目结构:

  1. UI 文件编译build.rs 调用 slint_build::compile("ui/main.slint"),将 .slint 文件编译为 Rust 代码(通常生成在 target/ 目录下)。
  2. 代码集成main.rs 通过 slint::include_modules!(); 宏将编译生成的 UI 模块(包括 MainWindow 结构体)引入当前作用域。
  3. 依赖管理Cargo.toml 中声明 slint 运行时依赖和 slint-build 编译时依赖。

5、 技术亮点总结

  1. 软硬件解耦:图形渲染(CPU 像素计算)与 UI 框架(GPU 绘制)分离,Rust 负责高性能图像生成,Slint 负责高效界面渲染与事件处理。
  2. 实时双向同步:通过 callback + property 机制,实现用户输入、后端状态、前端显示的毫秒级同步。
  3. 纯代码生成动画:波纹动画完全由算法实时生成,无需预置图片序列,节省资源且可动态调整参数(振幅、波长、速度等)。
  4. 响应式设计:UI 组件自动适应窗口尺寸,滑块与进度条视觉联动,提供一致的交互体验。

通过上述设计,本项目演示了如何用 Slint + Rust 构建一个兼具美观动画与实时交互的桌面 GUI 应用。

相关推荐
felixking2 小时前
C++20 协程
开发语言·c++·协程
Zane1994123 小时前
CAS 与原子类:Java 如何实现无锁编程
java·开发语言
码农颜4 小时前
6.3 主函数流程剖析
开发语言·php
TheBestRucy4 小时前
Python 九阳神功:从零筑基到线程飞升
服务器·开发语言·网络·人工智能·python
研☆香4 小时前
聊一聊前端的常见字 关键字
开发语言·前端·javascript
草莓橙子碗5 小时前
Claude 使用指南
开发语言·python
longxiaozhang66 小时前
C# Array类:数组其实没那么难
开发语言·c#
互联网中的一颗神经元6 小时前
06. Small 对象分配:mcache 无锁路径
开发语言·golang
PPPPickup6 小时前
金证秋招笔试
java·开发语言