Rust+Slint 实现动态流光边框源码分享

Rust+Slint 实现动态流光边框源码分享

一、效果展示

二、源码分享

1、工程结构

2、main.rs

rust 复制代码
slint::include_modules!();

fn main() {
    let ui = MainWindow::new().expect("无法创建主窗口");
    ui.run().expect("运行 UI 失败");
}

3、build.rs

rust 复制代码
fn main(){
    slint_build::compile("ui/main.slint")
        .unwrap()
}

4、main.slint

rust 复制代码
import { VerticalBox, HorizontalBox } from "std-widgets.slint";

// ==================== 流光边框组件 ====================
component FlowingBorder inherits Rectangle {
    in property <string> title;
    in property <color> color1: #e78f0c;
    in property <color> color2: #00ffff;
    in property <length> corner-radius: 12px;
    in property <length> border-size: 2px;

    width: 200px;
    height: 80px;

    // 父容器:圆角 + 裁剪(模拟 QPainterPath clip)
    border-radius: corner-radius;
    clip: true;
    background: #0d0d1a;

    // 底层:两个矩形旋转(模拟 C++ 的 QPainter 旋转矩形)
    Rectangle {
        width: 100%;
        height: 100%;
        clip: true;  // 裁剪到父容器大小

        // 旋转角度(一直增加,不取模)
        property <int> angle: 0;
        flow-timer := Timer {
            interval: 20ms;
            running: true;
            triggered() => {
                angle += 5;
            }
        }

        // 矩形1:颜色1,绕中心旋转
        Rectangle {
            width: max(root.width,root.height)*1.2;
            height: max(root.width,root.height)*1.2;
            background: @linear-gradient(0deg, color1 0%, color2 100%);
            transform-rotation: parent.angle * 1deg;
            transform-origin: {x: self.width / 2, y: self.height / 2};
        }

    }

    // 顶层:内层遮盖,露出边框间隙
    inner-bg := Rectangle {
        x: border-size;
        y: border-size;
        width: root.width - border-size * 2;
        height: root.height - border-size * 2;
        border-radius: corner-radius - border-size;
        background: #0d0d1a;

        VerticalBox {
            padding: 8px;
            alignment: center;
            spacing: 4px;

            Text {
                text: title;
                color: #ffffff;
                font-size: 16px;
                font-weight: 600;
                horizontal-alignment: center;
                vertical-alignment: center;
            }
        }
    }
}

// ==================== 主窗口 ====================
export component MainWindow inherits Window {
    title: "流光边框背景";
    width: 900px;
    height: 600px;

    background: #0a0a14;

    VerticalBox {
        padding: 30px;
        spacing: 25px;
        alignment: center;

        Text {
            text: "流光边框背景控件";
            color: #ffffff;
            font-size: 24px;
            font-weight: 700;
            horizontal-alignment: center;
            vertical-stretch: 0;
        }


        // 第一行:不同颜色组合
        HorizontalBox {
            spacing: 20px;
            alignment: center;
            vertical-stretch: 0;

            FlowingBorder {
                title: "青色流光";
                color1: #f76809;
                color2: #0088ff;
            }

            FlowingBorder {
                title: "红色流光";
                color1: #ff4444;
                color2: #ff8800;
            }

            FlowingBorder {
                title: "绿色流光";
                color1: #ff00d4;
                color2: #88ff44;
            }

            FlowingBorder {
                title: "紫色流光";
                color1: #00ff37;
                color2: #ff44aa;
            }
        }

        // 第二行:不同尺寸
        HorizontalBox {
            spacing: 20px;
            alignment: center;
            vertical-stretch: 0;

            FlowingBorder {
                title: "小";
                width: 120px;
                height: 50px;
                corner-radius: 8px;
                color1: #ffaa00;
                color2: #ff4488;
            }

            FlowingBorder {
                title: "中等";
                width: 200px;
                height: 80px;
                corner-radius: 12px;
                color1: #dff70b;
                color2: #aa00ff;
            }

            FlowingBorder {
                title: "大号控件";
                width: 280px;
                height: 100px;
                corner-radius: 16px;
                color1: #ff0088;
                color2: #00ff88;
            }
        }

        // 第三行:不同圆角
        HorizontalBox {
            spacing: 20px;
            alignment: center;
            vertical-stretch: 0;

            FlowingBorder {
                title: "小圆角";
                corner-radius: 4px;
                color1: #ffff00;
                color2: #ff8800;
            }

            FlowingBorder {
                title: "大圆角";
                corner-radius: 30px;
                color1: #ffee00;
                color2: #ff0000;
            }

            FlowingBorder {
                title: "胶囊";
                corner-radius: 40px;
                width: 180px;
                height: 60px;
                color1: #ff00ff;
                color2: #00ff22;
            }
        }
    }
}

