【Rust GUI开发入门】编写一个本地音乐播放器(4. 绘制按钮组件)

本系列教程对应的代码已开源在 Github zeedle

开始介绍播放器UI的构建部分,但是不会详细讲解Slint UI的设计基础,没有意义,因为官方文档介绍的已经十分详细了,一些基本用法需要借助参考文档熟悉。

Slint UI支持使用类SVG指令绘制矢量图标,为了保证UI的风格统一性,这里不使用网络上的图标,直接使用Path指令绘制,具体语法参考Path | Slint Docs

需要绘制的图标如下:

  • 播放/暂停
  • 上一曲
  • 下一曲
  • 播放模式

直接给出.slint代码:

slint 复制代码
import { Palette } from "std-widgets.slint";
export component NextSongButton inherits Window {
    callback clicked();
    TouchArea {
        clicked => {
            root.clicked();
        }
        Path {
            width: 100%;
            height: 100%;
            MoveTo {
                x: 0;
                y: 0;
            }

            LineTo {
                x: 0;
                y: 100;
            }

            LineTo {
                x: 70;
                y: 50;
            }

            Close { }

            MoveTo {
                x: 80;
                y: 0;
            }

            LineTo {
                x: 80;
                y: 100;
            }

            stroke: Palette.control-foreground;
            stroke-width: root.width * 0.1;
        }
    }
}

export component PrevSongButton inherits Window {
    callback clicked();
    TouchArea {
        clicked => {
            root.clicked();
        };
        Path {
            width: 100%;
            height: 100%;
            MoveTo {
                x: 80;
                y: 0;
            }

            LineTo {
                x: 80;
                y: 100;
            }

            LineTo {
                x: 10;
                y: 50;
            }

            Close { }

            MoveTo {
                x: 0;
                y: 0;
            }

            LineTo {
                x: 0;
                y: 100;
            }

            stroke: Palette.control-foreground;
            stroke-width: root.width * 0.1;
        }
    }
}

export component PlayPauseButton inherits Window {
    in-out property <bool> paused:true;
    callback toggled();
    TouchArea {
        clicked => {
            root.toggled();
        }
        if !root.paused:
            Path {
            width: 100%;
            height: 100%;
            MoveTo {
                x: 0;
                y: 0;
            }

            LineTo {
                x: 0;
                y: 100;
            }

            LineTo {
                x: 30;
                y: 100;
            }

            LineTo {
                x: 30;
                y: 0;
            }

            Close { }

            MoveTo {
                x: 50;
                y: 0;
            }

            LineTo {
                x: 50;
                y: 100;
            }

            LineTo {
                x: 80;
                y: 100;
            }

            LineTo {
                x: 80;
                y: 0;
            }

            Close { }

            stroke: Palette.control-foreground;
            stroke-width: root.width * 0.1;
        }
        if root.paused:
            Path {
            width: 100%;
            height: 100%;
            MoveTo {
                x: 0;
                y: 0;
            }

            LineTo {
                x: 0;
                y: 100;
            }

            LineTo {
                x: 80;
                y: 50;
            }

            Close { }

            stroke: Palette.control-foreground;
            stroke-width: root.width * 0.1;
        }
    }
}

export component InOrderButton inherits Window {
    in-out property <bool> selected;
    Path {
        MoveTo {
            x: 90;
            y: 50;
        }

        ArcTo {
            x: 50;
            y: 10;
            radius-x: 40;
            radius-y: 40;
            x-rotation: 0;
            large-arc: true;
            sweep: true;
        }

        LineTo {
            x: 40;
            y: 4;
        }

        MoveTo {
            x: 50;
            y: 10;
        }

        LineTo {
            x: 42;
            y: 18;
        }

        stroke-width: 1px;
        stroke: selected ? Palette.accent-background : Palette.foreground;
    }
}

export component RecursiveButton inherits Window {
    in-out property <bool> selected;
    Path {
        MoveTo {
            x: 90;
            y: 50;
        }

        ArcTo {
            x: 50;
            y: 10;
            radius-x: 40;
            radius-y: 40;
            x-rotation: 0;
            large-arc: true;
            sweep: true;
        }

        LineTo {
            x: 40;
            y: 4;
        }

        MoveTo {
            x: 50;
            y: 10;
        }

        LineTo {
            x: 42;
            y: 18;
        }

        MoveTo {
            x: 50;
            y: 40;
        }

        LineTo {
            x: 50;
            y: 60;
        }

        MoveTo {
            x: 50;
            y: 40;
        }

        LineTo {
            x: 44;
            y: 44;
        }

        stroke-width: 1px;
        stroke: selected ? Palette.accent-background : Palette.foreground;
    }
}

