本系列教程对应的代码已开源在 Github zeedle
UI线程 <math xmlns="http://www.w3.org/1998/Math/MathML"> → 消息 \xrightarrow{消息} </math>消息 后台线程
使用枚举定义消息类型
rust
enum PlayerCommand {
Play(SongInfo, TriggerSource), // 从头播放某个音频文件
Pause, // 暂停/继续播放
ChangeProgress(f32), // 拖拽进度条
PlayNext, // 播放下一首
PlayPrev, // 播放上一首
SwitchMode(PlayMode), // 切换播放模式
RefreshSongList(PathBuf), // 刷新歌曲列表
SortSongList(SortKey, bool), // 排序歌曲列表
SetLang(String), // 设置语言
}
通过管道发送数据
从UI主线程通过管道(channel)向后台线程发送信息,例如:
rust
// UI 主线程
...
let (tx, rx) = std::sync::channel::<PlayerCommand>();
std::thread::spawn(move || {
// 后台线程
while let Ok(cmd) = rx.recv() {
match cmd {
PlayerCommand::Pause => {...},
PlayerCommand::PlayNext => {...},
PlayerCommand::PlayNext => {...},
....
}
}
});
...
tx.send(PlayerCommand::Pause);
...
后台线程 <math xmlns="http://www.w3.org/1998/Math/MathML"> → 消息 \xrightarrow{消息} </math>消息 UI线程
全局状态
Slint UI支持在.slint
文件中声明全局变量,然后在Rust代码中访问/修改该变量的值,这样即可完成UI状态的更新:
slint
// ui state
export global UIState {
// 当前播放进度 (秒)
in-out property <float> progress;
// 总时长 (秒)
in-out property <float> duration;
// 当前播放进度文本
in-out property <string> progress_info_str;
// 播放/暂停状态
in-out property <bool> paused;
// 是否正在拖动进度条
in-out property <bool> dragging;
// 歌曲列表
in-out property <[SongInfo]> song_list;
// 当前播放歌曲的信息
in-out property <SongInfo> current_song;
// 播放模式
in-out property <PlayMode> play_mode;
// 是否已被用户触发播放
in-out property <bool> user_listening;
// 当前播放歌曲的歌词
in-out property <[LyricItem]> lyrics;
// 当前歌词视窗的滚动条位置(一般为负数)
in property <length> lyric_viewport_y;
// 当前一行歌词的高度
in-out property <length> lyric_line_height: 40px;
// 歌曲文件夹配置
in-out property <string> song_dir;
// 关于信息
in property <string> about_info;
// 专辑封面图像
in property <image> album_image;
// 播放历史
in property <[SongInfo]> play_history;
// 播放历史索引
in property <int> history_index: 0;
// 歌曲排序方式
in-out property <SortKey> sort_key;
in-out property <SortKey> last_sort_key;
// 升序/降序
in-out property <bool> sort_ascending: true;
// 当前语言
in-out property <string> lang;
// 主题颜色
in-out property <bool> light_ui;
}
从后台线程添加任务到UI主线程
与上文中使用管道发送指令不同,Slint UI提供了一种从后台线程发送指令到UI主线程的简便方式,即通过slint::invoke_from_event_loop(task_closure)
,将task添加到UI主线程的下一轮事件循环中执行,更新UI状态,例如:
rust
// 后台线程
let ui_weak = ui.as_weak();
while let Ok(cmd) = rx.recv() {
match cmd {
PlayerCommand::Pause => {
...
slint::invoke_from_event_loop(move || {
// 此闭包虽然在后台线程中定义,但是执行是发生在UI主线程里面的
// 只有在UI主线程中才能获得UI窗口对象的强引用,否则会失败
if let Some(ui) = ui_weak.upgrade(){
let ui_state = ui.global::<UIState>();
ui_state.set_paused(true);
}
})
...
},
}
}
总结
在Slint UI中,一种典型的范式是:
- UI线程通过管道向后台线程发送数据
.slint
中定义的全局变量(export global {...}
)可以在Rust代码中直接使用- 后台线程通过
slint::invoke_from_event_loop(task_closure)
向UI主线程的事件循环中添加任务
,典型做法是此任务
修改上述全局变量(即UIState),来完成UI状态更新