feat(music): relocate playlist into a now-playing bar + slide-out drawer

The full music panel was rendered inline at all times (its own card in
the 3-column layout, stuffed into the Controls panel otherwise), which
crowded every layout. Move it behind two toggles:

- A room-only ♪ button in the top bar shows/hides a slim 56px
  now-playing player bar (track + ⏮ ⏸/▶ ⏭ + position + expand). The
  preference persists (AppConfig.show_player_bar).
- The bar's ⤢ button opens the full panel in a resizable right-edge
  drawer (DividerKind::PlaylistDrawer, mirrors the Chat drawer). When
  open, body_w shrinks so the layouts' fixed panels don't overflow.

Removes all inline playlist placement (3-col card + ThreeColPlaylist
divider, ctrl_music block) and the now-dead clamp/consts. Pure
now_playing_label seam + drawer-width clamp test. 474 lib tests, clippy
-D warnings clean, release build green.

Implemented by Codex (gpt-5.5), reviewed + gates re-run by Claude.

Co-Authored-By: Codex <codex@openai.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-28 16:59:49 -04:00
co-authored by Codex Claude Opus 4.8
parent 0aaf6be529
commit b553a94875
2 changed files with 234 additions and 78 deletions
+18
View File
@@ -138,6 +138,10 @@ fn default_chat_drawer_width() -> f32 {
320.0
}
fn default_playlist_drawer_width() -> f32 {
320.0
}
fn default_window_width() -> f32 {
900.0
}
@@ -174,6 +178,9 @@ pub struct AppConfig {
/// W22 music: opt-in shared listening broadcast toggle. Local preference.
#[serde(default)]
pub music_broadcast: bool,
/// Show the slim now-playing player bar in the room screen.
#[serde(default = "default_true")]
pub show_player_bar: bool,
/// When true, `clip_volume` governs every clip. When false, each clip keeps
/// its own (in-memory) level and the universal slider is inactive.
#[serde(default = "default_true")]
@@ -207,6 +214,9 @@ pub struct AppConfig {
/// Chat drawer width for the drawer layout (px).
#[serde(default = "default_chat_drawer_width")]
pub chat_drawer_width: f32,
/// Playlist drawer width for the room-screen right-edge music panel (px).
#[serde(default = "default_playlist_drawer_width")]
pub playlist_drawer_width: f32,
/// Chosen arrangement of the in-call room screen.
#[serde(default)]
pub room_layout: RoomLayout,
@@ -348,6 +358,7 @@ impl Default for AppConfig {
music_playlist: Vec::new(),
music_volume: 1.0,
music_broadcast: false,
show_player_bar: true,
clip_volume_universal: true,
network_mode: NetworkMode::default(),
presence_mode: crate::presence::PresenceMode::default(),
@@ -358,6 +369,7 @@ impl Default for AppConfig {
threecol_playlist_height: default_threecol_playlist_height(),
controls_width: default_controls_width(),
chat_drawer_width: default_chat_drawer_width(),
playlist_drawer_width: default_playlist_drawer_width(),
room_layout: RoomLayout::default(),
theme: AppTheme::default(),
avatar: crate::avatar::Avatar::default(),
@@ -517,6 +529,8 @@ mod tests {
assert_eq!(deserialized.room_layout, RoomLayout::BottomDock);
assert_eq!(deserialized.controls_width, 280.0);
assert_eq!(deserialized.chat_drawer_width, 320.0);
assert_eq!(deserialized.playlist_drawer_width, 320.0);
assert!(deserialized.show_player_bar);
assert!(deserialized.custom_sound_self_join.is_none());
assert!(deserialized.custom_sound_peer_join.is_none());
assert!(deserialized.custom_sound_peer_leave.is_none());
@@ -701,6 +715,7 @@ mod tests {
assert!(def.music_playlist.is_empty());
assert_eq!(def.music_volume, 1.0);
assert!(!def.music_broadcast);
assert!(def.show_player_bar);
assert!(def.clip_volume_universal);
// Missing in JSON → unity (serde default).
@@ -712,6 +727,7 @@ mod tests {
assert!(cfg_missing.music_playlist.is_empty());
assert_eq!(cfg_missing.music_volume, 1.0);
assert!(!cfg_missing.music_broadcast);
assert!(cfg_missing.show_player_bar);
// Configs predating the toggle default to universal mode.
assert!(cfg_missing.clip_volume_universal);
@@ -723,6 +739,7 @@ mod tests {
music_playlist: vec!["/tmp/song.ogg".to_string()],
music_volume: 0.6,
music_broadcast: true,
show_player_bar: false,
clip_volume_universal: false,
..AppConfig::default()
};
@@ -734,6 +751,7 @@ mod tests {
assert_eq!(round_tripped.music_playlist, vec!["/tmp/song.ogg".to_string()]);
assert_eq!(round_tripped.music_volume, 0.6);
assert!(round_tripped.music_broadcast);
assert!(!round_tripped.show_player_bar);
assert!(!round_tripped.clip_volume_universal);
}