@rust-attr(derive(serde::Serialize, serde::Deserialize))
export enum PlayMode { 
    InOrder, 
    Recursive, 
    Random
 }

export component OverlapButton inherits Window {
    in-out property <PlayMode> mode;
    callback clicked();
    TouchArea {
        width: 100%;
        height: 100%;
        clicked => {
            root.clicked();
        }
        if mode == PlayMode.Recursive: RecursiveButton {
            width: 100%;
            height: 100%;
            selected: mode == PlayMode.Recursive || mode == PlayMode.InOrder;
        }
        if mode != PlayMode.Recursive: InOrderButton {
            width: 100%;
            height: 100%;
            selected: mode == PlayMode.Recursive || mode == PlayMode.InOrder;
        }
    }
}

export component RandomButton inherits Window {
    callback clicked();
    in-out property <bool> selected;
    TouchArea {
        clicked => {
            root.clicked();
        };
        Path {
            MoveTo {
                x: 0;
                y: 20;
            }

            LineTo {
                x: 30;
                y: 20;
            }

            LineTo {
                x: 70;
                y: 80;
            }

            LineTo {
                x: 100;
                y: 80;
            }

            LineTo {
                x: 90;
                y: 70;
            }

            MoveTo {
                x: 100;
                y: 80;
            }

            LineTo {
                x: 90;
                y: 90;
            }

            // 第二段
            MoveTo {
                x: 0;
                y: 80;
            }

            LineTo {
                x: 30;
                y: 80;
            }

            LineTo {
                x: 70;
                y: 20;
            }

            LineTo {
                x: 100;
                y: 20;
            }

            LineTo {
                x: 90;
                y: 30;
            }

            MoveTo {
                x: 100;
                y: 20;
            }

            LineTo {
                x: 90;
                y: 10;
            }

            stroke: selected ? Palette.accent-background : Palette.foreground;
            stroke-width: 1px;
        }
    }
}

代码解释

从上述指令可以看出,这里使用的绘图指令还是比较简单的,基本就是画直线,画圆弧:

  • MoveTo:移动到某个点,并按下画笔
  • LineTo:从当前位置绘制直线到目标位置,并松开画笔
  • ArcTo:绘制圆弧,参数比较多,可以参考文档
    • radius-x:X轴半径
    • radius-y:Y轴半径
    • sweep:是否为顺时针方向
    • x, y:目标位置
    • x-rotation:椭圆的X轴旋转角

上面.slint代码中,有一些关于属性<property>和组件回调函数<callback>相关的东西:

  • 属性:可以是组件的外观参数(长,宽等),或者指示组件的内部状态,in修饰符代表只能由外部传入(只写),out修饰符代表只能从内部传出(只读),in-out修饰符代表可读可写
  • 回调函数:指定用户点击/拖动...该组件时,要执行的任务
相关推荐
zsqw1232 小时前
以 Rust 为例,聊聊线性类型,以及整个类型系统
rust·编译器
Rust研习社2 小时前
Rust Tracing 实战指南:从基础用法到生产级落地
rust
分布式存储与RustFS3 小时前
MinIO迎来“恶龙”?RustFS这款开源存储简直“不讲武德”
架构·rust·开源·对象存储·minio·企业存储·rustfs
数据知道16 小时前
claw-code 源码分析:从 TypeScript 心智到 Python/Rust——跨栈移植时类型、边界与错误模型怎么对齐?
python·ai·rust·typescript·claude code·claw code
Freak嵌入式20 小时前
MicroPython LVGL基础知识和概念:GUI 的扩展接口
ide·驱动开发·嵌入式·gui·lvgl·micropython·upypi
Rust研习社1 天前
深入浅出 Rust 迭代器:从基础用法到性能优化
rust
@atweiwei1 天前
langchainrust:Rust 版 LangChain 框架(LLM+Agent+RAG)
开发语言·rust·langchain·agent·向量数据库·rag
skilllite作者1 天前
自进化 Agent 的 skills 别长成烟囱:从多入口分叉到统一发现与 spec 防火带
人工智能·算法·rust·openclaw·agentskills
Rust研习社2 天前
关于 Rust Option 的那些事:从基础到常用 API 全解析
rust