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.

fix gpui app window controls issue

+234 -174
+1
gpui/.gitignore
··· 1 1 target/ 2 2 dist/ 3 + .claude
+3 -1
gpui/src/app.rs
··· 4 4 use crate::ui::{assets::Assets, theme::Theme}; 5 5 use gpui::{ 6 6 px, size, AppContext, Application, Bounds, Menu, MenuItem, SystemMenuType, TitlebarOptions, 7 - WindowBounds, WindowOptions, 7 + WindowBounds, WindowDecorations, WindowOptions, 8 8 }; 9 9 10 10 pub fn run() { ··· 45 45 width: px(800.0), 46 46 height: px(600.0), 47 47 }), 48 + #[cfg(target_os = "linux")] 49 + window_decorations: Some(WindowDecorations::Client), 48 50 ..Default::default() 49 51 }, 50 52 |_window, cx| cx.new(StartupGate::new),
+21 -30
gpui/src/client.rs
··· 1 1 use crate::api::v1alpha1::{ 2 2 bluetooth_service_client::BluetoothServiceClient, browse_service_client::BrowseServiceClient, 3 - library_service_client::LibraryServiceClient, 4 - playback_service_client::PlaybackServiceClient, playlist_service_client::PlaylistServiceClient, 5 - settings_service_client::SettingsServiceClient, sound_service_client::SoundServiceClient, 6 - system_service_client::SystemServiceClient, AdjustVolumeRequest, FastForwardRewindRequest, 7 - GetArtistsRequest, GetCurrentRequest, GetGlobalSettingsRequest, GetGlobalStatusRequest, 8 - GetAlbumRequest, GetLikedTracksRequest, GetTracksRequest, InsertDirectoryRequest, 9 - ConnectBluetoothDeviceRequest, DisconnectBluetoothDeviceRequest, 10 - GetBluetoothDevicesRequest, InsertTracksRequest, 11 - LikeTrackRequest, NextRequest, PauseRequest, PlayAlbumRequest, PlayAllTracksRequest, 12 - PlayArtistTracksRequest, PlayDirectoryRequest, PlayTrackRequest, PlaylistResumeRequest, 13 - PreviousRequest, RemoveTracksRequest, ResumeRequest, ResumeTrackRequest, SaveSettingsRequest, 14 - SearchRequest, ShufflePlaylistRequest, SoundCurrentRequest, StartRequest, StatusRequest, 3 + library_service_client::LibraryServiceClient, playback_service_client::PlaybackServiceClient, 4 + playlist_service_client::PlaylistServiceClient, settings_service_client::SettingsServiceClient, 5 + sound_service_client::SoundServiceClient, system_service_client::SystemServiceClient, 6 + AdjustVolumeRequest, ConnectBluetoothDeviceRequest, DisconnectBluetoothDeviceRequest, 7 + FastForwardRewindRequest, GetAlbumRequest, GetArtistsRequest, GetBluetoothDevicesRequest, 8 + GetCurrentRequest, GetGlobalSettingsRequest, GetGlobalStatusRequest, GetLikedTracksRequest, 9 + GetTracksRequest, InsertDirectoryRequest, InsertTracksRequest, LikeTrackRequest, NextRequest, 10 + PauseRequest, PlayAlbumRequest, PlayAllTracksRequest, PlayArtistTracksRequest, 11 + PlayDirectoryRequest, PlayTrackRequest, PlaylistResumeRequest, PreviousRequest, 12 + RemoveTracksRequest, ResumeRequest, ResumeTrackRequest, SaveSettingsRequest, SearchRequest, 13 + ShufflePlaylistRequest, SoundCurrentRequest, StartRequest, StatusRequest, 15 14 StreamCurrentTrackRequest, StreamLibraryRequest, StreamPlaylistRequest, StreamStatusRequest, 16 15 TreeGetEntriesRequest, UnlikeTrackRequest, 17 16 }; ··· 46 45 .collect()) 47 46 } 48 47 49 - pub async fn get_album( 50 - id: &str, 51 - ) -> Result<(String, Option<String>)> { 48 + pub async fn get_album(id: &str) -> Result<(String, Option<String>)> { 52 49 let mut c = LibraryServiceClient::connect(url()).await?; 53 50 let resp = c.get_album(GetAlbumRequest { id: id.to_string() }).await?; 54 51 let album = resp.into_inner().album; ··· 792 789 smart_playlist_service_client::SmartPlaylistServiceClient, GetSmartPlaylistsRequest, 793 790 }; 794 791 let mut c = SmartPlaylistServiceClient::connect(url()).await?; 795 - let resp = c 796 - .get_smart_playlists(GetSmartPlaylistsRequest {}) 797 - .await?; 792 + let resp = c.get_smart_playlists(GetSmartPlaylistsRequest {}).await?; 798 793 Ok(resp 799 794 .into_inner() 800 795 .playlists ··· 836 831 837 832 pub async fn add_track_to_playlist(playlist_id: String, track_id: String) -> Result<()> { 838 833 use crate::api::v1alpha1::{ 839 - saved_playlist_service_client::SavedPlaylistServiceClient, 840 - AddTracksToSavedPlaylistRequest, 834 + saved_playlist_service_client::SavedPlaylistServiceClient, AddTracksToSavedPlaylistRequest, 841 835 }; 842 836 let mut c = SavedPlaylistServiceClient::connect(url()).await?; 843 837 c.add_tracks_to_saved_playlist(AddTracksToSavedPlaylistRequest { ··· 851 845 /// Fetch track IDs for a saved playlist and resolve them from the provided tracks map. 852 846 pub async fn fetch_saved_playlist_track_ids(playlist_id: String) -> Result<Vec<String>> { 853 847 use crate::api::v1alpha1::{ 854 - saved_playlist_service_client::SavedPlaylistServiceClient, 855 - GetSavedPlaylistTracksRequest, 848 + saved_playlist_service_client::SavedPlaylistServiceClient, GetSavedPlaylistTracksRequest, 856 849 }; 857 850 let mut c = SavedPlaylistServiceClient::connect(url()).await?; 858 851 let resp = c ··· 864 857 /// Fetch track IDs for a smart playlist. 865 858 pub async fn fetch_smart_playlist_track_ids(playlist_id: String) -> Result<Vec<String>> { 866 859 use crate::api::v1alpha1::{ 867 - smart_playlist_service_client::SmartPlaylistServiceClient, 868 - GetSmartPlaylistTracksRequest, 860 + smart_playlist_service_client::SmartPlaylistServiceClient, GetSmartPlaylistTracksRequest, 869 861 }; 870 862 let mut c = SmartPlaylistServiceClient::connect(url()).await?; 871 863 let resp = c ··· 924 916 Ok(()) 925 917 } 926 918 927 - pub async fn remove_track_from_saved_playlist( 928 - playlist_id: String, 929 - track_id: String, 930 - ) -> Result<()> { 919 + pub async fn remove_track_from_saved_playlist(playlist_id: String, track_id: String) -> Result<()> { 931 920 use crate::api::v1alpha1::{ 932 921 saved_playlist_service_client::SavedPlaylistServiceClient, 933 922 RemoveTrackFromSavedPlaylistRequest, ··· 1016 1005 1017 1006 pub async fn connect_bluetooth_device(address: String) -> Result<()> { 1018 1007 let mut c = BluetoothServiceClient::connect(url()).await?; 1019 - c.connect_device(ConnectBluetoothDeviceRequest { address }).await?; 1008 + c.connect_device(ConnectBluetoothDeviceRequest { address }) 1009 + .await?; 1020 1010 Ok(()) 1021 1011 } 1022 1012 1023 1013 pub async fn disconnect_bluetooth_device(address: String) -> Result<()> { 1024 1014 let mut c = BluetoothServiceClient::connect(url()).await?; 1025 - c.disconnect(DisconnectBluetoothDeviceRequest { address }).await?; 1015 + c.disconnect(DisconnectBluetoothDeviceRequest { address }) 1016 + .await?; 1026 1017 Ok(()) 1027 1018 }
+22 -26
gpui/src/ui/components/bluetooth_picker.rs
··· 111 111 let addr = address.clone(); 112 112 if is_connected { 113 113 rt.spawn(async move { 114 - let _ = crate::client::disconnect_bluetooth_device(addr).await; 114 + let _ = crate::client::disconnect_bluetooth_device( 115 + addr, 116 + ) 117 + .await; 115 118 }); 116 119 } else { 117 120 rt.spawn(async move { 118 - let _ = crate::client::connect_bluetooth_device(addr).await; 121 + let _ = 122 + crate::client::connect_bluetooth_device(addr) 123 + .await; 119 124 }); 120 125 } 121 126 let mut state = cx.global::<BluetoothState>().clone(); ··· 132 137 .flex_shrink_0() 133 138 .child(Icon::new(Icons::Bluetooth).size_4()), 134 139 ) 135 - .child( 136 - div() 137 - .flex_1() 138 - .text_sm() 139 - .truncate() 140 - .child(name), 141 - ) 140 + .child(div().flex_1().text_sm().truncate().child(name)) 142 141 .when(is_connected, |this: gpui::Stateful<gpui::Div>| { 143 142 this.child( 144 143 div() ··· 180 179 } 181 180 }); 182 181 183 - cx.spawn(async move |cx| { 184 - loop { 185 - while let Ok(available) = rx.try_recv() { 186 - if cx 187 - .update(|cx| { 188 - let mut state = cx.global::<BluetoothState>().clone(); 189 - state.available = available; 190 - cx.set_global(state); 191 - }) 192 - .is_err() 193 - { 194 - return; 195 - } 182 + cx.spawn(async move |cx| loop { 183 + while let Ok(available) = rx.try_recv() { 184 + if cx 185 + .update(|cx| { 186 + let mut state = cx.global::<BluetoothState>().clone(); 187 + state.available = available; 188 + cx.set_global(state); 189 + }) 190 + .is_err() 191 + { 192 + return; 196 193 } 197 - cx.background_executor() 198 - .timer(std::time::Duration::from_millis(200)) 199 - .await; 200 194 } 195 + cx.background_executor() 196 + .timer(std::time::Duration::from_millis(200)) 197 + .await; 201 198 }) 202 199 .detach(); 203 200 } ··· 221 218 }) 222 219 .detach(); 223 220 } 224 -
+4 -15
gpui/src/ui/components/device_picker.rs
··· 5 5 use gpui::prelude::FluentBuilder; 6 6 use gpui::{ 7 7 div, px, App, Context, FontWeight, InteractiveElement, IntoElement, MouseButton, 8 - ParentElement, Render, StatefulInteractiveElement, Styled, Window, MouseMoveEvent, 8 + MouseMoveEvent, ParentElement, Render, StatefulInteractiveElement, Styled, Window, 9 9 }; 10 10 11 11 pub fn device_icon(device: &DeviceItem) -> Icons { ··· 122 122 let rt = cx.global::<Controller>().rt(); 123 123 let id_clone = id.clone(); 124 124 rt.spawn(async move { 125 - let _ = 126 - crate::client::connect_device(id_clone).await; 125 + let _ = crate::client::connect_device(id_clone).await; 127 126 }); 128 127 let mut state = cx.global::<DevicesState>().clone(); 129 128 for d in state.devices.iter_mut() { ··· 132 131 state.picker_open = false; 133 132 cx.set_global(state); 134 133 }) 135 - .child( 136 - div() 137 - .flex_shrink_0() 138 - .child(Icon::new(icon).size_4()), 139 - ) 140 - .child( 141 - div() 142 - .flex_1() 143 - .text_sm() 144 - .truncate() 145 - .child(name), 146 - ) 134 + .child(div().flex_shrink_0().child(Icon::new(icon).size_4())) 135 + .child(div().flex_1().text_sm().truncate().child(name)) 147 136 .when(is_current, |this: gpui::Stateful<gpui::Div>| { 148 137 this.child( 149 138 div()
+6
gpui/src/ui/components/icons.rs
··· 152 152 Artist, 153 153 Disc, 154 154 WinClose, 155 + WinMinimize, 156 + WinMaximize, 157 + WinRestore, 155 158 Play, 156 159 Pause, 157 160 Next, ··· 191 194 Icons::Artist => "icons/artist.svg", 192 195 Icons::Disc => "icons/disc.svg", 193 196 Icons::WinClose => "icons/window-close.svg", 197 + Icons::WinMinimize => "icons/window-minimize.svg", 198 + Icons::WinMaximize => "icons/window-maximize.svg", 199 + Icons::WinRestore => "icons/window-restore.svg", 194 200 Icons::Play => "icons/play.svg", 195 201 Icons::Pause => "icons/pause.svg", 196 202 Icons::Next => "icons/next.svg",
+23 -20
gpui/src/ui/components/miniplayer.rs
··· 13 13 use crate::ui::theme::Theme; 14 14 use gpui::prelude::FluentBuilder; 15 15 use gpui::{ 16 - div, img, px, App, Context, FontWeight, InteractiveElement, IntoElement, 17 - ObjectFit, ParentElement, Render, ScrollWheelEvent, StatefulInteractiveElement, Styled, 18 - StyledImage, Window, 16 + div, img, px, App, Context, FontWeight, InteractiveElement, IntoElement, ObjectFit, 17 + ParentElement, Render, ScrollWheelEvent, StatefulInteractiveElement, Styled, StyledImage, 18 + Window, 19 19 }; 20 20 21 21 pub struct MiniPlayer; ··· 474 474 theme.volume_slider_fill, 475 475 px(4.0), 476 476 ) 477 - .on_seek(move |fraction, _window, cx| { 478 - let range = (VOLUME_MAX_DB - VOLUME_MIN_DB) as f32; 479 - let new_vol = (VOLUME_MIN_DB as f32 480 - + fraction * range) 481 - .round() as i32; 482 - let steps = { 483 - let current = state_ref.read(cx).volume; 484 - new_vol - current 485 - }; 486 - if steps != 0 { 487 - state_ref.update(cx, |s, cx| { 488 - s.volume = new_vol; 489 - cx.notify(); 490 - }); 491 - rt.spawn(adjust_volume(steps)); 492 - } 493 - }) 477 + .on_seek( 478 + move |fraction, _window, cx| { 479 + let range = (VOLUME_MAX_DB - VOLUME_MIN_DB) as f32; 480 + let new_vol = (VOLUME_MIN_DB as f32 481 + + fraction * range) 482 + .round() 483 + as i32; 484 + let steps = { 485 + let current = state_ref.read(cx).volume; 486 + new_vol - current 487 + }; 488 + if steps != 0 { 489 + state_ref.update(cx, |s, cx| { 490 + s.volume = new_vol; 491 + cx.notify(); 492 + }); 493 + rt.spawn(adjust_volume(steps)); 494 + } 495 + }, 496 + ) 494 497 }), 495 498 ) 496 499 .child(
+1 -1
gpui/src/ui/components/mod.rs
··· 6 6 pub mod navbar; 7 7 pub mod pages; 8 8 pub mod search_input; 9 - pub mod text_input; 10 9 pub mod seek_bar; 10 + pub mod text_input; 11 11 pub mod titlebar; 12 12 13 13 #[derive(Clone, Copy, PartialEq)]
+16 -10
gpui/src/ui/components/pages/files.rs
··· 1 1 use crate::client::{play_directory, play_directory_at, FileEntry}; 2 2 use crate::controller::Controller; 3 3 use crate::ui::components::icons::{Icon, Icons}; 4 - use crate::ui::components::{ 5 - FileContextMenu, FileContextMenuState, FilesBrowseState, FilesMode, 6 - }; 4 + use crate::ui::components::{FileContextMenu, FileContextMenuState, FilesBrowseState, FilesMode}; 7 5 use crate::ui::theme::Theme; 8 6 use gpui::prelude::FluentBuilder; 9 7 use gpui::{ 10 - div, px, uniform_list, AnyElement, App, ClickEvent, Context, FontWeight, 11 - InteractiveElement, IntoElement, ParentElement, Render, StatefulInteractiveElement, Styled, 12 - WeakEntity, Window, 8 + div, px, uniform_list, AnyElement, App, ClickEvent, Context, FontWeight, InteractiveElement, 9 + IntoElement, ParentElement, Render, StatefulInteractiveElement, Styled, WeakEntity, Window, 13 10 }; 14 11 use std::collections::HashMap; 15 12 use std::time::{Duration, Instant}; ··· 49 46 let _ = this.update(cx, |this, _cx| { 50 47 this.upnp_cache.insert( 51 48 "upnp://".to_string(), 52 - CacheEntry { entries: devices, fetched_at: Instant::now() }, 49 + CacheEntry { 50 + entries: devices, 51 + fetched_at: Instant::now(), 52 + }, 53 53 ); 54 54 }); 55 55 } ··· 135 135 ) { 136 136 let (tx, rx) = tokio::sync::oneshot::channel::<Vec<FileEntry>>(); 137 137 cx.global::<Controller>().rt().spawn(async move { 138 - let entries = crate::client::tree_get_entries(path).await.unwrap_or_default(); 138 + let entries = crate::client::tree_get_entries(path) 139 + .await 140 + .unwrap_or_default(); 139 141 let _ = tx.send(entries); 140 142 }); 141 143 cx.spawn(async move |this: WeakEntity<FilesView>, cx| { ··· 145 147 if let Some(ref ck) = cache_key { 146 148 this.upnp_cache.insert( 147 149 ck.clone(), 148 - CacheEntry { entries: entries.clone(), fetched_at: Instant::now() }, 150 + CacheEntry { 151 + entries: entries.clone(), 152 + fetched_at: Instant::now(), 153 + }, 149 154 ); 150 155 } 151 156 // Only update the displayed entries if the user is still ··· 280 285 .cursor_pointer() 281 286 .hover(|t| t.bg(theme.library_track_bg_hover)) 282 287 .on_click(|_, _, cx: &mut App| { 283 - cx.global_mut::<FilesBrowseState>().navigate(FilesMode::Local, None); 288 + cx.global_mut::<FilesBrowseState>() 289 + .navigate(FilesMode::Local, None); 284 290 }) 285 291 .child( 286 292 div()
+53 -42
gpui/src/ui/components/pages/library.rs
··· 32 32 /// Parse "yyyy-MM-dd" into "9 December 2014". Falls back to the raw string on any parse failure. 33 33 fn format_release_date(s: &str) -> String { 34 34 const MONTHS: [&str; 12] = [ 35 - "January", "February", "March", "April", "May", "June", 36 - "July", "August", "September", "October", "November", "December", 35 + "January", 36 + "February", 37 + "March", 38 + "April", 39 + "May", 40 + "June", 41 + "July", 42 + "August", 43 + "September", 44 + "October", 45 + "November", 46 + "December", 37 47 ]; 38 48 let parts: Vec<&str> = s.splitn(3, '-').collect(); 39 49 if parts.len() == 3 { ··· 202 212 let result = cx 203 213 .background_executor() 204 214 .spawn(async move { 205 - tokio.block_on(async { 206 - crate::client::get_album(&album_id).await 207 - }) 215 + tokio.block_on(async { crate::client::get_album(&album_id).await }) 208 216 }) 209 217 .await; 210 218 let _ = cx.update(|app: &mut gpui::App| { ··· 248 256 cx.spawn(async move |_, cx| { 249 257 let found = cx 250 258 .background_executor() 251 - .spawn(async move { 252 - crate::server::scan_mdns(std::time::Duration::from_secs(3)) 253 - }) 259 + .spawn(async move { crate::server::scan_mdns(std::time::Duration::from_secs(3)) }) 254 260 .await; 255 261 let _ = cx.update(|app: &mut gpui::App| { 256 262 let state = app.global_mut::<DiscoveredServers>(); ··· 265 271 detail_scroll_handle: UniformListScrollHandle::new(), 266 272 miniplayer: { 267 273 cx.new(|cx| { 268 - cx.observe_global::<DevicesState>(|_, cx| cx.notify()).detach(); 274 + cx.observe_global::<DevicesState>(|_, cx| cx.notify()) 275 + .detach(); 269 276 MiniPlayer 270 277 }) 271 278 }, ··· 334 341 }) 335 342 }) 336 343 .await; 337 - let id_set: std::collections::HashSet<String> = 338 - track_ids.iter().cloned().collect(); 344 + let id_set: std::collections::HashSet<String> = track_ids.iter().cloned().collect(); 339 345 let mut resolved: Vec<crate::state::Track> = all_tracks 340 346 .into_iter() 341 347 .filter(|t| id_set.contains(&t.id)) 342 348 .collect(); 343 - let order_map: std::collections::HashMap<String, usize> = 344 - track_ids.into_iter().enumerate().map(|(i, id)| (id, i)).collect(); 345 - resolved.sort_by_key(|t| { 346 - order_map.get(&t.id).copied().unwrap_or(usize::MAX) 347 - }); 349 + let order_map: std::collections::HashMap<String, usize> = track_ids 350 + .into_iter() 351 + .enumerate() 352 + .map(|(i, id)| (id, i)) 353 + .collect(); 354 + resolved.sort_by_key(|t| order_map.get(&t.id).copied().unwrap_or(usize::MAX)); 348 355 let _ = cx.update(|app: &mut gpui::App| { 349 356 app.global_mut::<PlaylistsState>().playlist_tracks = resolved; 350 357 }); ··· 3278 3285 .background_executor() 3279 3286 .spawn(async move { 3280 3287 tokio.block_on(async { 3281 - let saved = crate::client::fetch_saved_playlists().await.unwrap_or_default(); 3282 - let smart = crate::client::fetch_smart_playlists().await.unwrap_or_default(); 3288 + let saved = 3289 + crate::client::fetch_saved_playlists() 3290 + .await 3291 + .unwrap_or_default(); 3292 + let smart = 3293 + crate::client::fetch_smart_playlists() 3294 + .await 3295 + .unwrap_or_default(); 3283 3296 (saved, smart) 3284 3297 }) 3285 3298 }) ··· 3293 3306 .detach(); 3294 3307 cx.notify(); 3295 3308 })) 3296 - .child( 3297 - div() 3298 - .w(px(6.0)) 3299 - .h(px(6.0)) 3300 - .rounded_full() 3301 - .bg(if cur_is_local { 3302 - gpui::rgb(0x39FF14) 3303 - } else { 3304 - theme.library_header_text 3305 - }), 3306 - ) 3309 + .child(div().w(px(6.0)).h(px(6.0)).rounded_full().bg( 3310 + if cur_is_local { 3311 + gpui::rgb(0x39FF14) 3312 + } else { 3313 + theme.library_header_text 3314 + }, 3315 + )) 3307 3316 .child( 3308 3317 div() 3309 3318 .flex_1() ··· 3346 3355 .background_executor() 3347 3356 .spawn(async move { 3348 3357 tokio.block_on(async { 3349 - let saved = crate::client::fetch_saved_playlists().await.unwrap_or_default(); 3350 - let smart = crate::client::fetch_smart_playlists().await.unwrap_or_default(); 3358 + let saved = 3359 + crate::client::fetch_saved_playlists() 3360 + .await 3361 + .unwrap_or_default(); 3362 + let smart = 3363 + crate::client::fetch_smart_playlists() 3364 + .await 3365 + .unwrap_or_default(); 3351 3366 (saved, smart) 3352 3367 }) 3353 3368 }) ··· 3361 3376 .detach(); 3362 3377 cx.notify(); 3363 3378 })) 3364 - .child( 3365 - div() 3366 - .w(px(6.0)) 3367 - .h(px(6.0)) 3368 - .rounded_full() 3369 - .bg(if is_active { 3370 - gpui::rgb(0x39FF14) 3371 - } else { 3372 - theme.library_header_text 3373 - }), 3374 - ) 3379 + .child(div().w(px(6.0)).h(px(6.0)).rounded_full().bg( 3380 + if is_active { 3381 + gpui::rgb(0x39FF14) 3382 + } else { 3383 + theme.library_header_text 3384 + }, 3385 + )) 3375 3386 .child( 3376 3387 div() 3377 3388 .flex_1()
+2 -1
gpui/src/ui/components/pages/queue.rs
··· 22 22 QueuePage { 23 23 scroll_handle: UniformListScrollHandle::new(), 24 24 miniplayer: cx.new(|cx| { 25 - cx.observe_global::<DevicesState>(|_, cx| cx.notify()).detach(); 25 + cx.observe_global::<DevicesState>(|_, cx| cx.notify()) 26 + .detach(); 26 27 MiniPlayer 27 28 }), 28 29 last_scrolled_idx: None,
+68 -21
gpui/src/ui/components/titlebar.rs
··· 2 2 use crate::ui::components::icons::{Icon, Icons}; 3 3 use crate::ui::theme::Theme; 4 4 use gpui::{ 5 - div, white, App, AppContext, Context, Entity, InteractiveElement, IntoElement, ParentElement, 6 - Render, Styled, Window, WindowControlArea, 5 + div, prelude::FluentBuilder, white, App, AppContext, Context, Entity, InteractiveElement, 6 + IntoElement, MouseButton, ParentElement, Render, Styled, Window, 7 7 }; 8 8 9 9 #[derive(Clone)] ··· 39 39 .id("drag_area_left") 40 40 .w_full() 41 41 .h_full() 42 - .window_control_area(WindowControlArea::Drag), 42 + .on_mouse_down(MouseButton::Left, |_, window, _| { 43 + window.start_window_move(); 44 + }), 43 45 ), 44 46 ) 45 47 .child( ··· 56 58 .id("drag_area_center_left") 57 59 .w_full() 58 60 .h_full() 59 - .window_control_area(WindowControlArea::Drag), 61 + .on_mouse_down(MouseButton::Left, |_, window, _| { 62 + window.start_window_move(); 63 + }), 60 64 ) 61 65 .child(self.navbar.clone()) 62 66 .child( ··· 64 68 .id("drag_area_center_right") 65 69 .w_full() 66 70 .h_full() 67 - .window_control_area(WindowControlArea::Drag), 71 + .on_mouse_down(MouseButton::Left, |_, window, _| { 72 + window.start_window_move(); 73 + }), 68 74 ), 69 75 ) 70 76 .child( ··· 80 86 .id("drag_area_right") 81 87 .w_full() 82 88 .h_full() 83 - .window_control_area(WindowControlArea::Drag), 89 + .on_mouse_down(MouseButton::Left, |_, window, _| { 90 + window.start_window_move(); 91 + }), 84 92 ) 85 - .child( 86 - div() 87 - .id("win_close_btn") 88 - .h_8() 89 - .w_8() 90 - .rounded_full() 91 - .flex() 92 - .items_center() 93 - .justify_center() 94 - .hover(|this| this.bg(theme.titlebar_window_icons_bg_hover)) 95 - .text_color(theme.titlebar_window_icons_text) 96 - .cursor_pointer() 97 - .child(Icon::new(Icons::WinClose)) 98 - .window_control_area(WindowControlArea::Close), 99 - ), 93 + .when(cfg!(target_os = "linux"), |parent| { 94 + parent 95 + .child( 96 + div() 97 + .id("win_minimize_btn") 98 + .h_8() 99 + .w_8() 100 + .rounded_full() 101 + .flex() 102 + .items_center() 103 + .justify_center() 104 + .hover(|this| this.bg(theme.titlebar_window_icons_bg_hover)) 105 + .text_color(theme.titlebar_window_icons_text) 106 + .cursor_pointer() 107 + .child(Icon::new(Icons::WinMinimize)) 108 + .on_mouse_down(MouseButton::Left, |_, window, _| { 109 + window.minimize_window(); 110 + }), 111 + ) 112 + .child( 113 + div() 114 + .id("win_maximize_btn") 115 + .h_8() 116 + .w_8() 117 + .rounded_full() 118 + .flex() 119 + .items_center() 120 + .justify_center() 121 + .hover(|this| this.bg(theme.titlebar_window_icons_bg_hover)) 122 + .text_color(theme.titlebar_window_icons_text) 123 + .cursor_pointer() 124 + .child(Icon::new(Icons::WinMaximize)) 125 + .on_mouse_down(MouseButton::Left, |_, window, _| { 126 + window.zoom_window(); 127 + }), 128 + ) 129 + .child( 130 + div() 131 + .id("win_close_btn") 132 + .h_8() 133 + .w_8() 134 + .rounded_full() 135 + .flex() 136 + .items_center() 137 + .justify_center() 138 + .hover(|this| this.bg(theme.titlebar_window_icons_bg_hover)) 139 + .text_color(theme.titlebar_window_icons_text) 140 + .cursor_pointer() 141 + .child(Icon::new(Icons::WinClose)) 142 + .on_mouse_down(MouseButton::Left, |_, window, _| { 143 + window.remove_window(); 144 + }), 145 + ) 146 + }), 100 147 ) 101 148 } 102 149 }
+5 -1
gpui/src/ui/global_keybinds.rs
··· 9 9 10 10 pub fn register_keybinds(cx: &mut App) { 11 11 cx.bind_keys([ 12 - KeyBinding::new("space", PlayPause, Some("Rockbox && !SearchInput && !TextInput")), 12 + KeyBinding::new( 13 + "space", 14 + PlayPause, 15 + Some("Rockbox && !SearchInput && !TextInput"), 16 + ), 13 17 KeyBinding::new("cmd-right", Next, None), 14 18 KeyBinding::new("cmd-left", Prev, None), 15 19 KeyBinding::new("shift-s", Shuffle, None),
+9 -6
gpui/src/ui/rockbox.rs
··· 11 11 use gpui::prelude::FluentBuilder; 12 12 use gpui::{ 13 13 div, px, Animation, AnimationExt as _, AppContext, Context, ElementId, Entity, FocusHandle, 14 - InteractiveElement, IntoElement, ParentElement, Render, Styled, 15 - Window, 14 + InteractiveElement, IntoElement, ParentElement, Render, Styled, Window, 16 15 }; 17 16 18 17 pub struct Rockbox { ··· 34 33 global_keybinds::register_keybinds(cx); 35 34 let titlebar = cx.new(|cx| Titlebar::new(cx)); 36 35 let controlbar = cx.new(|cx| { 37 - cx.observe_global::<DevicesState>(|_, cx| cx.notify()).detach(); 38 - cx.observe_global::<BluetoothState>(|_, cx| cx.notify()).detach(); 36 + cx.observe_global::<DevicesState>(|_, cx| cx.notify()) 37 + .detach(); 38 + cx.observe_global::<BluetoothState>(|_, cx| cx.notify()) 39 + .detach(); 39 40 ControlBar 40 41 }); 41 42 let player_page = cx.new(|cx| PlayerPage::new(cx, controlbar)); 42 43 let library_page = cx.new(|cx| LibraryPage::new(cx)); 43 44 let queue_page = cx.new(|cx| QueuePage::new(cx)); 44 45 let device_picker = cx.new(|cx| { 45 - cx.observe_global::<DevicesState>(|_, cx| cx.notify()).detach(); 46 + cx.observe_global::<DevicesState>(|_, cx| cx.notify()) 47 + .detach(); 46 48 DevicePicker 47 49 }); 48 50 let bluetooth_picker = cx.new(|cx| { 49 - cx.observe_global::<BluetoothState>(|_, cx| cx.notify()).detach(); 51 + cx.observe_global::<BluetoothState>(|_, cx| cx.notify()) 52 + .detach(); 50 53 BluetoothPicker 51 54 }); 52 55 fetch_and_update_devices(cx);