5、Cargo.toml

rust 复制代码
[package]
name = "modbus-debug-assistant"
version = "0.1.0"
edition = "2024"

[dependencies]
slint = "1.17.1"


[build-dependencies]
slint-build = "1.17.1"

三、实现原理

1、整体思路

流光边框的核心思路是:用旋转的渐变矩形作为底层背景,再用一个内层矩形遮盖中间区域,只露出边缘一圈,从而形成"光在边框上流动"的视觉效果。

整个实现可以拆成四层来看:
#mermaid-svg-4aqaXOKbDNpaN4vX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4aqaXOKbDNpaN4vX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4aqaXOKbDNpaN4vX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4aqaXOKbDNpaN4vX .error-icon{fill:#552222;}#mermaid-svg-4aqaXOKbDNpaN4vX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4aqaXOKbDNpaN4vX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4aqaXOKbDNpaN4vX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4aqaXOKbDNpaN4vX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4aqaXOKbDNpaN4vX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4aqaXOKbDNpaN4vX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4aqaXOKbDNpaN4vX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4aqaXOKbDNpaN4vX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4aqaXOKbDNpaN4vX .marker.cross{stroke:#333333;}#mermaid-svg-4aqaXOKbDNpaN4vX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4aqaXOKbDNpaN4vX p{margin:0;}#mermaid-svg-4aqaXOKbDNpaN4vX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4aqaXOKbDNpaN4vX .cluster-label text{fill:#333;}#mermaid-svg-4aqaXOKbDNpaN4vX .cluster-label span{color:#333;}#mermaid-svg-4aqaXOKbDNpaN4vX .cluster-label span p{background-color:transparent;}#mermaid-svg-4aqaXOKbDNpaN4vX .label text,#mermaid-svg-4aqaXOKbDNpaN4vX span{fill:#333;color:#333;}#mermaid-svg-4aqaXOKbDNpaN4vX .node rect,#mermaid-svg-4aqaXOKbDNpaN4vX .node circle,#mermaid-svg-4aqaXOKbDNpaN4vX .node ellipse,#mermaid-svg-4aqaXOKbDNpaN4vX .node polygon,#mermaid-svg-4aqaXOKbDNpaN4vX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4aqaXOKbDNpaN4vX .rough-node .label text,#mermaid-svg-4aqaXOKbDNpaN4vX .node .label text,#mermaid-svg-4aqaXOKbDNpaN4vX .image-shape .label,#mermaid-svg-4aqaXOKbDNpaN4vX .icon-shape .label{text-anchor:middle;}#mermaid-svg-4aqaXOKbDNpaN4vX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4aqaXOKbDNpaN4vX .rough-node .label,#mermaid-svg-4aqaXOKbDNpaN4vX .node .label,#mermaid-svg-4aqaXOKbDNpaN4vX .image-shape .label,#mermaid-svg-4aqaXOKbDNpaN4vX .icon-shape .label{text-align:center;}#mermaid-svg-4aqaXOKbDNpaN4vX .node.clickable{cursor:pointer;}#mermaid-svg-4aqaXOKbDNpaN4vX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4aqaXOKbDNpaN4vX .arrowheadPath{fill:#333333;}#mermaid-svg-4aqaXOKbDNpaN4vX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4aqaXOKbDNpaN4vX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4aqaXOKbDNpaN4vX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4aqaXOKbDNpaN4vX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4aqaXOKbDNpaN4vX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4aqaXOKbDNpaN4vX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4aqaXOKbDNpaN4vX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4aqaXOKbDNpaN4vX .cluster text{fill:#333;}#mermaid-svg-4aqaXOKbDNpaN4vX .cluster span{color:#333;}#mermaid-svg-4aqaXOKbDNpaN4vX div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4aqaXOKbDNpaN4vX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4aqaXOKbDNpaN4vX rect.text{fill:none;stroke-width:0;}#mermaid-svg-4aqaXOKbDNpaN4vX .icon-shape,#mermaid-svg-4aqaXOKbDNpaN4vX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4aqaXOKbDNpaN4vX .icon-shape p,#mermaid-svg-4aqaXOKbDNpaN4vX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4aqaXOKbDNpaN4vX .icon-shape .label rect,#mermaid-svg-4aqaXOKbDNpaN4vX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4aqaXOKbDNpaN4vX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4aqaXOKbDNpaN4vX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4aqaXOKbDNpaN4vX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 父容器 Rectangle
clip: true 裁剪到圆角矩形
底层旋转渐变矩形
Timer 驱动角度递增
内层遮盖矩形
露出边框间隙 = 流光效果

  • 父容器 :负责圆角裁剪,相当于 C++ 里的 QPainterPath 裁剪区域;
  • 底层:一个超大的渐变矩形,绕中心持续旋转;
  • Timer:每 20ms 让角度 +5°,驱动旋转动画;
  • 内层遮盖 :比父容器小一圈(缩进 border-size),把中间盖住,只留边框一圈可见。

