本系列教程对应的代码已开源在 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
修饰符代表可读可写 - 回调函数:指定用户点击/拖动...该组件时,要执行的任务