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

Update layout of library and search pages for vertical aspect ratios #559

Merged
merged 4 commits into from
Oct 14, 2024
Merged
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
7 changes: 6 additions & 1 deletion spotify_player/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
config,
key::{Key, KeySequence},
state::*,
ui::single_line_input::LineInput,
ui::{single_line_input::LineInput, Orientation},
utils::parse_uri,
};

Expand All @@ -29,6 +29,11 @@ pub fn start_event_handler(state: SharedState, client_pub: flume::Sender<ClientR
let _enter = tracing::info_span!("terminal_event", event = ?event).entered();
if let Err(err) = match event {
crossterm::event::Event::Mouse(event) => handle_mouse_event(event, &client_pub, &state),
crossterm::event::Event::Resize(columns, rows) => {
let mut state = state.ui.lock();
state.orientation = Orientation::from_size(columns, rows);
Ok(())
}
crossterm::event::Event::Key(event) => {
if event.kind == crossterm::event::KeyEventKind::Press {
// only handle key press event to avoid handling a key event multiple times
Expand Down
10 changes: 9 additions & 1 deletion spotify_player/src/state/ui/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{config, key};
use crate::{config, key, ui};

pub type UIStateGuard<'a> = parking_lot::MutexGuard<'a, UIState>;

Expand All @@ -25,6 +25,7 @@ pub struct UIState {
pub is_running: bool,
pub theme: config::Theme,
pub input_key_sequence: key::KeySequence,
pub orientation: ui::Orientation,

pub history: Vec<PageState>,
pub popup: Option<PopupState>,
Expand Down Expand Up @@ -134,6 +135,13 @@ impl Default for UIState {
is_running: true,
theme: Default::default(),
input_key_sequence: key::KeySequence { keys: vec![] },
orientation: match crossterm::terminal::size() {
Ok((columns, rows)) => ui::Orientation::from_size(columns, rows),
Err(err) => {
tracing::warn!("Unable to get terminal size, error: {err:#}");
Default::default()
}
},

history: vec![PageState::Library {
state: LibraryPageUIState::new(),
Expand Down
32 changes: 32 additions & 0 deletions spotify_player/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,35 @@ fn render_main_layout(
PageType::CommandHelp => page::render_commands_help_page(frame, ui, rect),
}
}

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Orientation {
Vertical,
#[default]
Horizontal,
}

impl Orientation {
/// Construct screen orientation based on the terminal's size
pub fn from_size(columns: u16, rows: u16) -> Self {
let ratio = columns as f64 / rows as f64;

// a larger ratio has to be used since terminal cells aren't square
if ratio > 2.3 {
Self::Horizontal
} else {
Self::Vertical
}
}

pub fn layout<I>(&self, constraints: I) -> Layout
where
I: IntoIterator,
I::Item: Into<Constraint>,
{
match self {
Self::Vertical => Layout::vertical(constraints),
Self::Horizontal => Layout::horizontal(constraints),
}
}
}
79 changes: 55 additions & 24 deletions spotify_player/src/ui/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,40 @@ pub fn render_search_page(
let search_input_rect = chunks[0];
let rect = chunks[1];

// track/album/artist/playlist search results layout (2x2 table)
let chunks = Layout::vertical([Constraint::Ratio(1, 2); 2])
.split(rect)
.iter()
.flat_map(|rect| {
Layout::horizontal([Constraint::Ratio(1, 2); 2])
.split(*rect)
.to_vec()
})
.collect::<Vec<_>>();
// track/album/artist/playlist search results layout
let chunks = match ui.orientation {
Orientation::Vertical => {
let constraints = match focus_state {
SearchFocusState::Input => [Constraint::Ratio(1, 4); 4],
_ => {
let mut constraints = [Constraint::Percentage(20); 4];
constraints[focus_state as usize - 1] = Constraint::Percentage(40);
constraints
}
};

Layout::vertical(constraints).split(rect)
}
// 2x2 table
Orientation::Horizontal => Layout::vertical([Constraint::Ratio(1, 2); 2])
.split(rect)
.iter()
.flat_map(|rect| {
Layout::horizontal([Constraint::Ratio(1, 2); 2])
.split(*rect)
.to_vec()
})
.collect(),
};

let track_rect = construct_and_render_block(
"Tracks",
&ui.theme,
Borders::TOP | Borders::RIGHT,
if ui.orientation == Orientation::Horizontal {
Borders::TOP | Borders::RIGHT
} else {
Borders::TOP
},
frame,
chunks[0],
);
Expand All @@ -69,7 +88,11 @@ pub fn render_search_page(
let artist_rect = construct_and_render_block(
"Artists",
&ui.theme,
Borders::TOP | Borders::RIGHT,
if ui.orientation == Orientation::Horizontal {
Borders::TOP | Borders::RIGHT
} else {
Borders::TOP
},
frame,
chunks[2],
);
Expand Down Expand Up @@ -323,32 +346,40 @@ pub fn render_library_page(
};

// 2. Construct the page's layout
// Horizontally split the library page into 3 windows:
// Split the library page into 3 windows:
// - a playlists window
// - a saved albums window
// - a followed artists window

let chunks = Layout::horizontal([
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);
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);

let playlist_rect = construct_and_render_block(
"Playlists",
&ui.theme,
Borders::TOP | Borders::LEFT | Borders::BOTTOM,
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,
Borders::TOP | Borders::LEFT | Borders::BOTTOM,
match ui.orientation {
Orientation::Horizontal => Borders::TOP | Borders::LEFT | Borders::BOTTOM,
Orientation::Vertical => Borders::ALL,
},
frame,
chunks[1],
);
Expand Down
Loading