【Rust GUI开发入门】编写一个本地音乐播放器(3. UI与后台线程通信)

本系列教程对应的代码已开源在 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状态更新
相关推荐
微小冷7 小时前
Rust异步编程详解
开发语言·rust·async·await·异步编程·tokio
鸿乃江边鸟7 小时前
Spark Datafusion Comet 向量化Rust Native--CometShuffleExchangeExec怎么控制读写
大数据·rust·spark·native
明飞19871 天前
tauri
rust
咚为1 天前
Rust tokio:Task ≠ Thread:Tokio 调度模型中的“假并发”与真实代价
开发语言·后端·rust
天天进步20151 天前
Motia性能进阶与未来:从现有源码推测 Rust 重构之路
开发语言·重构·rust
Hello.Reader2 天前
Rocket 0.5 响应体系Responder、流式输出、WebSocket 与 uri! 类型安全 URI
websocket·网络协议·安全·rust·rocket
FreeBuf_2 天前
黑客利用React Native CLI漏洞(CVE-2025-11953)在公开披露前部署Rust恶意软件
react native·react.js·rust
鸿乃江边鸟2 天前
Spark Datafusion Comet 向量化Rust Native--Native算子(CometNativeExec)怎么串联执行
大数据·rust·spark·native
mit6.8242 天前
[]try catch no | result yes
rust
Ivanqhz2 天前
向量化计算
开发语言·c++·后端·算法·支持向量机·rust