Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(layout): extend layout functionality #576

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 28 additions & 14 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.**
Expand All @@ -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` |
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion examples/app.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
47 changes: 39 additions & 8 deletions spotify_player/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u16>,
pub album_percent: Option<u16>,
pub artist_percent: Option<u16>,
}

#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
Expand Down Expand Up @@ -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,
Expand All @@ -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(())
}
Expand All @@ -361,8 +383,17 @@ impl LayoutConfig {
impl AppConfig {
pub fn new(path: &Path) -> Result<Self> {
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()?;
Expand Down
180 changes: 109 additions & 71 deletions spotify_player/src/ui/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand All @@ -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<Constraint> = 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::<Vec<_>>();
};
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(
Expand Down Expand Up @@ -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(
Expand Down