Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add album/artist detail nav and art bg

Introduce SelectedAlbum, SelectedArtist and BackSection globals and
wire AlbumDetail/ArtistDetail into LibraryPage. Refactor library
rendering
to compute detail data in a single borrow, add a reusable track_row
helper, and use a new theme.library_art_bg for artwork placeholders.

+563 -85
+1 -1
gpui/src/ui/components/miniplayer.rs
··· 63 63 .flex() 64 64 .items_center() 65 65 .justify_center() 66 - .bg(theme.border) 66 + .bg(theme.library_art_bg) 67 67 .text_color(theme.player_icons_text) 68 68 .child(Icon::new(Icons::Music).size_4()), 69 69 )
+15 -2
gpui/src/ui/components/mod.rs
··· 12 12 Player, 13 13 Queue, 14 14 } 15 - 16 15 impl gpui::Global for Page {} 17 16 18 17 #[derive(Clone, Copy, PartialEq)] ··· 20 19 Songs, 21 20 Albums, 22 21 Artists, 22 + AlbumDetail, 23 + ArtistDetail, 23 24 } 25 + impl gpui::Global for LibrarySection {} 24 26 25 - impl gpui::Global for LibrarySection {} 27 + #[derive(Clone, PartialEq)] 28 + pub struct SelectedAlbum(pub String); 29 + impl gpui::Global for SelectedAlbum {} 30 + 31 + #[derive(Clone, PartialEq)] 32 + pub struct SelectedArtist(pub String); 33 + impl gpui::Global for SelectedArtist {} 34 + 35 + /// Where the back button in a detail view should navigate to. 36 + #[derive(Clone, Copy, PartialEq)] 37 + pub struct BackSection(pub LibrarySection); 38 + impl gpui::Global for BackSection {}
+541 -81
gpui/src/ui/components/pages/library.rs
··· 3 3 use crate::ui::components::icons::{Icon, Icons}; 4 4 use crate::ui::components::miniplayer::MiniPlayer; 5 5 use crate::ui::components::search_input::SearchInput; 6 - use crate::ui::components::LibrarySection; 6 + use crate::ui::components::{BackSection, LibrarySection, SelectedAlbum, SelectedArtist}; 7 7 use crate::ui::theme::Theme; 8 8 use gpui::prelude::FluentBuilder; 9 9 use gpui::{ ··· 14 14 15 15 pub struct LibraryPage { 16 16 scroll_handle: UniformListScrollHandle, 17 + detail_scroll_handle: UniformListScrollHandle, 17 18 miniplayer: Entity<MiniPlayer>, 18 19 search_input: Entity<SearchInput>, 19 20 } ··· 21 22 impl LibraryPage { 22 23 pub fn new(cx: &mut App) -> Self { 23 24 cx.set_global(LibrarySection::Songs); 25 + cx.set_global(SelectedAlbum(String::new())); 26 + cx.set_global(SelectedArtist(String::new())); 27 + cx.set_global(BackSection(LibrarySection::Albums)); 24 28 LibraryPage { 25 29 scroll_handle: UniformListScrollHandle::new(), 30 + detail_scroll_handle: UniformListScrollHandle::new(), 26 31 miniplayer: cx.new(|_| MiniPlayer), 27 32 search_input: cx.new(|cx| SearchInput::new(cx)), 28 33 } ··· 33 38 fn render(&mut self, window: &mut Window, cx: &mut gpui::Context<Self>) -> impl IntoElement { 34 39 let theme = *cx.global::<Theme>(); 35 40 let section = *cx.global::<LibrarySection>(); 41 + let back_section = cx.global::<BackSection>().0; 42 + let selected_album = cx.global::<SelectedAlbum>().0.clone(); 43 + let selected_artist = cx.global::<SelectedArtist>().0.clone(); 36 44 37 45 let content_width = f32::from(window.viewport_size().width) - 200.0; 38 46 let album_cols = ((content_width / 200.0).floor() as u16).max(2); 39 47 let artist_cols = ((content_width / 160.0).floor() as u16).max(2); 48 + let detail_album_cols = ((content_width / 180.0).floor() as u16).max(2); 40 49 41 - // Pre-compute section data while holding state borrow 42 - let (n_songs, albums, artists) = { 50 + // Pre-compute all section data in a single state borrow 51 + let (n_songs, albums, artists, current_idx, album_tracks, album_artist, artist_tracks, artist_albums_detail) = { 43 52 let state = cx.global::<Controller>().state.read(cx); 44 53 54 + let current_idx = state.current_idx; 45 55 let n_songs = state.tracks.len(); 46 56 47 57 let mut album_map: std::collections::BTreeMap<String, (String, usize)> = ··· 63 73 } 64 74 let artists: Vec<(String, usize)> = artist_map.into_iter().collect(); 65 75 66 - (n_songs, albums, artists) 76 + // Album detail: tracks filtered by selected album 77 + let album_tracks: Vec<(usize, String, String, u64)> = state 78 + .tracks 79 + .iter() 80 + .enumerate() 81 + .filter(|(_, t)| t.album == selected_album) 82 + .map(|(idx, t)| (idx, t.title.clone(), t.track_number.to_string(), t.duration)) 83 + .collect(); 84 + 85 + let album_artist = state 86 + .tracks 87 + .iter() 88 + .find(|t| t.album == selected_album) 89 + .map(|t| t.artist.clone()) 90 + .unwrap_or_default(); 91 + 92 + // Artist detail: tracks and albums filtered by selected artist 93 + let artist_tracks: Vec<(usize, String, String, u64)> = state 94 + .tracks 95 + .iter() 96 + .enumerate() 97 + .filter(|(_, t)| t.artist == selected_artist) 98 + .map(|(idx, t)| (idx, t.title.clone(), t.album.clone(), t.duration)) 99 + .collect(); 100 + 101 + let mut artist_album_map: std::collections::BTreeMap<String, usize> = 102 + Default::default(); 103 + for track in &state.tracks { 104 + if track.artist == selected_artist { 105 + *artist_album_map.entry(track.album.clone()).or_default() += 1; 106 + } 107 + } 108 + let artist_albums_detail: Vec<(String, usize)> = 109 + artist_album_map.into_iter().collect(); 110 + 111 + ( 112 + n_songs, 113 + albums, 114 + artists, 115 + current_idx, 116 + album_tracks, 117 + album_artist, 118 + artist_tracks, 119 + artist_albums_detail, 120 + ) 67 121 }; 68 122 123 + let n_album_tracks = album_tracks.len(); 124 + let n_artist_tracks = artist_tracks.len(); 69 125 let scroll_handle = self.scroll_handle.clone(); 126 + let _detail_scroll_handle = self.detail_scroll_handle.clone(); 70 127 71 - // Sidebar nav item builder 128 + // Sidebar nav item — Albums/Artists stay active while in their detail view 72 129 let make_nav_item = move |label: &'static str, target: LibrarySection| { 73 - let is_active = section == target; 130 + let is_active = section == target 131 + || (section == LibrarySection::AlbumDetail && target == LibrarySection::Albums) 132 + || (section == LibrarySection::ArtistDetail && target == LibrarySection::Artists) 133 + || (section == LibrarySection::AlbumDetail 134 + && back_section == LibrarySection::ArtistDetail 135 + && target == LibrarySection::Artists); 74 136 div() 75 137 .id(label) 76 138 .w_full() ··· 98 160 .child(label) 99 161 }; 100 162 101 - // Content area — switches per section 163 + // ── track row helper (shared between Songs, AlbumDetail, ArtistDetail) ────── 164 + let track_row = 165 + move |row_id: (&'static str, usize), 166 + global_idx: usize, 167 + num: String, 168 + title: String, 169 + secondary: String, // artist or album depending on context 170 + show_secondary: bool, 171 + duration: u64, 172 + is_current: bool| { 173 + div() 174 + .id(row_id) 175 + .w_full() 176 + .flex() 177 + .items_center() 178 + .px_6() 179 + .py_3() 180 + .cursor_pointer() 181 + .hover(|this| this.bg(theme.library_track_bg_hover)) 182 + .when(is_current, |this| { 183 + this.bg(theme.library_track_bg_active) 184 + .border_b_2() 185 + .border_color(theme.switcher_active) 186 + }) 187 + .on_click(move |_, _, cx: &mut App| { 188 + let state = cx.global::<Controller>().state.clone(); 189 + state.update(cx, |s: &mut crate::state::AppState, _| { 190 + s.play_track(global_idx) 191 + }); 192 + }) 193 + .child( 194 + div() 195 + .w(px(32.0)) 196 + .text_sm() 197 + .text_color(theme.library_header_text) 198 + .child(num), 199 + ) 200 + .child( 201 + div() 202 + .flex_1() 203 + .text_sm() 204 + .truncate() 205 + .text_color(if is_current { 206 + theme.library_track_title_active 207 + } else { 208 + theme.library_text 209 + }) 210 + .font_weight(if is_current { 211 + FontWeight(600.0) 212 + } else { 213 + FontWeight(400.0) 214 + }) 215 + .child(title), 216 + ) 217 + .when(show_secondary, |this| { 218 + this.child( 219 + div() 220 + .w_48() 221 + .text_sm() 222 + .truncate() 223 + .text_color(theme.library_header_text) 224 + .child(secondary), 225 + ) 226 + }) 227 + .child( 228 + div() 229 + .w_16() 230 + .text_sm() 231 + .text_color(theme.library_header_text) 232 + .child(format_duration(duration)), 233 + ) 234 + }; 235 + 102 236 let content = match section { 237 + // ── Songs ───────────────────────────────────────────────────────────── 103 238 LibrarySection::Songs => div() 104 239 .flex_1() 105 240 .min_h_0() ··· 158 293 ) 159 294 .child( 160 295 uniform_list("library_tracks", n_songs, move |range, _window, cx| { 161 - let theme = *cx.global::<Theme>(); 296 + let _theme = *cx.global::<Theme>(); 162 297 let state = cx.global::<Controller>().state.read(cx); 163 298 let current_idx = state.current_idx; 164 299 range 165 300 .map(|idx| { 166 301 let track = &state.tracks[idx]; 167 302 let is_current = current_idx == Some(idx); 168 - div() 169 - .id(("track_row", idx)) 170 - .w_full() 171 - .flex() 172 - .items_center() 173 - .px_6() 174 - .py_3() 175 - .cursor_pointer() 176 - .hover(|this| this.bg(theme.library_track_bg_hover)) 177 - .when(is_current, |this| { 178 - this.bg(theme.library_track_bg_active) 179 - .border_b_2() 180 - .border_color(theme.switcher_active) 181 - }) 182 - .on_click(move |_, _, cx: &mut App| { 183 - let state = cx.global::<Controller>().state.clone(); 184 - state.update(cx, |s: &mut crate::state::AppState, _| { 185 - s.play_track(idx) 186 - }); 187 - }) 188 - .child( 189 - div() 190 - .w(px(32.0)) 191 - .text_sm() 192 - .text_color(theme.library_header_text) 193 - .child(track.track_number.to_string()), 194 - ) 195 - .child( 196 - div() 197 - .flex_1() 198 - .text_sm() 199 - .truncate() 200 - .text_color(if is_current { 201 - theme.library_track_title_active 202 - } else { 203 - theme.library_text 204 - }) 205 - .font_weight(if is_current { 206 - FontWeight(600.0) 207 - } else { 208 - FontWeight(400.0) 209 - }) 210 - .child(track.title.clone()), 211 - ) 212 - .child( 213 - div() 214 - .w_48() 215 - .text_sm() 216 - .truncate() 217 - .text_color(theme.library_header_text) 218 - .child(track.artist.clone()), 219 - ) 220 - .child( 221 - div() 222 - .w_48() 223 - .text_sm() 224 - .truncate() 225 - .text_color(theme.library_header_text) 226 - .child(track.album.clone()), 227 - ) 228 - .child( 229 - div() 230 - .w_16() 231 - .text_sm() 232 - .text_color(theme.library_header_text) 233 - .child(format_duration(track.duration)), 234 - ) 303 + track_row( 304 + ("track_row", idx), 305 + idx, 306 + track.track_number.to_string(), 307 + track.title.clone(), 308 + track.artist.clone(), 309 + true, 310 + track.duration, 311 + is_current, 312 + ) 235 313 }) 236 314 .collect() 237 315 }) ··· 241 319 ) 242 320 .into_any_element(), 243 321 322 + // ── Albums grid ─────────────────────────────────────────────────────── 244 323 LibrarySection::Albums => div() 245 324 .id("albums_scroll") 246 325 .flex_1() ··· 255 334 .gap_6() 256 335 .children(albums.into_iter().enumerate().map( 257 336 |(idx, (name, artist, _count))| { 258 - let theme = theme; 337 + let name_clone = name.clone(); 259 338 div() 260 339 .id(("album_card", idx)) 261 340 .flex() ··· 263 342 .gap_y_2() 264 343 .cursor_pointer() 265 344 .hover(|this| this.opacity(0.8)) 345 + .on_click(move |_, _, cx: &mut App| { 346 + *cx.global_mut::<SelectedAlbum>() = 347 + SelectedAlbum(name_clone.clone()); 348 + *cx.global_mut::<BackSection>() = 349 + BackSection(LibrarySection::Albums); 350 + *cx.global_mut::<LibrarySection>() = 351 + LibrarySection::AlbumDetail; 352 + }) 266 353 .child({ 267 354 let mut art = div() 268 355 .w_full() 269 356 .rounded_lg() 270 - .bg(theme.border) 357 + .bg(theme.library_art_bg) 271 358 .flex() 272 359 .items_center() 273 360 .justify_center() ··· 302 389 ) 303 390 .into_any_element(), 304 391 392 + // ── Artists grid ────────────────────────────────────────────────────── 305 393 LibrarySection::Artists => div() 306 394 .id("artists_scroll") 307 395 .flex_1() ··· 315 403 .grid_cols(artist_cols) 316 404 .gap_6() 317 405 .children(artists.into_iter().enumerate().map(|(idx, (name, count))| { 318 - let theme = theme; 406 + let name_clone = name.clone(); 319 407 div() 320 408 .id(("artist_card", idx)) 321 409 .flex() ··· 324 412 .gap_y_2() 325 413 .cursor_pointer() 326 414 .hover(|this| this.opacity(0.8)) 415 + .on_click(move |_, _, cx: &mut App| { 416 + *cx.global_mut::<SelectedArtist>() = 417 + SelectedArtist(name_clone.clone()); 418 + *cx.global_mut::<LibrarySection>() = 419 + LibrarySection::ArtistDetail; 420 + }) 327 421 .child({ 328 422 let mut avatar = div() 329 423 .w_full() 330 424 .rounded_full() 331 - .bg(theme.border) 425 + .bg(theme.library_art_bg) 332 426 .flex() 333 427 .items_center() 334 428 .justify_center() ··· 361 455 })), 362 456 ) 363 457 .into_any_element(), 458 + 459 + // ── Album Detail ────────────────────────────────────────────────────── 460 + LibrarySection::AlbumDetail => { 461 + let back_label = if back_section == LibrarySection::ArtistDetail { 462 + format!("← {}", selected_artist) 463 + } else { 464 + "← Albums".to_string() 465 + }; 466 + let n_tracks_label = format!( 467 + "{} track{}", 468 + n_album_tracks, 469 + if n_album_tracks == 1 { "" } else { "s" } 470 + ); 471 + let album_name_display = selected_album.clone(); 472 + let album_artist_display = album_artist.clone(); 473 + 474 + div() 475 + .id("album_detail_scroll") 476 + .flex_1() 477 + .min_h_0() 478 + .overflow_y_scroll() 479 + .child( 480 + div() 481 + .w_full() 482 + .flex() 483 + .flex_col() 484 + // Header 485 + .child( 486 + div() 487 + .px_6() 488 + .pt_5() 489 + .pb_6() 490 + .flex() 491 + .flex_col() 492 + .gap_y_5() 493 + // Back button 494 + .child( 495 + div() 496 + .id("album_detail_back") 497 + .cursor_pointer() 498 + .text_sm() 499 + .text_color(theme.library_header_text) 500 + .hover(|this| this.text_color(theme.library_text)) 501 + .on_click(move |_, _, cx: &mut App| { 502 + *cx.global_mut::<LibrarySection>() = back_section; 503 + }) 504 + .child(back_label), 505 + ) 506 + // Album info row 507 + .child( 508 + div() 509 + .flex() 510 + .items_center() 511 + .gap_x_6() 512 + .child( 513 + div() 514 + .w(px(128.0)) 515 + .h(px(128.0)) 516 + .rounded_lg() 517 + .flex_shrink_0() 518 + .bg(theme.library_art_bg) 519 + .flex() 520 + .items_center() 521 + .justify_center() 522 + .text_color(theme.player_icons_text) 523 + .child(Icon::new(Icons::Music).size_10()), 524 + ) 525 + .child( 526 + div() 527 + .flex() 528 + .flex_col() 529 + .gap_y_1() 530 + .child( 531 + div() 532 + .text_2xl() 533 + .font_weight(FontWeight(700.0)) 534 + .text_color(theme.library_text) 535 + .child(album_name_display), 536 + ) 537 + .child( 538 + div() 539 + .text_base() 540 + .text_color(theme.library_header_text) 541 + .child(album_artist_display), 542 + ) 543 + .child( 544 + div() 545 + .text_sm() 546 + .text_color(theme.library_header_text) 547 + .child(n_tracks_label), 548 + ), 549 + ), 550 + ), 551 + ) 552 + // Track list header 553 + .child( 554 + div() 555 + .w_full() 556 + .flex() 557 + .items_center() 558 + .px_6() 559 + .py_3() 560 + .border_b_1() 561 + .border_color(theme.library_table_border) 562 + .child( 563 + div() 564 + .w(px(32.0)) 565 + .text_xs() 566 + .font_weight(FontWeight::MEDIUM) 567 + .text_color(theme.library_header_text) 568 + .child("#"), 569 + ) 570 + .child( 571 + div() 572 + .flex_1() 573 + .text_xs() 574 + .font_weight(FontWeight::MEDIUM) 575 + .text_color(theme.library_header_text) 576 + .child("TITLE"), 577 + ) 578 + .child( 579 + div() 580 + .w_16() 581 + .text_xs() 582 + .font_weight(FontWeight::MEDIUM) 583 + .text_color(theme.library_header_text) 584 + .child("TIME"), 585 + ), 586 + ) 587 + // Track rows 588 + .children(album_tracks.into_iter().enumerate().map( 589 + |(i, (global_idx, title, num, duration))| { 590 + let is_current = current_idx == Some(global_idx); 591 + track_row( 592 + ("album_detail_row", i), 593 + global_idx, 594 + num, 595 + title, 596 + String::new(), 597 + false, 598 + duration, 599 + is_current, 600 + ) 601 + }, 602 + )), 603 + ) 604 + .into_any_element() 605 + } 606 + 607 + // ── Artist Detail ───────────────────────────────────────────────────── 608 + LibrarySection::ArtistDetail => { 609 + let n_tracks_label = format!( 610 + "{} track{}", 611 + n_artist_tracks, 612 + if n_artist_tracks == 1 { "" } else { "s" } 613 + ); 614 + let artist_name_display = selected_artist.clone(); 615 + let sa_clone = selected_artist.clone(); 616 + 617 + div() 618 + .id("artist_detail_scroll") 619 + .flex_1() 620 + .min_h_0() 621 + .overflow_y_scroll() 622 + .child( 623 + div() 624 + .w_full() 625 + .flex() 626 + .flex_col() 627 + // Header 628 + .child( 629 + div() 630 + .px_6() 631 + .pt_5() 632 + .pb_6() 633 + .flex() 634 + .flex_col() 635 + .gap_y_5() 636 + // Back button 637 + .child( 638 + div() 639 + .id("artist_detail_back") 640 + .cursor_pointer() 641 + .text_sm() 642 + .text_color(theme.library_header_text) 643 + .hover(|this| this.text_color(theme.library_text)) 644 + .on_click(|_, _, cx: &mut App| { 645 + *cx.global_mut::<LibrarySection>() = 646 + LibrarySection::Artists; 647 + }) 648 + .child("← Artists"), 649 + ) 650 + // Artist info row 651 + .child( 652 + div() 653 + .flex() 654 + .items_center() 655 + .gap_x_6() 656 + .child( 657 + div() 658 + .w(px(96.0)) 659 + .h(px(96.0)) 660 + .rounded_full() 661 + .flex_shrink_0() 662 + .bg(theme.library_art_bg) 663 + .flex() 664 + .items_center() 665 + .justify_center() 666 + .text_color(theme.player_icons_text) 667 + .child(Icon::new(Icons::Music).size_8()), 668 + ) 669 + .child( 670 + div() 671 + .flex() 672 + .flex_col() 673 + .gap_y_1() 674 + .child( 675 + div() 676 + .text_2xl() 677 + .font_weight(FontWeight(700.0)) 678 + .text_color(theme.library_text) 679 + .child(artist_name_display), 680 + ) 681 + .child( 682 + div() 683 + .text_sm() 684 + .text_color(theme.library_header_text) 685 + .child(n_tracks_label), 686 + ), 687 + ), 688 + ), 689 + ) 690 + // Albums section 691 + .child( 692 + div() 693 + .px_6() 694 + .pb_2() 695 + .text_sm() 696 + .font_weight(FontWeight(600.0)) 697 + .text_color(theme.library_text) 698 + .child("Albums"), 699 + ) 700 + .child( 701 + div() 702 + .w_full() 703 + .px_6() 704 + .pb_6() 705 + .grid() 706 + .grid_cols(detail_album_cols) 707 + .gap_4() 708 + .children( 709 + artist_albums_detail.into_iter().enumerate().map( 710 + |(idx, (album_name, _count))| { 711 + let album_name_clone = album_name.clone(); 712 + let sa = sa_clone.clone(); 713 + div() 714 + .id(("artist_album_card", idx)) 715 + .flex() 716 + .flex_col() 717 + .gap_y_2() 718 + .cursor_pointer() 719 + .hover(|this| this.opacity(0.8)) 720 + .on_click(move |_, _, cx: &mut App| { 721 + *cx.global_mut::<SelectedAlbum>() = 722 + SelectedAlbum(album_name_clone.clone()); 723 + *cx.global_mut::<SelectedArtist>() = 724 + SelectedArtist(sa.clone()); 725 + *cx.global_mut::<BackSection>() = 726 + BackSection(LibrarySection::ArtistDetail); 727 + *cx.global_mut::<LibrarySection>() = 728 + LibrarySection::AlbumDetail; 729 + }) 730 + .child({ 731 + let mut art = div() 732 + .w_full() 733 + .rounded_lg() 734 + .bg(theme.library_art_bg) 735 + .flex() 736 + .items_center() 737 + .justify_center() 738 + .text_color(theme.player_icons_text) 739 + .child(Icon::new(Icons::Music).size_6()); 740 + art.style().aspect_ratio = Some(1.0_f32); 741 + art 742 + }) 743 + .child( 744 + div() 745 + .text_xs() 746 + .font_weight(FontWeight(500.0)) 747 + .text_color(theme.library_text) 748 + .truncate() 749 + .child(album_name), 750 + ) 751 + }, 752 + ), 753 + ), 754 + ) 755 + // Songs section header 756 + .child( 757 + div() 758 + .px_6() 759 + .pb_2() 760 + .text_sm() 761 + .font_weight(FontWeight(600.0)) 762 + .text_color(theme.library_text) 763 + .child("Songs"), 764 + ) 765 + .child( 766 + div() 767 + .w_full() 768 + .flex() 769 + .items_center() 770 + .px_6() 771 + .py_3() 772 + .border_b_1() 773 + .border_color(theme.library_table_border) 774 + .child( 775 + div() 776 + .w(px(32.0)) 777 + .text_xs() 778 + .font_weight(FontWeight::MEDIUM) 779 + .text_color(theme.library_header_text) 780 + .child("#"), 781 + ) 782 + .child( 783 + div() 784 + .flex_1() 785 + .text_xs() 786 + .font_weight(FontWeight::MEDIUM) 787 + .text_color(theme.library_header_text) 788 + .child("TITLE"), 789 + ) 790 + .child( 791 + div() 792 + .w_48() 793 + .text_xs() 794 + .font_weight(FontWeight::MEDIUM) 795 + .text_color(theme.library_header_text) 796 + .child("ALBUM"), 797 + ) 798 + .child( 799 + div() 800 + .w_16() 801 + .text_xs() 802 + .font_weight(FontWeight::MEDIUM) 803 + .text_color(theme.library_header_text) 804 + .child("TIME"), 805 + ), 806 + ) 807 + // Artist track rows 808 + .children(artist_tracks.into_iter().enumerate().map( 809 + |(i, (global_idx, title, album, duration))| { 810 + let is_current = current_idx == Some(global_idx); 811 + track_row( 812 + ("artist_detail_row", i), 813 + global_idx, 814 + format!("{}", i + 1), 815 + title, 816 + album, 817 + true, 818 + duration, 819 + is_current, 820 + ) 821 + }, 822 + )), 823 + ) 824 + .into_any_element() 825 + } 364 826 }; 365 827 366 828 div() ··· 387 849 .border_r_1() 388 850 .border_color(theme.library_table_border) 389 851 .pt_4() 390 - // Search input 391 852 .child(self.search_input.clone()) 392 853 .gap_y_1() 393 854 .child(make_nav_item("Songs", LibrarySection::Songs)) 394 855 .child(make_nav_item("Albums", LibrarySection::Albums)) 395 856 .child(make_nav_item("Artists", LibrarySection::Artists)), 396 857 ) 397 - // Content area 398 858 .child(content), 399 859 ) 400 860 .child(self.miniplayer.clone())
+1 -1
gpui/src/ui/components/pages/player.rs
··· 67 67 .flex() 68 68 .items_center() 69 69 .justify_center() 70 - .bg(theme.border) 70 + .bg(theme.library_art_bg) 71 71 .text_color(theme.player_icons_text) 72 72 .child(Icon::new(Icons::Music).size_16()), 73 73 )
+5
gpui/src/ui/theme.rs
··· 63 63 // Common 64 64 pub border: Rgba, 65 65 pub scrollbar_thumb: Rgba, 66 + 67 + // Library art / avatar placeholder 68 + pub library_art_bg: Rgba, 66 69 } 67 70 68 71 impl Default for Theme { ··· 125 128 126 129 border: rgba(0xFFFFFF29), 127 130 scrollbar_thumb: rgb(0x6F00FF), 131 + 132 + library_art_bg: rgba(0xFFFFFF0D), 128 133 } 129 134 } 130 135 }