2、圆角裁剪(clip)

slint 复制代码
border-radius: corner-radius;
clip: true;
background: #0d0d1a;
  • border-radius 设置圆角半径;
  • clip: true 表示所有子元素超出父容器圆角范围的部分都会被裁剪掉;
  • 这样底层旋转矩形即使转出边界,也不会"溢出"到圆角外面,保证边框始终是圆角矩形。

这一步对应 C++ 实现里的 painter.setClipPath(roundedRectPath)。

3、底层旋转渐变矩形

slint 复制代码
Rectangle {
    width: max(root.width,root.height)*1.2;
    height: max(root.width,root.height)*1.2;
    background: @linear-gradient(0deg, color1 0%, color2 100%);
    transform-rotation: parent.angle * 1deg;
    transform-origin: {x: self.width / 2, y: self.height / 2};
}

关键点:

  • 尺寸放大到 1.2 倍:保证矩形旋转时四个角始终能覆盖到父容器的边缘,不会露出空白;
  • 线性渐变 :color1 → color2 的渐变让"光"有方向感,旋转时看起来像光在流动;
  • transform-rotation :以自身中心为原点旋转,角度由父级 angle 属性驱动;
  • transform-origin:旋转中心设为矩形正中心,保证绕中心转而不是绕左上角转。

4、Timer 驱动动画

slint 复制代码
property <int> angle: 0;
flow-timer := Timer {
    interval: 20ms;
    running: true;
    triggered() => {
        angle += 5;
    }
}
  • interval: 20ms:每 20ms 触发一次,即每秒 50 帧;
  • 每次触发 angle += 5,即每秒旋转 50 × 5 = 250°,约 1.4 秒转一圈;
  • 角度不取模,一直累加,Slint 内部会自动处理大角度旋转,无需手动归零。

角度累加不取模的好处是:避免取模运算带来的性能开销,同时 Slint 的旋转计算对大角度天然支持。

5、内层遮盖,露出边框

slint 复制代码
inner-bg := Rectangle {
    x: border-size;
    y: border-size;
    width: root.width - border-size * 2;
    height: root.height - border-size * 2;
    border-radius: corner-radius - border-size;
    background: #0d0d1a;
}
  • 内层矩形比父容器四周各缩进 border-size(默认 2px);
  • 背景色与父容器一致(#0d0d1a),把中间区域完全盖住;
  • 于是只有边框一圈能透出底层旋转的渐变,形成"流光边框";
  • 圆角也相应缩小 border-size,保证内层圆角与外层同心。

6、与 C++ 实现的对应关系

C++(QPainter) Slint 实现 作用
setClipPath clip: true + border-radius 圆角裁剪
QPainter::rotate transform-rotation 旋转渐变矩形
QTimer Timer 驱动动画
内层填充矩形 inner-bg 遮盖中间,露出边框
QLinearGradient @linear-gradient 渐变颜色

7、小结

整个流光边框的核心就一句话:旋转的渐变 + 内层遮盖 = 边框流光 。通过调整 color1、color2、border-size、corner-radius 和 Timer 的间隔/步长,可以轻松改变光效的颜色、粗细、圆角和速度,从而适配不同的 UI 风格。

相关推荐
niucloud-admin1 小时前
NIUCLOUD PHP SAAS上门家政使用文档:机构报表
前端·人工智能
包小黑1 小时前
Python 转 EXE (实用工具)
开发语言·python
Bigger1 小时前
开源了一个"先预览、能反悔"的文件整理工具,聊聊背后的工程细节
rust·开源·app
枣把儿1 小时前
开源!全套公众号写作、封面、发布自动化工具包!
前端·命令行
陶醉的松鼠1 小时前
前端容易忽略的 debugger 调试技巧
前端
我命由我123451 小时前
6-3.Python 异常处理 - 异常处理进阶(else 块与 finally 块、异常传递、自定义异常)
开发语言·前端·数据库·后端·python·学习·java-ee
额额额对了1 小时前
【项目实战】从IMX6ULL环境监测到N647 AI视觉加速
linux·开发语言·嵌入式硬件
龙九山1 小时前
我的 10 个 npm 插件,差点被 AI 判了死刑
前端·开源
deli0072 小时前
键盘按键测试:按下就点亮,keydown 延迟精确到 1 毫秒
前端