feat(mini-player): expandable queue + UX polish

Adds a queue panel that toggles via a new toolbar button and resizes the
mini window between 340×180 (collapsed) and 340×440 (expanded). Tracks
in the queue are clickable — click jumps to that index via a new
mini:jump event handled in the main-window bridge. The current track
auto-scrolls into view when the queue opens.

Resize uses a new resize_mini_player Rust command (more reliable than
JS setSize, which was silently no-op'ing because the
core:window:allow-set-size capability was missing). That capability is
now granted as well.

The mini player hydrates its initial state from the persisted
playerStore, so real content (cover, title, artist, queue) shows as
soon as React mounts instead of "—" while we wait for the first
mini:sync. Dynamic state (isPlaying, progress) still comes from
events.

Window sizing:
- inner_size bumped to 340×180 so the toolbar row isn't clipped
- mini label added to tauri-plugin-window-state denylist so old saved
  sizes don't come back
- show_main_window now also hides the mini — clicking expand no longer
  leaves both windows on screen

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Psychotoxical
2026-04-19 00:46:28 +02:00
parent ab35ef5eb4
commit 0afcc4ab68
5 changed files with 254 additions and 36 deletions
+1
View File
@@ -36,6 +36,7 @@
"core:window:allow-is-fullscreen",
"core:window:allow-start-dragging",
"core:window:allow-create",
"core:window:allow-set-size",
"core:webview:allow-create-webview-window",
"process:allow-restart",
"updater:default"
+23 -4
View File
@@ -2641,8 +2641,8 @@ fn open_mini_player(app: tauri::AppHandle) -> Result<(), String> {
tauri::WebviewUrl::App("index.html".into()),
)
.title("Psysonic Mini")
.inner_size(340.0, 140.0)
.min_inner_size(320.0, 120.0)
.inner_size(340.0, 180.0)
.min_inner_size(320.0, 180.0)
.resizable(true)
.decorations(true)
.always_on_top(use_always_on_top)
@@ -2674,9 +2674,13 @@ fn close_mini_player(app: tauri::AppHandle) -> Result<(), String> {
/// Unminimize + show + focus the main window. Called from the mini player's
/// "expand" button. Can't rely on a JS event bridge here because the main
/// window's JS is paused while minimized on WebKitGTK.
/// window's JS is paused while minimized on WebKitGTK. Also hides the mini
/// window so the two don't sit on screen at the same time.
#[tauri::command]
fn show_main_window(app: tauri::AppHandle) -> Result<(), String> {
if let Some(mini) = app.get_webview_window("mini") {
let _ = mini.hide();
}
if let Some(main) = app.get_webview_window("main") {
main.unminimize().map_err(|e| e.to_string())?;
main.show().map_err(|e| e.to_string())?;
@@ -2694,6 +2698,16 @@ fn set_mini_player_always_on_top(app: tauri::AppHandle, on_top: bool) -> Result<
Ok(())
}
/// Resize the mini player window (logical pixels). Used when toggling the
/// queue panel to expand/collapse without a capability dance.
#[tauri::command]
fn resize_mini_player(app: tauri::AppHandle, width: f64, height: f64) -> Result<(), String> {
if let Some(win) = app.get_webview_window("mini") {
win.set_size(tauri::LogicalSize::new(width, height)).map_err(|e| e.to_string())?;
}
Ok(())
}
pub fn run() {
// Linux: second `psysonic --player …` forwards over D-Bus before heavy startup.
#[cfg(target_os = "linux")]
@@ -2721,7 +2735,11 @@ pub fn run() {
.manage(TrayState::default())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_window_state::Builder::default().build())
.plugin(
tauri_plugin_window_state::Builder::default()
.with_denylist(&["mini"])
.build()
)
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
.plugin(tauri_plugin_store::Builder::default().build())
@@ -2919,6 +2937,7 @@ pub fn run() {
open_mini_player,
close_mini_player,
set_mini_player_always_on_top,
resize_mini_player,
show_main_window,
register_global_shortcut,
unregister_global_shortcut,