From ed4e4c2b2a201a09890666fa55d68d27fe891b1e Mon Sep 17 00:00:00 2001 From: NickWilder Date: Fri, 11 Oct 2024 22:21:00 -0500 Subject: [PATCH 1/2] Extend layout functionality --- spotify_player/src/config/mod.rs | 47 ++++++-- spotify_player/src/ui/page.rs | 180 +++++++++++++++++++------------ 2 files changed, 148 insertions(+), 79 deletions(-) diff --git a/spotify_player/src/config/mod.rs b/spotify_player/src/config/mod.rs index 397d596b..cff31cad 100644 --- a/spotify_player/src/config/mod.rs +++ b/spotify_player/src/config/mod.rs @@ -191,8 +191,9 @@ pub struct LayoutConfig { #[derive(Debug, Deserialize, Serialize, ConfigParse, Clone)] pub struct LibraryLayoutConfig { - pub playlist_percent: u16, - pub album_percent: u16, + pub playlist_percent: Option, + pub album_percent: Option, + pub artist_percent: Option, } #[derive(Debug, Deserialize, Serialize, Clone, PartialEq)] @@ -339,8 +340,9 @@ impl Default for LayoutConfig { fn default() -> Self { Self { library: LibraryLayoutConfig { - playlist_percent: 40, - album_percent: 40, + playlist_percent: Some(40), + album_percent: Some(40), + artist_percent: Some(20), }, playback_window_position: Position::Top, playback_window_height: 6, @@ -350,8 +352,28 @@ impl Default for LayoutConfig { impl LayoutConfig { fn check_values(&self) -> anyhow::Result<()> { - if self.library.album_percent + self.library.playlist_percent > 99 { - anyhow::bail!("Invalid library layout: summation of album_percent and playlist_percent cannot be greater than 99!"); + let mut total_percent = 0; + let mut set_columns = 0; + + if let Some(percent) = self.library.playlist_percent { + total_percent += percent; + set_columns += 1; + } + if let Some(percent) = self.library.album_percent { + total_percent += percent; + set_columns += 1; + } + if let Some(percent) = self.library.artist_percent { + total_percent += percent; + set_columns += 1; + } + + if total_percent > 100 { + anyhow::bail!( + "Invalid library layout: summation of set percentages cannot be greater than 100!" + ); + } else if set_columns == 0 { + anyhow::bail!("Invalid library layout: at least one of album_percent, playlist_percent, or artist_percentage must be set!"); } else { Ok(()) } @@ -361,8 +383,17 @@ impl LayoutConfig { impl AppConfig { pub fn new(path: &Path) -> Result { let mut config = Self::default(); - if !config.parse_config_file(path)? { - config.write_config_file(path)? + if config.parse_config_file(path)? { + // If a config file was found and parsed, reset the library percentages + // to ensure only the user-specified values are set + config.layout.library.playlist_percent = None; + config.layout.library.album_percent = None; + config.layout.library.artist_percent = None; + + // Re-parse the config file to set only the user-specified values + config.parse_config_file(path)?; + } else { + config.write_config_file(path)?; } config.layout.check_values()?; diff --git a/spotify_player/src/ui/page.rs b/spotify_player/src/ui/page.rs index 33bfa785..4b0e953d 100644 --- a/spotify_player/src/ui/page.rs +++ b/spotify_player/src/ui/page.rs @@ -340,8 +340,8 @@ pub fn render_library_page( let data = state.data.read(); let configs = config::get_config(); - let (focus_state, playlist_folder_id) = match ui.current_page() { - PageState::Library { state } => (state.focus, state.playlist_folder_id), + let focus_state = match ui.current_page() { + PageState::Library { state } => state.focus, _ => return, }; @@ -351,60 +351,90 @@ pub fn render_library_page( // - a saved albums window // - a followed artists window - let chunks = ui - .orientation - .layout([ - Constraint::Percentage(configs.app_config.layout.library.playlist_percent), - Constraint::Percentage(configs.app_config.layout.library.album_percent), - Constraint::Percentage( - 100 - (configs.app_config.layout.library.album_percent - + configs.app_config.layout.library.playlist_percent), - ), - ]) - .split(rect); + // Calculate constraints based on visible columns + let mut constraints: Vec = Vec::new(); + if let Some(percent) = configs.app_config.layout.library.playlist_percent { + constraints.push(Constraint::Percentage(percent)); + } + if let Some(percent) = configs.app_config.layout.library.album_percent { + constraints.push(Constraint::Percentage(percent)); + } + if let Some(percent) = configs.app_config.layout.library.artist_percent { + constraints.push(Constraint::Percentage(percent)); + } - let playlist_rect = construct_and_render_block( - "Playlists", - &ui.theme, - match ui.orientation { - Orientation::Horizontal => Borders::TOP | Borders::LEFT | Borders::BOTTOM, - Orientation::Vertical => Borders::ALL, - }, - frame, - chunks[0], - ); - let album_rect = construct_and_render_block( - "Albums", - &ui.theme, - match ui.orientation { - Orientation::Horizontal => Borders::TOP | Borders::LEFT | Borders::BOTTOM, - Orientation::Vertical => Borders::ALL, - }, - frame, - chunks[1], - ); - let artist_rect = - construct_and_render_block("Artists", &ui.theme, Borders::ALL, frame, chunks[2]); + let total_sections = constraints.len(); + let chunks = Layout::horizontal(&constraints).split(rect); - // 3. Construct the page's widgets - // Construct the playlist window - let items = ui - .search_filtered_items(&data.user_data.folder_playlists_items(playlist_folder_id)) - .into_iter() - .map(|item| match item { - PlaylistFolderItem::Playlist(p) => { - (p.to_string(), curr_context_uri == Some(p.id.uri())) + let mut _chunk_index = 0; + + let mut playlist_rect = None; + let mut album_rect = None; + let mut artist_rect = None; + + if configs.app_config.layout.library.playlist_percent.is_some() { + let borders = if _chunk_index == total_sections - 1 { + Borders::ALL + } else { + match ui.orientation { + Orientation::Horizontal => Borders::TOP | Borders::LEFT | Borders::BOTTOM, + Orientation::Vertical => Borders::ALL, } - PlaylistFolderItem::Folder(f) => (f.to_string(), false), - }) - .collect::>(); + }; + playlist_rect = Some(construct_and_render_block( + "Playlists", + &ui.theme, + borders, + frame, + chunks[_chunk_index], + )); + _chunk_index += 1; + } + + if configs.app_config.layout.library.album_percent.is_some() { + let borders = if _chunk_index == total_sections - 1 { + Borders::ALL + } else { + match ui.orientation { + Orientation::Horizontal => Borders::TOP | Borders::LEFT | Borders::BOTTOM, + Orientation::Vertical => Borders::ALL, + } + }; + album_rect = Some(construct_and_render_block( + "Albums", + &ui.theme, + borders, + frame, + chunks[_chunk_index], + )); + _chunk_index += 1; + } + if configs.app_config.layout.library.artist_percent.is_some() { + let borders = Borders::ALL; + artist_rect = Some(construct_and_render_block( + "Artists", + &ui.theme, + borders, + frame, + chunks[_chunk_index], + )); + } + + // 3. Construct the page's widgets + // Construct the playlist window let (playlist_list, n_playlists) = utils::construct_list_widget( &ui.theme, - items, - is_active - && focus_state != LibraryFocusState::SavedAlbums - && focus_state != LibraryFocusState::FollowedArtists, + ui.search_filtered_items(&data.user_data.playlists) + .into_iter() + .map(|item| match item { + PlaylistFolderItem::Playlist(p) => { + (p.to_string(), curr_context_uri == Some(p.id.uri())) + } + PlaylistFolderItem::Folder(f) => (f.to_string(), false), + }) + .collect(), + is_active && focus_state == LibraryFocusState::Playlists, ); // Construct the saved album window let (album_list, n_albums) = utils::construct_list_widget( @@ -433,27 +463,35 @@ pub fn render_library_page( _ => return, }; - utils::render_list_window( - frame, - playlist_list, - playlist_rect, - n_playlists, - &mut page_state.playlist_list, - ); - utils::render_list_window( - frame, - album_list, - album_rect, - n_albums, - &mut page_state.saved_album_list, - ); - utils::render_list_window( - frame, - artist_list, - artist_rect, - n_artists, - &mut page_state.followed_artist_list, - ); + if let Some(rect) = playlist_rect { + utils::render_list_window( + frame, + playlist_list, + rect, + n_playlists, + &mut page_state.playlist_list, + ); + } + + if let Some(rect) = album_rect { + utils::render_list_window( + frame, + album_list, + rect, + n_albums, + &mut page_state.saved_album_list, + ); + } + + if let Some(rect) = artist_rect { + utils::render_list_window( + frame, + artist_list, + rect, + n_artists, + &mut page_state.followed_artist_list, + ); + } } pub fn render_browse_page( From 4aa9d656b9a65797e656d1632af024fb64b98dac Mon Sep 17 00:00:00 2001 From: Nick Wilder Date: Thu, 17 Oct 2024 23:09:10 -0500 Subject: [PATCH 2/2] Extend README and example config file --- docs/config.md | 42 ++++++++++++++++++++++++++++-------------- examples/app.toml | 2 +- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/docs/config.md b/docs/config.md index ba1d82db..896ddbd1 100644 --- a/docs/config.md +++ b/docs/config.md @@ -15,7 +15,7 @@ - [Keymaps](#keymaps) All configuration files should be placed inside the application's configuration folder (default to be `$HOME/.config/spotify-player`). - + ## General **The default `app.toml` can be found in the example [`app.toml`](../examples/app.toml) file.** @@ -25,10 +25,10 @@ All configuration files should be placed inside the application's configuration | Option | Description | Default | | --------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------- | | `client_id` | the Spotify client's ID | `65b708073fc0480ea92a077233ca87bd` | -| `client_id_command` | a shell command that prints the Spotify client ID to stdout (overrides `client_id`) | `None` | +| `client_id_command` | a shell command that prints the Spotify client ID to stdout (overrides `client_id`) | `None` | | `client_port` | the port that the application's client is running on to handle CLI commands | `8080` | | `tracks_playback_limit` | the limit for the number of tracks played in a **tracks** playback | `50` | -| `playback_format` | the format of the text in the playback's window | `{status} {track} • {artists}\n{album}\n{metadata}` | +| `playback_format` | the format of the text in the playback's window | `{status} {track} • {artists}\n{album}\n{metadata}` | | `notify_format` | the format of a notification (`notify` feature only) | `{ summary = "{track} • {artists}", body = "{album}" }` | | `notify_timeout_in_secs` | the timeout (in seconds) of a notification (`notify` feature only) | `0` (no timeout) | | `player_event_hook_command` | the hook command executed when there is a new player event | `None` | @@ -139,24 +139,38 @@ More details on the above configuration options can be found under the [Librespo ### Layout configurations -The layout of the application can be adjusted via these options. +The layout of the application can be adjusted via these options. + +| Option | Description | Default | +| -------------------------- | ---------------------------------------------------- | ------- | +| `library.album_percent` | The percentage of the album window in the library | `40` | +| `library.playlist_percent` | The percentage of the playlist window in the library | `40` | +| `library.artist_percent` | The percentage of the artist window in the library | `20` | +| `playback_window_position` | The position of the playback window | `Top` | +| `playback_window_height` | The height of the playback window | `6` | + +Example: + +```toml + +[layout] +library = { album_percent = 40, playlist_percent = 40, artist_percent = 20 } +playback_window_position = "Top" + +``` -| Option | Description | Default | -| -------------------------- | ---------------------------------------------------------------- | ------- | -| `library.album_percent` | The percentage of the album window in the library | `40` | -| `library.playlist_percent` | The percentage of the playlist window in the library | `40` | -| `playback_window_position` | The position of the playback window | `Top` | -| `playback_window_height` | The height of the playback window | `6` | +If you would like to remove any of columns, omit them from the configuration file. -Example: +Example: -``` toml +```toml [layout] -library = { album_percent = 40, playlist_percent = 40 } +library = { album_percent = 40, playlist_percent = 60 } playback_window_position = "Top" ``` + ## Themes `spotify_player` uses the `theme.toml` config file to look for user-defined themes. @@ -286,7 +300,7 @@ key_sequence = "q" ## Actions -Actions are located in the same `keymap.toml` file as keymaps. An action can be triggered by a key sequence that is not bound to any command. Once the mapped key sequence is pressed, the corresponding action will be triggered. By default actions will act upon the currently selected item, you can change this behaviour by setting the `target` field for a keymap to either `PlayingTrack` or `SelectedItem`. +Actions are located in the same `keymap.toml` file as keymaps. An action can be triggered by a key sequence that is not bound to any command. Once the mapped key sequence is pressed, the corresponding action will be triggered. By default actions will act upon the currently selected item, you can change this behaviour by setting the `target` field for a keymap to either `PlayingTrack` or `SelectedItem`. a list of actions can be found [here](../README.md#actions). For example, diff --git a/examples/app.toml b/examples/app.toml index 7c055ae7..4aade59b 100644 --- a/examples/app.toml +++ b/examples/app.toml @@ -31,6 +31,6 @@ normalization = false autoplay = false [layout] -library = { playlist_percent = 40, album_percent = 40 } +library = { playlist_percent = 40, album_percent = 40, artist_percent = 20 } playback_window_position = "Top" playback_window_height = 6