diff --git a/src/ui/mod.rs b/src/ui/mod.rs index a57904c..1a15aa4 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,7 +1,6 @@ use std::iter; -use std::rc::Rc; -use ratatui::layout::{Alignment, Constraint, Layout, Rect}; +use ratatui::layout::{Alignment, Constraint, Flex, Layout, Rect}; use ratatui::style::{Modifier, Style, Stylize}; use ratatui::text::{Line, Text}; use ratatui::widgets::Paragraph; @@ -17,10 +16,9 @@ use crate::models::{HorizontallyScrollableText, Route, StatefulList, StatefulTab use crate::ui::radarr_ui::RadarrUi; use crate::ui::styles::ManagarrStyle; use crate::ui::utils::{ - background_block, borderless_block, centered_rect, horizontal_chunks, - horizontal_chunks_with_margin, layout_block, layout_block_top_border, layout_button_paragraph, - layout_button_paragraph_borderless, layout_paragraph_borderless, logo_block, show_cursor, - style_block_highlight, title_block, title_block_centered, vertical_chunks_with_margin, + background_block, borderless_block, centered_rect, layout_block, layout_block_top_border, + layout_button_paragraph, layout_button_paragraph_borderless, layout_paragraph_borderless, + logo_block, show_cursor, style_block_highlight, title_block, title_block_centered, }; mod radarr_ui; @@ -31,46 +29,48 @@ static HIGHLIGHT_SYMBOL: &str = "=> "; pub trait DrawUi { fn accepts(route: Route) -> bool; - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect); + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect); fn draw_context_row(_f: &mut Frame<'_>, _app: &App<'_>, _area: Rect) {} } pub fn ui(f: &mut Frame<'_>, app: &mut App<'_>) { f.render_widget(background_block(), f.size()); - let [header, context, table] = if !app.error.text.is_empty() { - let [header, error, context, table] = Layout::vertical([ + let [header_area, context_area, table_area] = if !app.error.text.is_empty() { + let [header_area, error_area, context_area, table_area] = Layout::vertical([ Constraint::Length(3), Constraint::Length(3), Constraint::Length(10), - Constraint::Fill(1), + Constraint::Fill(0), ]) - .margin(1) .areas(f.size()); - draw_error(f, app, error); + draw_error(f, app, error_area); - [header, context, table] + [header_area, context_area, table_area] } else { Layout::vertical([ Constraint::Length(3), Constraint::Length(10), - Constraint::Fill(1), + Constraint::Fill(0), ]) - .margin(1) .areas(f.size()) }; - draw_header_row(f, app, header); + draw_header_row(f, app, header_area); if RadarrUi::accepts(*app.get_current_route()) { - RadarrUi::draw_context_row(f, app, context); - RadarrUi::draw(f, app, table); + RadarrUi::draw_context_row(f, app, context_area); + RadarrUi::draw(f, app, table_area); } } fn draw_header_row(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { - let chunks = - horizontal_chunks_with_margin(vec![Constraint::Length(75), Constraint::Min(0)], area, 1); + f.render_widget(logo_block(), area); + + let [tabs_area, help_area] = Layout::horizontal([Constraint::Min(25), Constraint::Min(25)]) + .flex(Flex::SpaceBetween) + .margin(1) + .areas(area); let help_text = Text::from(app.server_tabs.get_active_tab_help().help()); let titles = app @@ -79,15 +79,15 @@ fn draw_header_row(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { .iter() .map(|tab| Line::from(tab.title.bold())); let tabs = Tabs::new(titles) - .block(logo_block()) + .block(borderless_block()) .highlight_style(Style::new().secondary()) .select(app.server_tabs.index); let help = Paragraph::new(help_text) .block(borderless_block()) .alignment(Alignment::Right); - f.render_widget(tabs, area); - f.render_widget(help, chunks[1]); + f.render_widget(tabs, tabs_area); + f.render_widget(help, help_area); } fn draw_error(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { @@ -244,37 +244,32 @@ pub fn draw_error_popup(f: &mut Frame<'_>, message: &str) { f.render_widget(error_message, prompt_area); } -fn draw_tabs<'a>( - f: &mut Frame<'_>, - area: Rect, - title: &str, - tab_state: &TabState, -) -> (Rect, Block<'a>) { - let chunks = - vertical_chunks_with_margin(vec![Constraint::Length(2), Constraint::Min(0)], area, 1); - let horizontal_chunks = horizontal_chunks_with_margin( - vec![Constraint::Percentage(10), Constraint::Fill(1)], - area, - 1, - ); - let block = title_block(title); +fn draw_tabs(f: &mut Frame<'_>, area: Rect, title: &str, tab_state: &TabState) -> Rect { + f.render_widget(title_block(title), area); + + let [header_area, content_area] = Layout::vertical([Constraint::Length(1), Constraint::Fill(0)]) + .margin(1) + .areas(area); + let [tabs_area, help_area] = Layout::horizontal([Constraint::Min(25), Constraint::Min(25)]) + .flex(Flex::SpaceBetween) + .areas(header_area); let titles = tab_state .tabs .iter() .map(|tab_route| Line::from(tab_route.title.bold())); let tabs = Tabs::new(titles) - .block(block) + .block(borderless_block()) .highlight_style(Style::new().secondary()) .select(tab_state.index); let help = Paragraph::new(Text::from(tab_state.get_active_tab_help().help())) .block(borderless_block()) .alignment(Alignment::Right); - f.render_widget(tabs, area); - f.render_widget(help, horizontal_chunks[1]); + f.render_widget(tabs, tabs_area); + f.render_widget(help, help_area); - (chunks[1], layout_block_top_border()) + content_area } pub struct TableProps<'a, T> { @@ -295,7 +290,7 @@ pub struct ListProps<'a, T> { fn draw_table<'a, T, F>( f: &mut Frame<'_>, - content_area: Rect, + area: Rect, block: Block<'_>, table_props: TableProps<'a, T>, row_mapper: F, @@ -312,7 +307,7 @@ fn draw_table<'a, T, F>( help, } = table_props; - let content_area = draw_help_and_get_content_rect(f, content_area, help); + let content_area = draw_help_footer_and_get_content_area(f, area, help); #[allow(clippy::unnecessary_unwrap)] if wrapped_content.is_some() && wrapped_content.as_ref().unwrap().is_some() { @@ -351,7 +346,7 @@ fn draw_table_contents<'a, T, F>( content: &mut StatefulTable, table_headers: Vec<&str>, constraints: Vec, - content_area: Rect, + area: Rect, ) where F: Fn(&T) -> Row<'a>, { @@ -367,7 +362,7 @@ fn draw_table_contents<'a, T, F>( .highlight_symbol(HIGHLIGHT_SYMBOL); } - f.render_stateful_widget(table, content_area, &mut content.state); + f.render_stateful_widget(table, area, &mut content.state); } pub fn loading(f: &mut Frame<'_>, block: Block<'_>, area: Rect, is_loading: bool) { @@ -383,76 +378,73 @@ pub fn loading(f: &mut Frame<'_>, block: Block<'_>, area: Rect, is_loading: bool pub fn draw_prompt_box( f: &mut Frame<'_>, - prompt_area: Rect, + area: Rect, title: &str, prompt: &str, yes_no_value: bool, ) { - draw_prompt_box_with_content(f, prompt_area, title, prompt, None, yes_no_value); + draw_prompt_box_with_content(f, area, title, prompt, None, yes_no_value); } pub fn draw_prompt_box_with_content( f: &mut Frame<'_>, - prompt_area: Rect, + area: Rect, title: &str, prompt: &str, content: Option>, yes_no_value: bool, ) { - f.render_widget(title_block_centered(title), prompt_area); + f.render_widget(title_block_centered(title), area); - let chunks = if let Some(content_paragraph) = content { - let vertical_chunks = vertical_chunks_with_margin( - vec![ - Constraint::Length(4), - Constraint::Length(7), - Constraint::Min(0), - Constraint::Length(3), - ], - prompt_area, - 1, - ); + let [prompt_area, buttons_area] = if let Some(content_paragraph) = content { + let [prompt_area, content_area, _, buttons_area] = Layout::vertical([ + Constraint::Length(4), + Constraint::Length(7), + Constraint::Fill(0), + Constraint::Length(3), + ]) + .margin(1) + .areas(area); - f.render_widget(content_paragraph, vertical_chunks[1]); + f.render_widget(content_paragraph, content_area); - Rc::new([vertical_chunks[0], vertical_chunks[2], vertical_chunks[3]]) + [prompt_area, buttons_area] } else { - vertical_chunks_with_margin( - vec![ - Constraint::Percentage(72), - Constraint::Min(0), - Constraint::Length(3), - ], - prompt_area, - 1, - ) + let [prompt_area, _, buttons_area] = Layout::vertical([ + Constraint::Percentage(72), + Constraint::Fill(0), + Constraint::Length(3), + ]) + .margin(1) + .areas(area); + + [prompt_area, buttons_area] }; let prompt_paragraph = layout_paragraph_borderless(prompt); - f.render_widget(prompt_paragraph, chunks[0]); + f.render_widget(prompt_paragraph, prompt_area); - let horizontal_chunks = horizontal_chunks( - vec![Constraint::Percentage(50), Constraint::Percentage(50)], - chunks[2], - ); + let [yes_area, no_area] = + Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]) + .areas(buttons_area); - draw_button(f, horizontal_chunks[0], "Yes", yes_no_value); - draw_button(f, horizontal_chunks[1], "No", !yes_no_value); + draw_button(f, yes_area, "Yes", yes_no_value); + draw_button(f, no_area, "No", !yes_no_value); } pub fn draw_prompt_box_with_checkboxes( f: &mut Frame<'_>, - prompt_area: Rect, + area: Rect, title: &str, prompt: &str, checkboxes: Vec<(&str, bool, bool)>, highlight_yes_no: bool, yes_no_value: bool, ) { - f.render_widget(title_block_centered(title), prompt_area); + f.render_widget(title_block_centered(title), area); let mut constraints = vec![ Constraint::Length(4), - Constraint::Min(0), + Constraint::Fill(0), Constraint::Length(3), ]; @@ -461,7 +453,7 @@ pub fn draw_prompt_box_with_checkboxes( iter::repeat(Constraint::Length(3)).take(checkboxes.len()), ); - let chunks = vertical_chunks_with_margin(constraints, prompt_area, 1); + let chunks = Layout::vertical(constraints).margin(1).split(area); let prompt_paragraph = layout_paragraph_borderless(prompt); f.render_widget(prompt_paragraph, chunks[0]); @@ -471,23 +463,12 @@ pub fn draw_prompt_box_with_checkboxes( draw_checkbox_with_label(f, chunks[i + 1], label, is_checked, is_selected); } - let horizontal_chunks = horizontal_chunks( - vec![Constraint::Percentage(50), Constraint::Percentage(50)], - chunks[checkboxes.len() + 2], - ); + let [yes_area, no_area] = + Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]) + .areas(chunks[checkboxes.len() + 2]); - draw_button( - f, - horizontal_chunks[0], - "Yes", - highlight_yes_no && yes_no_value, - ); - draw_button( - f, - horizontal_chunks[1], - "No", - highlight_yes_no && !yes_no_value, - ); + draw_button(f, yes_area, "Yes", highlight_yes_no && yes_no_value); + draw_button(f, no_area, "No", highlight_yes_no && !yes_no_value); } pub fn draw_checkbox(f: &mut Frame<'_>, area: Rect, is_checked: bool, is_selected: bool) { @@ -508,23 +489,17 @@ pub fn draw_checkbox_with_label( is_checked: bool, is_selected: bool, ) { - let horizontal_chunks = horizontal_chunks( - vec![ - Constraint::Percentage(48), - Constraint::Percentage(48), - Constraint::Percentage(4), - ], - area, - ); + let [label_area, checkbox_area] = + Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area); let label_paragraph = Paragraph::new(Text::from(format!("\n{label}: "))) .block(borderless_block()) .alignment(Alignment::Right) .primary(); - f.render_widget(label_paragraph, horizontal_chunks[0]); + f.render_widget(label_paragraph, label_area); - draw_checkbox(f, horizontal_chunks[1], is_checked, is_selected); + draw_checkbox(f, checkbox_area, is_checked, is_selected); } pub fn draw_button(f: &mut Frame<'_>, area: Rect, label: &str, is_selected: bool) { @@ -543,22 +518,18 @@ pub fn draw_button_with_icon( let label_paragraph = layout_button_paragraph_borderless(is_selected, label, Alignment::Left); let icon_paragraph = layout_button_paragraph_borderless(is_selected, icon, Alignment::Right); - let horizontal_chunks = horizontal_chunks_with_margin( - vec![ - Constraint::Percentage(50), - Constraint::Percentage(49), - Constraint::Percentage(1), - ], - area, - 1, - ); + let [label_area, icon_area] = + Layout::horizontal([Constraint::Percentage(25), Constraint::Percentage(25)]) + .flex(Flex::SpaceBetween) + .margin(1) + .areas(area); f.render_widget( layout_block().style(style_block_highlight(is_selected)), area, ); - f.render_widget(label_paragraph, horizontal_chunks[0]); - f.render_widget(icon_paragraph, horizontal_chunks[1]); + f.render_widget(label_paragraph, label_area); + f.render_widget(icon_paragraph, icon_area); } pub fn draw_drop_down_menu_button( @@ -568,23 +539,17 @@ pub fn draw_drop_down_menu_button( selection: &str, is_selected: bool, ) { - let horizontal_chunks = horizontal_chunks( - vec![ - Constraint::Percentage(48), - Constraint::Percentage(48), - Constraint::Percentage(4), - ], - area, - ); + let [label_area, button_area] = + Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area); let description_paragraph = Paragraph::new(Text::from(format!("\n{description}: "))) .block(borderless_block()) .alignment(Alignment::Right) .primary(); - f.render_widget(description_paragraph, horizontal_chunks[0]); + f.render_widget(description_paragraph, label_area); - draw_button_with_icon(f, horizontal_chunks[1], selection, "▼", is_selected); + draw_button_with_icon(f, button_area, selection, "▼ ", is_selected); } pub fn draw_selectable_list<'a, T>( @@ -618,7 +583,7 @@ pub fn draw_list_box<'a, T>( let (content_area, block) = if is_popup { f.render_widget(title_block(title), area); ( - draw_help_and_get_content_rect(f, area, help), + draw_help_footer_and_get_content_area(f, area, help), borderless_block(), ) } else { @@ -639,18 +604,22 @@ pub fn draw_list_box<'a, T>( } } -fn draw_help_and_get_content_rect(f: &mut Frame<'_>, area: Rect, help: Option) -> Rect { +fn draw_help_footer_and_get_content_area( + f: &mut Frame<'_>, + area: Rect, + help: Option, +) -> Rect { if let Some(help_string) = help { - let chunks = - vertical_chunks_with_margin(vec![Constraint::Min(0), Constraint::Length(2)], area, 1); + let [content_area, help_footer_area] = + Layout::vertical([Constraint::Fill(0), Constraint::Length(2)]).areas(area); let help_paragraph = Paragraph::new(Text::from(format!(" {help_string}").help())) .block(layout_block_top_border()) .alignment(Alignment::Left); - f.render_widget(help_paragraph, chunks[1]); + f.render_widget(help_paragraph, help_footer_area); - chunks[0] + content_area } else { area } @@ -721,26 +690,20 @@ pub fn draw_text_box_with_label( should_show_cursor, cursor_after_string, } = labeled_text_box_props; - let horizontal_chunks = horizontal_chunks( - vec![ - Constraint::Percentage(48), - Constraint::Percentage(48), - Constraint::Percentage(4), - ], - area, - ); + let [label_area, text_box_area] = + Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area); let label_paragraph = Paragraph::new(Text::from(format!("\n{label}: "))) .block(borderless_block()) .alignment(Alignment::Right) .primary(); - f.render_widget(label_paragraph, horizontal_chunks[0]); + f.render_widget(label_paragraph, label_area); draw_text_box( f, TextBoxProps { - text_box_area: horizontal_chunks[1], + text_box_area, block_title: None, block_content: text, offset, @@ -753,24 +716,18 @@ pub fn draw_text_box_with_label( pub fn draw_input_box_popup( f: &mut Frame<'_>, - input_box_area: Rect, + area: Rect, box_title: &str, box_content: &HorizontallyScrollableText, ) { - let chunks = vertical_chunks_with_margin( - vec![ - Constraint::Length(3), - Constraint::Length(1), - Constraint::Min(0), - ], - input_box_area, - 1, - ); + let [text_box_area, help_area] = Layout::vertical([Constraint::Length(3), Constraint::Length(1)]) + .margin(1) + .areas(area); draw_text_box( f, TextBoxProps { - text_box_area: chunks[0], + text_box_area, block_title: Some(box_title), block_content: &box_content.text, offset: *box_content.offset.borrow(), @@ -784,14 +741,14 @@ pub fn draw_input_box_popup( .help() .alignment(Alignment::Center) .block(borderless_block()); - f.render_widget(help, chunks[1]); + f.render_widget(help, help_area); } -pub fn draw_error_message_popup(f: &mut Frame<'_>, error_message_area: Rect, error_msg: &str) { +pub fn draw_error_message_popup(f: &mut Frame<'_>, area: Rect, error_msg: &str) { let input = Paragraph::new(error_msg) .failure() .alignment(Alignment::Center) .block(layout_block()); - f.render_widget(input, error_message_area); + f.render_widget(input, area); } diff --git a/src/ui/radarr_ui/collections/collection_details_ui.rs b/src/ui/radarr_ui/collections/collection_details_ui.rs index 46a2f0a..9c8c142 100644 --- a/src/ui/radarr_ui/collections/collection_details_ui.rs +++ b/src/ui/radarr_ui/collections/collection_details_ui.rs @@ -1,4 +1,4 @@ -use ratatui::layout::{Alignment, Constraint, Rect}; +use ratatui::layout::{Alignment, Constraint, Flex, Layout, Rect}; use ratatui::style::Stylize; use ratatui::text::{Line, Text}; use ratatui::widgets::{Cell, Paragraph, Row, Wrap}; @@ -16,7 +16,7 @@ use crate::ui::radarr_ui::collections::draw_collections; use crate::ui::styles::ManagarrStyle; use crate::ui::utils::{ borderless_block, get_width_from_percentage, layout_block_top_border_with_title, title_block, - title_style, vertical_chunks_with_margin, + title_style, }; use crate::ui::{draw_large_popup_over, draw_small_popup_over, draw_table, DrawUi, TableProps}; use crate::utils::convert_runtime; @@ -36,7 +36,7 @@ impl DrawUi for CollectionDetailsUi { false } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { let draw_collection_details_popup = |f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect| match context_option @@ -58,7 +58,7 @@ impl DrawUi for CollectionDetailsUi { draw_large_popup_over( f, app, - content_rect, + area, draw_collections, draw_collection_details_popup, ); @@ -66,16 +66,14 @@ impl DrawUi for CollectionDetailsUi { } } -pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) { - let chunks = vertical_chunks_with_margin( - vec![ - Constraint::Percentage(25), - Constraint::Percentage(70), - Constraint::Percentage(5), - ], - content_area, - 1, - ); +pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { + let [description_area, table_area, help_footer_area] = Layout::vertical([ + Constraint::Percentage(25), + Constraint::Percentage(70), + Constraint::Percentage(5), + ]) + .margin(1) + .areas(area); let collection_selection = if let Some(filtered_collections) = app.data.radarr_data.filtered_collections.as_ref() { filtered_collections.current_selection() @@ -157,14 +155,14 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, content_are .block(borderless_block()) .alignment(Alignment::Center); - f.render_widget(title_block(&collection_selection.title.text), content_area); + f.render_widget(title_block(&collection_selection.title.text), area); - f.render_widget(description_paragraph, chunks[0]); - f.render_widget(help_paragraph, chunks[2]); + f.render_widget(description_paragraph, description_area); + f.render_widget(help_paragraph, help_footer_area); draw_table( f, - chunks[1], + table_area, layout_block_top_border_with_title(title_style("Movies")), TableProps { content: Some(&mut app.data.radarr_data.collection_movies), @@ -203,7 +201,7 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, content_are "" }; movie.title.scroll_left_or_reset( - get_width_from_percentage(chunks[1], 20), + get_width_from_percentage(table_area, 20), current_selection == *movie, app.tick_count % app.ticks_until_scroll == 0, ); @@ -251,15 +249,15 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, content_are ); } -fn draw_movie_overview(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) { +fn draw_movie_overview(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let title_block = title_block("Overview"); - f.render_widget(title_block, content_area); + f.render_widget(title_block, area); - let chunks = vertical_chunks_with_margin( - vec![Constraint::Percentage(95), Constraint::Percentage(5)], - content_area, - 1, - ); + let [paragraph_area, help_area] = + Layout::vertical([Constraint::Percentage(95), Constraint::Length(1)]) + .flex(Flex::SpaceBetween) + .margin(1) + .areas(area); let overview = Text::from( app .data @@ -279,6 +277,6 @@ fn draw_movie_overview(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) .block(borderless_block()) .alignment(Alignment::Center); - f.render_widget(paragraph, chunks[0]); - f.render_widget(help_paragraph, chunks[1]); + f.render_widget(paragraph, paragraph_area); + f.render_widget(help_paragraph, help_area); } diff --git a/src/ui/radarr_ui/collections/edit_collection_ui.rs b/src/ui/radarr_ui/collections/edit_collection_ui.rs index 56adfcb..42b3b47 100644 --- a/src/ui/radarr_ui/collections/edit_collection_ui.rs +++ b/src/ui/radarr_ui/collections/edit_collection_ui.rs @@ -1,4 +1,4 @@ -use ratatui::layout::{Constraint, Rect}; +use ratatui::layout::{Constraint, Layout, Rect}; use ratatui::widgets::ListItem; use ratatui::Frame; @@ -10,9 +10,7 @@ use crate::models::servarr_data::radarr::radarr_data::{ use crate::models::Route; use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi; use crate::ui::radarr_ui::collections::draw_collections; -use crate::ui::utils::{ - horizontal_chunks, layout_paragraph_borderless, title_block_centered, vertical_chunks_with_margin, -}; +use crate::ui::utils::{layout_paragraph_borderless, title_block_centered}; use crate::ui::{ draw_button, draw_checkbox_with_label, draw_drop_down_menu_button, draw_drop_down_popup, draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over, draw_popup, @@ -34,7 +32,7 @@ impl DrawUi for EditCollectionUi { false } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { let draw_edit_collection_prompt = |f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect| match active_radarr_block { @@ -67,18 +65,14 @@ impl DrawUi for EditCollectionUi { if let Some(context) = context_option { match context { - ActiveRadarrBlock::Collections => draw_medium_popup_over( - f, - app, - content_rect, - draw_collections, - draw_edit_collection_prompt, - ), + ActiveRadarrBlock::Collections => { + draw_medium_popup_over(f, app, area, draw_collections, draw_edit_collection_prompt) + } _ if COLLECTION_DETAILS_BLOCKS.contains(&context) => { draw_large_popup_over_background_fn_with_ui::( f, app, - content_rect, + area, draw_collections, ); draw_popup(f, app, draw_edit_collection_prompt, 60, 60); @@ -90,11 +84,7 @@ impl DrawUi for EditCollectionUi { } } -fn draw_edit_collection_confirmation_prompt( - f: &mut Frame<'_>, - app: &mut App<'_>, - prompt_area: Rect, -) { +fn draw_edit_collection_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let (collection_title, collection_overview) = if let Some(filtered_collections) = app.data.radarr_data.filtered_collections.as_ref() { ( @@ -140,34 +130,32 @@ fn draw_edit_collection_confirmation_prompt( let selected_minimum_availability = minimum_availability_list.current_selection(); let selected_quality_profile = quality_profile_list.current_selection(); - f.render_widget(title_block_centered(&title), prompt_area); + f.render_widget(title_block_centered(&title), area); - let chunks = vertical_chunks_with_margin( - vec![ + let [paragraph_area, monitored_area, min_availability_area, quality_profile_area, root_folder_area, search_on_add_area, _, buttons_area] = + Layout::vertical([ Constraint::Length(6), Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), - Constraint::Min(0), + Constraint::Fill(0), Constraint::Length(3), - ], - prompt_area, - 1, - ); + ]) + .margin(1) + .areas(area); let prompt_paragraph = layout_paragraph_borderless(&collection_overview); - f.render_widget(prompt_paragraph, chunks[0]); + f.render_widget(prompt_paragraph, paragraph_area); - let horizontal_chunks = horizontal_chunks( - vec![Constraint::Percentage(50), Constraint::Percentage(50)], - chunks[7], - ); + let [save_area, cancel_area] = + Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]) + .areas(buttons_area); draw_checkbox_with_label( f, - chunks[1], + monitored_area, "Monitored", monitored.unwrap_or_default(), selected_block == &ActiveRadarrBlock::EditCollectionToggleMonitored, @@ -175,14 +163,14 @@ fn draw_edit_collection_confirmation_prompt( draw_drop_down_menu_button( f, - chunks[2], + min_availability_area, "Minimum Availability", selected_minimum_availability.to_display_str(), selected_block == &ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, ); draw_drop_down_menu_button( f, - chunks[3], + quality_profile_area, "Quality Profile", selected_quality_profile, selected_block == &ActiveRadarrBlock::EditCollectionSelectQualityProfile, @@ -192,7 +180,7 @@ fn draw_edit_collection_confirmation_prompt( draw_text_box_with_label( f, LabeledTextBoxProps { - area: chunks[4], + area: root_folder_area, label: "Root Folder", text: &path.text, offset: *path.offset.borrow(), @@ -206,34 +194,24 @@ fn draw_edit_collection_confirmation_prompt( draw_checkbox_with_label( f, - chunks[5], + search_on_add_area, "Search on Add", search_on_add.unwrap_or_default(), selected_block == &ActiveRadarrBlock::EditCollectionToggleSearchOnAdd, ); - draw_button( - f, - horizontal_chunks[0], - "Save", - yes_no_value && highlight_yes_no, - ); - draw_button( - f, - horizontal_chunks[1], - "Cancel", - !yes_no_value && highlight_yes_no, - ); + draw_button(f, save_area, "Save", yes_no_value && highlight_yes_no); + draw_button(f, cancel_area, "Cancel", !yes_no_value && highlight_yes_no); } fn draw_edit_collection_select_minimum_availability_popup( f: &mut Frame<'_>, app: &mut App<'_>, - popup_area: Rect, + area: Rect, ) { draw_selectable_list( f, - popup_area, + area, &mut app .data .radarr_data @@ -248,11 +226,11 @@ fn draw_edit_collection_select_minimum_availability_popup( fn draw_edit_collection_select_quality_profile_popup( f: &mut Frame<'_>, app: &mut App<'_>, - popup_area: Rect, + area: Rect, ) { draw_selectable_list( f, - popup_area, + area, &mut app .data .radarr_data diff --git a/src/ui/radarr_ui/collections/mod.rs b/src/ui/radarr_ui/collections/mod.rs index 875197c..1a1f92a 100644 --- a/src/ui/radarr_ui/collections/mod.rs +++ b/src/ui/radarr_ui/collections/mod.rs @@ -10,12 +10,12 @@ use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, COLLEC use crate::models::Route; use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi; use crate::ui::radarr_ui::collections::edit_collection_ui::EditCollectionUi; +use crate::ui::styles::ManagarrStyle; use crate::ui::utils::{get_width_from_percentage, layout_block_top_border}; use crate::ui::{ draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps, }; -use crate::ui::styles::ManagarrStyle; mod collection_details_ui; #[cfg(test)] @@ -36,14 +36,14 @@ impl DrawUi for CollectionsUi { false } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let route = *app.get_current_route(); let mut collections_ui_matcher = |active_radarr_block| match active_radarr_block { - ActiveRadarrBlock::Collections => draw_collections(f, app, content_rect), + ActiveRadarrBlock::Collections => draw_collections(f, app, area), ActiveRadarrBlock::SearchCollection => draw_popup_over( f, app, - content_rect, + area, draw_collections, draw_collection_search_box, 30, @@ -52,7 +52,7 @@ impl DrawUi for CollectionsUi { ActiveRadarrBlock::SearchCollectionError => draw_popup_over( f, app, - content_rect, + area, draw_collections, draw_search_collection_error_box, 30, @@ -61,7 +61,7 @@ impl DrawUi for CollectionsUi { ActiveRadarrBlock::FilterCollections => draw_popup_over( f, app, - content_rect, + area, draw_collections, draw_filter_collections_box, 30, @@ -70,7 +70,7 @@ impl DrawUi for CollectionsUi { ActiveRadarrBlock::FilterCollectionsError => draw_popup_over( f, app, - content_rect, + area, draw_collections, draw_filter_collections_error_box, 30, @@ -79,7 +79,7 @@ impl DrawUi for CollectionsUi { ActiveRadarrBlock::UpdateAllCollectionsPrompt => draw_prompt_popup_over( f, app, - content_rect, + area, draw_collections, draw_update_all_collections_prompt, ), @@ -87,8 +87,8 @@ impl DrawUi for CollectionsUi { }; match route { - _ if CollectionDetailsUi::accepts(route) => CollectionDetailsUi::draw(f, app, content_rect), - _ if EditCollectionUi::accepts(route) => EditCollectionUi::draw(f, app, content_rect), + _ if CollectionDetailsUi::accepts(route) => CollectionDetailsUi::draw(f, app, area), + _ if EditCollectionUi::accepts(route) => EditCollectionUi::draw(f, app, area), Route::Radarr(active_radarr_block, _) if COLLECTIONS_BLOCKS.contains(&active_radarr_block) => { @@ -168,17 +168,18 @@ pub(super) fn draw_collections(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) ), Cell::from(search_on_add), Cell::from(monitored), - ]).primary() + ]) + .primary() }, app.is_loading, true, ); } -fn draw_update_all_collections_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_update_all_collections_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_prompt_box( f, - prompt_area, + area, "Update All Collections", "Do you want to update all of your collections?", app.data.radarr_data.prompt_confirm, diff --git a/src/ui/radarr_ui/downloads/mod.rs b/src/ui/radarr_ui/downloads/mod.rs index a298af1..1a48e7e 100644 --- a/src/ui/radarr_ui/downloads/mod.rs +++ b/src/ui/radarr_ui/downloads/mod.rs @@ -6,9 +6,9 @@ use crate::app::App; use crate::models::radarr_models::DownloadRecord; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS}; use crate::models::{HorizontallyScrollableText, Route}; +use crate::ui::styles::ManagarrStyle; use crate::ui::utils::{get_width_from_percentage, layout_block_top_border}; use crate::ui::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps}; -use crate::ui::styles::ManagarrStyle; use crate::utils::convert_to_gb; #[cfg(test)] @@ -26,24 +26,16 @@ impl DrawUi for DownloadsUi { false } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { match active_radarr_block { - ActiveRadarrBlock::Downloads => draw_downloads(f, app, content_rect), - ActiveRadarrBlock::DeleteDownloadPrompt => draw_prompt_popup_over( - f, - app, - content_rect, - draw_downloads, - draw_delete_download_prompt, - ), - ActiveRadarrBlock::UpdateDownloadsPrompt => draw_prompt_popup_over( - f, - app, - content_rect, - draw_downloads, - draw_update_downloads_prompt, - ), + ActiveRadarrBlock::Downloads => draw_downloads(f, app, area), + ActiveRadarrBlock::DeleteDownloadPrompt => { + draw_prompt_popup_over(f, app, area, draw_downloads, draw_delete_download_prompt) + } + ActiveRadarrBlock::UpdateDownloadsPrompt => { + draw_prompt_popup_over(f, app, area, draw_downloads, draw_update_downloads_prompt) + } _ => (), } } @@ -120,17 +112,18 @@ fn draw_downloads(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { ), Cell::from(indexer.to_owned()), Cell::from(download_client.to_owned()), - ]).primary() + ]) + .primary() }, app.is_loading, true, ); } -fn draw_delete_download_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_delete_download_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_prompt_box( f, - prompt_area, + area, "Cancel Download", format!( "Do you really want to delete this download: \n{}?", @@ -141,10 +134,10 @@ fn draw_delete_download_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area ); } -fn draw_update_downloads_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_update_downloads_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_prompt_box( f, - prompt_area, + area, "Update Downloads", "Do you want to update your downloads?", app.data.radarr_data.prompt_confirm, diff --git a/src/ui/radarr_ui/indexers/edit_indexer_ui.rs b/src/ui/radarr_ui/indexers/edit_indexer_ui.rs index 81a4c91..3e4b023 100644 --- a/src/ui/radarr_ui/indexers/edit_indexer_ui.rs +++ b/src/ui/radarr_ui/indexers/edit_indexer_ui.rs @@ -2,17 +2,13 @@ use crate::app::App; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS}; use crate::models::Route; use crate::ui::radarr_ui::indexers::draw_indexers; -use crate::ui::utils::{ - horizontal_chunks, horizontal_chunks_with_margin, title_block_centered, vertical_chunks, - vertical_chunks_with_margin, -}; +use crate::ui::utils::title_block_centered; use crate::ui::{ draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading, DrawUi, LabeledTextBoxProps, }; -use ratatui::layout::{Constraint, Layout, Rect}; +use ratatui::layout::{Constraint, Flex, Layout, Rect}; use ratatui::Frame; -use std::iter; #[cfg(test)] #[path = "edit_indexer_ui_tests.rs"] @@ -29,11 +25,11 @@ impl DrawUi for EditIndexerUi { false } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_popup_over( f, app, - content_rect, + area, draw_indexers, draw_edit_indexer_prompt, 70, @@ -42,7 +38,7 @@ impl DrawUi for EditIndexerUi { } } -fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let block = title_block_centered("Edit Indexer"); let yes_no_value = app.data.radarr_data.prompt_confirm; let selected_block = app.data.radarr_data.selected_block.get_active_block(); @@ -52,38 +48,34 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R if edit_indexer_modal_option.is_some() { let edit_indexer_modal = edit_indexer_modal_option.as_ref().unwrap(); - f.render_widget(block, prompt_area); + f.render_widget(block, area); - let chunks = vertical_chunks_with_margin( - vec![Constraint::Min(0), Constraint::Length(3)], - prompt_area, - 1, - ); + let [settings_area, buttons_area] = + Layout::vertical([Constraint::Fill(0), Constraint::Length(3)]) + .margin(1) + .areas(area); - let split_chunks = horizontal_chunks_with_margin( - vec![Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)], - chunks[0], - 1, - ); + let [left_side_area, right_side_area] = + Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]) + .margin(1) + .areas(settings_area); let [name, rss, auto_search, interactive_search, _] = Layout::vertical([ Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), - Constraint::Min(0), + Constraint::Fill(0), ]) - .areas(split_chunks[0]); - let right_chunks = vertical_chunks( - vec![ - Constraint::Length(3), - Constraint::Length(3), - Constraint::Length(3), - Constraint::Length(3), - Constraint::Min(0), - ], - split_chunks[1], - ); + .areas(left_side_area); + let [url_area, api_key_area, seed_ratio_area, tags_area, _] = Layout::vertical([ + Constraint::Length(3), + Constraint::Length(3), + Constraint::Length(3), + Constraint::Length(3), + Constraint::Fill(0), + ]) + .areas(right_side_area); if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { draw_text_box_with_label( @@ -101,7 +93,7 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R draw_text_box_with_label( f, LabeledTextBoxProps { - area: right_chunks[0], + area: url_area, label: "URL", text: &edit_indexer_modal.url.text, offset: *edit_indexer_modal.url.offset.borrow(), @@ -113,7 +105,7 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R draw_text_box_with_label( f, LabeledTextBoxProps { - area: right_chunks[1], + area: api_key_area, label: "API Key", text: &edit_indexer_modal.api_key.text, offset: *edit_indexer_modal.api_key.offset.borrow(), @@ -126,7 +118,7 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R draw_text_box_with_label( f, LabeledTextBoxProps { - area: right_chunks[2], + area: seed_ratio_area, label: "Seed Ratio", text: &edit_indexer_modal.seed_ratio.text, offset: *edit_indexer_modal.seed_ratio.offset.borrow(), @@ -138,7 +130,7 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R draw_text_box_with_label( f, LabeledTextBoxProps { - area: right_chunks[3], + area: tags_area, label: "Tags", text: &edit_indexer_modal.tags.text, offset: *edit_indexer_modal.tags.offset.borrow(), @@ -151,7 +143,7 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R draw_text_box_with_label( f, LabeledTextBoxProps { - area: right_chunks[2], + area: seed_ratio_area, label: "Tags", text: &edit_indexer_modal.tags.text, offset: *edit_indexer_modal.tags.offset.borrow(), @@ -188,25 +180,15 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch, ); - let button_chunks = horizontal_chunks( - iter::repeat(Constraint::Ratio(1, 4)).take(4).collect(), - chunks[1], - ); + let [save_area, cancel_area] = + Layout::horizontal([Constraint::Percentage(25), Constraint::Percentage(25)]) + .flex(Flex::Center) + .areas(buttons_area); - draw_button( - f, - button_chunks[1], - "Save", - yes_no_value && highlight_yes_no, - ); - draw_button( - f, - button_chunks[2], - "Cancel", - !yes_no_value && highlight_yes_no, - ); + draw_button(f, save_area, "Save", yes_no_value && highlight_yes_no); + draw_button(f, cancel_area, "Cancel", !yes_no_value && highlight_yes_no); } } else { - loading(f, block, prompt_area, app.is_loading); + loading(f, block, area, app.is_loading); } } diff --git a/src/ui/radarr_ui/indexers/indexer_settings_ui.rs b/src/ui/radarr_ui/indexers/indexer_settings_ui.rs index 015a069..21009a4 100644 --- a/src/ui/radarr_ui/indexers/indexer_settings_ui.rs +++ b/src/ui/radarr_ui/indexers/indexer_settings_ui.rs @@ -1,6 +1,5 @@ -use ratatui::layout::{Constraint, Rect}; +use ratatui::layout::{Constraint, Flex, Layout, Rect}; use ratatui::Frame; -use std::iter; use crate::app::App; use crate::models::servarr_data::radarr::radarr_data::{ @@ -8,10 +7,7 @@ use crate::models::servarr_data::radarr::radarr_data::{ }; use crate::models::Route; use crate::ui::radarr_ui::indexers::draw_indexers; -use crate::ui::utils::{ - horizontal_chunks, horizontal_chunks_with_margin, title_block_centered, vertical_chunks, - vertical_chunks_with_margin, -}; +use crate::ui::utils::title_block_centered; use crate::ui::{ draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading, DrawUi, LabeledTextBoxProps, @@ -32,11 +28,11 @@ impl DrawUi for IndexerSettingsUi { false } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_popup_over( f, app, - content_rect, + area, draw_indexers, draw_edit_indexer_settings_prompt, 70, @@ -45,7 +41,7 @@ impl DrawUi for IndexerSettingsUi { } } -fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let block = title_block_centered("Configure All Indexer Settings"); let yes_no_value = app.data.radarr_data.prompt_confirm; let selected_block = app.data.radarr_data.selected_block.get_active_block(); @@ -54,46 +50,41 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp if indexer_settings_option.is_some() { let indexer_settings = indexer_settings_option.as_ref().unwrap(); - f.render_widget(block, prompt_area); + f.render_widget(block, area); - let chunks = vertical_chunks_with_margin( - vec![Constraint::Min(0), Constraint::Length(3)], - prompt_area, - 1, - ); + let [settings_area, buttons_area] = + Layout::vertical([Constraint::Fill(0), Constraint::Length(3)]) + .margin(1) + .areas(area); - let split_chunks = horizontal_chunks_with_margin( - vec![Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)], - chunks[0], - 1, - ); + let [left_side_area, right_side_area] = + Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]) + .margin(1) + .areas(settings_area); - let left_chunks = vertical_chunks( - vec![ + let [min_age_area, retention_area, max_size_area, prefer_flags_area, _] = Layout::vertical([ + Constraint::Length(3), + Constraint::Length(3), + Constraint::Length(3), + Constraint::Length(3), + Constraint::Fill(0), + ]) + .areas(left_side_area); + let [availability_delay_area, rss_sync_interval_area, whitelisted_sub_tags_area, allow_hardcoded_subs_area, _] = + Layout::vertical([ Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), - Constraint::Min(0), - ], - split_chunks[0], - ); - let right_chunks = vertical_chunks( - vec![ - Constraint::Length(3), - Constraint::Length(3), - Constraint::Length(3), - Constraint::Length(3), - Constraint::Min(0), - ], - split_chunks[1], - ); + Constraint::Fill(0), + ]) + .areas(right_side_area); if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { draw_text_box_with_label( f, LabeledTextBoxProps { - area: left_chunks[0], + area: min_age_area, label: "Minimum Age (minutes) ▴▾", text: &indexer_settings.minimum_age.to_string(), offset: 0, @@ -106,7 +97,7 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp draw_text_box_with_label( f, LabeledTextBoxProps { - area: left_chunks[1], + area: retention_area, label: "Retention (days) ▴▾", text: &indexer_settings.retention.to_string(), offset: 0, @@ -119,7 +110,7 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp draw_text_box_with_label( f, LabeledTextBoxProps { - area: left_chunks[2], + area: max_size_area, label: "Maximum Size (MB) ▴▾", text: &indexer_settings.maximum_size.to_string(), offset: 0, @@ -132,7 +123,7 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp draw_text_box_with_label( f, LabeledTextBoxProps { - area: right_chunks[0], + area: availability_delay_area, label: "Availability Delay (days) ▴▾", text: &indexer_settings.availability_delay.to_string(), offset: 0, @@ -145,7 +136,7 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp draw_text_box_with_label( f, LabeledTextBoxProps { - area: right_chunks[1], + area: rss_sync_interval_area, label: "RSS Sync Interval (minutes) ▴▾", text: &indexer_settings.rss_sync_interval.to_string(), offset: 0, @@ -158,7 +149,7 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp draw_text_box_with_label( f, LabeledTextBoxProps { - area: right_chunks[2], + area: whitelisted_sub_tags_area, label: "Whitelisted Subtitle Tags", text: &indexer_settings.whitelisted_hardcoded_subs.text, offset: *indexer_settings.whitelisted_hardcoded_subs.offset.borrow(), @@ -173,7 +164,7 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp draw_checkbox_with_label( f, - left_chunks[3], + prefer_flags_area, "Prefer Indexer Flags", indexer_settings.prefer_indexer_flags, selected_block == &ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags, @@ -181,30 +172,20 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp draw_checkbox_with_label( f, - right_chunks[3], + allow_hardcoded_subs_area, "Allow Hardcoded Subs", indexer_settings.allow_hardcoded_subs, selected_block == &ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs, ); - let button_chunks = horizontal_chunks( - iter::repeat(Constraint::Ratio(1, 4)).take(4).collect(), - chunks[1], - ); + let [save_area, cancel_area] = + Layout::horizontal([Constraint::Percentage(25), Constraint::Percentage(25)]) + .flex(Flex::Center) + .areas(buttons_area); - draw_button( - f, - button_chunks[1], - "Save", - yes_no_value && highlight_yes_no, - ); - draw_button( - f, - button_chunks[2], - "Cancel", - !yes_no_value && highlight_yes_no, - ); + draw_button(f, save_area, "Save", yes_no_value && highlight_yes_no); + draw_button(f, cancel_area, "Cancel", !yes_no_value && highlight_yes_no); } else { - loading(f, block, prompt_area, app.is_loading); + loading(f, block, area, app.is_loading); } } diff --git a/src/ui/radarr_ui/indexers/mod.rs b/src/ui/radarr_ui/indexers/mod.rs index defcbd7..6c025c4 100644 --- a/src/ui/radarr_ui/indexers/mod.rs +++ b/src/ui/radarr_ui/indexers/mod.rs @@ -36,24 +36,20 @@ impl DrawUi for IndexersUi { false } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let route = *app.get_current_route(); let mut indexers_matchers = |active_radarr_block| match active_radarr_block { - ActiveRadarrBlock::Indexers => draw_indexers(f, app, content_rect), - ActiveRadarrBlock::DeleteIndexerPrompt => draw_prompt_popup_over( - f, - app, - content_rect, - draw_indexers, - draw_delete_indexer_prompt, - ), + ActiveRadarrBlock::Indexers => draw_indexers(f, app, area), + ActiveRadarrBlock::DeleteIndexerPrompt => { + draw_prompt_popup_over(f, app, area, draw_indexers, draw_delete_indexer_prompt) + } _ => (), }; match route { - _ if EditIndexerUi::accepts(route) => EditIndexerUi::draw(f, app, content_rect), - _ if IndexerSettingsUi::accepts(route) => IndexerSettingsUi::draw(f, app, content_rect), - _ if TestAllIndexersUi::accepts(route) => TestAllIndexersUi::draw(f, app, content_rect), + _ if EditIndexerUi::accepts(route) => EditIndexerUi::draw(f, app, area), + _ if IndexerSettingsUi::accepts(route) => IndexerSettingsUi::draw(f, app, area), + _ if TestAllIndexersUi::accepts(route) => TestAllIndexersUi::draw(f, app, area), Route::Radarr(active_radarr_block, _) if INDEXERS_BLOCKS.contains(&active_radarr_block) => { indexers_matchers(active_radarr_block) } @@ -142,10 +138,10 @@ fn draw_indexers(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { ) } -fn draw_delete_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_delete_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_prompt_box( f, - prompt_area, + area, "Delete Indexer", format!( "Do you really want to delete this indexer: \n{}?", diff --git a/src/ui/radarr_ui/indexers/test_all_indexers_ui.rs b/src/ui/radarr_ui/indexers/test_all_indexers_ui.rs index 85efab6..a42203b 100644 --- a/src/ui/radarr_ui/indexers/test_all_indexers_ui.rs +++ b/src/ui/radarr_ui/indexers/test_all_indexers_ui.rs @@ -7,7 +7,7 @@ use crate::ui::radarr_ui::indexers::draw_indexers; use crate::ui::styles::ManagarrStyle; use crate::ui::utils::{borderless_block, get_width_from_percentage, title_block}; use crate::ui::{ - draw_help_and_get_content_rect, draw_large_popup_over, draw_table, DrawUi, TableProps, + draw_help_footer_and_get_content_area, draw_large_popup_over, draw_table, DrawUi, TableProps, }; use ratatui::layout::{Constraint, Rect}; use ratatui::widgets::{Cell, Row}; @@ -28,11 +28,11 @@ impl DrawUi for TestAllIndexersUi { false } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_large_popup_over( f, app, - content_rect, + area, draw_indexers, draw_test_all_indexers_test_results, ); @@ -51,7 +51,7 @@ fn draw_test_all_indexers_test_results(f: &mut Frame<'_>, app: &mut App<'_>, are "<↑↓> scroll | {}", build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES) )); - let content_area = draw_help_and_get_content_rect(f, area, help); + let content_area = draw_help_footer_and_get_content_area(f, area, help); draw_table( f, diff --git a/src/ui/radarr_ui/library/add_movie_ui.rs b/src/ui/radarr_ui/library/add_movie_ui.rs index d0ad6db..8c8886d 100644 --- a/src/ui/radarr_ui/library/add_movie_ui.rs +++ b/src/ui/radarr_ui/library/add_movie_ui.rs @@ -1,4 +1,4 @@ -use ratatui::layout::{Alignment, Constraint, Rect}; +use ratatui::layout::{Alignment, Constraint, Layout, Rect}; use ratatui::text::Text; use ratatui::widgets::{Cell, ListItem, Paragraph, Row}; use ratatui::Frame; @@ -13,8 +13,8 @@ use crate::ui::radarr_ui::collections::{draw_collection_details, draw_collection use crate::ui::radarr_ui::library::draw_library; use crate::ui::styles::ManagarrStyle; use crate::ui::utils::{ - borderless_block, get_width_from_percentage, horizontal_chunks, layout_block, - layout_paragraph_borderless, title_block_centered, vertical_chunks_with_margin, + borderless_block, get_width_from_percentage, layout_block, layout_paragraph_borderless, + title_block_centered, }; use crate::ui::{ draw_button, draw_drop_down_menu_button, draw_drop_down_popup, draw_error_popup, @@ -40,7 +40,7 @@ impl DrawUi for AddMovieUi { false } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { let draw_add_movie_search_popup = |f: &mut Frame<'_>, app: &mut App<'_>, area: Rect| match active_radarr_block { @@ -80,21 +80,9 @@ impl DrawUi for AddMovieUi { match active_radarr_block { _ if ADD_MOVIE_BLOCKS.contains(&active_radarr_block) => { if context_option.is_some() { - draw_large_popup_over( - f, - app, - content_rect, - draw_collections, - draw_add_movie_search_popup, - ) + draw_large_popup_over(f, app, area, draw_collections, draw_add_movie_search_popup) } else { - draw_large_popup_over( - f, - app, - content_rect, - draw_library, - draw_add_movie_search_popup, - ) + draw_large_popup_over(f, app, area, draw_library, draw_add_movie_search_popup) } } _ => (), @@ -112,15 +100,13 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { AddMovieSearchResult::default() }; - let chunks = vertical_chunks_with_margin( - vec![ - Constraint::Length(3), - Constraint::Min(0), - Constraint::Length(3), - ], - area, - 1, - ); + let [search_box_area, results_area, help_area] = Layout::vertical([ + Constraint::Length(3), + Constraint::Fill(0), + Constraint::Length(3), + ]) + .margin(1) + .areas(area); let block_content = &app.data.radarr_data.search.as_ref().unwrap().text; let offset = *app .data @@ -137,7 +123,7 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_text_box( f, TextBoxProps { - text_box_area: chunks[0], + text_box_area: search_box_area, block_title: Some("Add Movie"), block_content, offset, @@ -146,16 +132,16 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { cursor_after_string: true, }, ); - f.render_widget(layout_block(), chunks[1]); + f.render_widget(layout_block(), results_area); let help_text = Text::from(build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES).help()); let help_paragraph = Paragraph::new(help_text) .block(borderless_block()) .alignment(Alignment::Center); - f.render_widget(help_paragraph, chunks[2]); + f.render_widget(help_paragraph, help_area); } ActiveRadarrBlock::AddMovieEmptySearchResults => { - f.render_widget(layout_block(), chunks[1]); + f.render_widget(layout_block(), results_area); draw_error_popup(f, "No movies found matching your query!"); } ActiveRadarrBlock::AddMovieSearchResults @@ -171,11 +157,11 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let help_paragraph = Paragraph::new(help_text) .block(borderless_block()) .alignment(Alignment::Center); - f.render_widget(help_paragraph, chunks[2]); + f.render_widget(help_paragraph, help_area); draw_table( f, - chunks[1], + results_area, layout_block(), TableProps { content: None, @@ -269,7 +255,7 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_text_box( f, TextBoxProps { - text_box_area: chunks[0], + text_box_area: search_box_area, block_title: Some("Add Movie"), block_content, offset, @@ -280,14 +266,14 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { ); } -fn draw_confirmation_popup(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_confirmation_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { match active_radarr_block { ActiveRadarrBlock::AddMovieSelectMonitor => { draw_drop_down_popup( f, app, - prompt_area, + area, draw_confirmation_prompt, draw_add_movie_select_monitor_popup, ); @@ -296,7 +282,7 @@ fn draw_confirmation_popup(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Re draw_drop_down_popup( f, app, - prompt_area, + area, draw_confirmation_prompt, draw_add_movie_select_minimum_availability_popup, ); @@ -305,7 +291,7 @@ fn draw_confirmation_popup(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Re draw_drop_down_popup( f, app, - prompt_area, + area, draw_confirmation_prompt, draw_add_movie_select_quality_profile_popup, ); @@ -314,20 +300,20 @@ fn draw_confirmation_popup(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Re draw_drop_down_popup( f, app, - prompt_area, + area, draw_confirmation_prompt, draw_add_movie_select_root_folder_popup, ); } ActiveRadarrBlock::AddMoviePrompt | ActiveRadarrBlock::AddMovieTagsInput => { - draw_confirmation_prompt(f, app, prompt_area) + draw_confirmation_prompt(f, app, area) } _ => (), } } } -fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let (movie_title, movie_overview) = if let Route::Radarr(_, Some(_)) = app.get_current_route() { ( &app @@ -386,34 +372,32 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R let selected_quality_profile = quality_profile_list.current_selection(); let selected_root_folder = root_folder_list.current_selection(); - f.render_widget(title_block_centered(&title), prompt_area); + f.render_widget(title_block_centered(&title), area); - let chunks = vertical_chunks_with_margin( - vec![ + let [paragraph_area, root_folder_area, monitor_area, min_availability_area, quality_profile_area, tags_area, _, buttons_area] = + Layout::vertical([ Constraint::Length(6), Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), - Constraint::Min(0), + Constraint::Fill(0), Constraint::Length(3), - ], - prompt_area, - 1, - ); + ]) + .margin(1) + .areas(area); let prompt_paragraph = layout_paragraph_borderless(&prompt); - f.render_widget(prompt_paragraph, chunks[0]); + f.render_widget(prompt_paragraph, paragraph_area); - let horizontal_chunks = horizontal_chunks( - vec![Constraint::Percentage(50), Constraint::Percentage(50)], - chunks[7], - ); + let [add_area, cancel_area] = + Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]) + .areas(buttons_area); draw_drop_down_menu_button( f, - chunks[1], + root_folder_area, "Root Folder", &selected_root_folder.path, selected_block == &ActiveRadarrBlock::AddMovieSelectRootFolder, @@ -421,7 +405,7 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R draw_drop_down_menu_button( f, - chunks[2], + monitor_area, "Monitor", selected_monitor.to_display_str(), selected_block == &ActiveRadarrBlock::AddMovieSelectMonitor, @@ -429,14 +413,14 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R draw_drop_down_menu_button( f, - chunks[3], + min_availability_area, "Minimum Availability", selected_minimum_availability.to_display_str(), selected_block == &ActiveRadarrBlock::AddMovieSelectMinimumAvailability, ); draw_drop_down_menu_button( f, - chunks[4], + quality_profile_area, "Quality Profile", selected_quality_profile, selected_block == &ActiveRadarrBlock::AddMovieSelectQualityProfile, @@ -446,7 +430,7 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R draw_text_box_with_label( f, LabeledTextBoxProps { - area: chunks[5], + area: tags_area, label: "Tags", text: &tags.text, offset: *tags.offset.borrow(), @@ -457,24 +441,14 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R ); } - draw_button( - f, - horizontal_chunks[0], - "Add", - yes_no_value && highlight_yes_no, - ); - draw_button( - f, - horizontal_chunks[1], - "Cancel", - !yes_no_value && highlight_yes_no, - ); + draw_button(f, add_area, "Add", yes_no_value && highlight_yes_no); + draw_button(f, cancel_area, "Cancel", !yes_no_value && highlight_yes_no); } -fn draw_add_movie_select_monitor_popup(f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect) { +fn draw_add_movie_select_monitor_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_selectable_list( f, - popup_area, + area, &mut app .data .radarr_data @@ -489,11 +463,11 @@ fn draw_add_movie_select_monitor_popup(f: &mut Frame<'_>, app: &mut App<'_>, pop fn draw_add_movie_select_minimum_availability_popup( f: &mut Frame<'_>, app: &mut App<'_>, - popup_area: Rect, + area: Rect, ) { draw_selectable_list( f, - popup_area, + area, &mut app .data .radarr_data @@ -505,14 +479,10 @@ fn draw_add_movie_select_minimum_availability_popup( ); } -fn draw_add_movie_select_quality_profile_popup( - f: &mut Frame<'_>, - app: &mut App<'_>, - popup_area: Rect, -) { +fn draw_add_movie_select_quality_profile_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_selectable_list( f, - popup_area, + area, &mut app .data .radarr_data @@ -524,10 +494,10 @@ fn draw_add_movie_select_quality_profile_popup( ); } -fn draw_add_movie_select_root_folder_popup(f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect) { +fn draw_add_movie_select_root_folder_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_selectable_list( f, - popup_area, + area, &mut app .data .radarr_data diff --git a/src/ui/radarr_ui/library/delete_movie_ui.rs b/src/ui/radarr_ui/library/delete_movie_ui.rs index cf46bfd..b8e325f 100644 --- a/src/ui/radarr_ui/library/delete_movie_ui.rs +++ b/src/ui/radarr_ui/library/delete_movie_ui.rs @@ -22,7 +22,7 @@ impl DrawUi for DeleteMovieUi { false } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { if matches!( *app.get_current_route(), Route::Radarr(ActiveRadarrBlock::DeleteMoviePrompt, _) @@ -55,7 +55,7 @@ impl DrawUi for DeleteMovieUi { ) }; - draw_prompt_popup_over(f, app, content_rect, draw_library, draw_delete_movie_prompt); + draw_prompt_popup_over(f, app, area, draw_library, draw_delete_movie_prompt); } } } diff --git a/src/ui/radarr_ui/library/edit_movie_ui.rs b/src/ui/radarr_ui/library/edit_movie_ui.rs index 1b5aaab..4e36106 100644 --- a/src/ui/radarr_ui/library/edit_movie_ui.rs +++ b/src/ui/radarr_ui/library/edit_movie_ui.rs @@ -1,4 +1,5 @@ use ratatui::layout::{Constraint, Rect}; +use ratatui::prelude::Layout; use ratatui::widgets::ListItem; use ratatui::Frame; @@ -11,9 +12,7 @@ use crate::models::Route; use crate::ui::radarr_ui::library::draw_library; use crate::ui::radarr_ui::library::movie_details_ui::MovieDetailsUi; -use crate::ui::utils::{ - horizontal_chunks, layout_paragraph_borderless, title_block_centered, vertical_chunks_with_margin, -}; +use crate::ui::utils::{layout_paragraph_borderless, title_block_centered}; use crate::ui::{ draw_button, draw_checkbox_with_label, draw_drop_down_menu_button, draw_drop_down_popup, draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over, draw_popup, @@ -35,7 +34,7 @@ impl DrawUi for EditMovieUi { false } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { let draw_edit_movie_prompt = |f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect| match active_radarr_block { @@ -69,13 +68,13 @@ impl DrawUi for EditMovieUi { if let Some(context) = context_option { match context { ActiveRadarrBlock::Movies => { - draw_medium_popup_over(f, app, content_rect, draw_library, draw_edit_movie_prompt); + draw_medium_popup_over(f, app, area, draw_library, draw_edit_movie_prompt); } _ if MOVIE_DETAILS_BLOCKS.contains(&context) => { draw_large_popup_over_background_fn_with_ui::( f, app, - content_rect, + area, draw_library, ); draw_popup(f, app, draw_edit_movie_prompt, 60, 60); @@ -87,7 +86,7 @@ impl DrawUi for EditMovieUi { } } -fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let (movie_title, movie_overview) = if let Some(filtered_movies) = app.data.radarr_data.filtered_movies.as_ref() { ( @@ -128,34 +127,32 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, pro let selected_minimum_availability = minimum_availability_list.current_selection(); let selected_quality_profile = quality_profile_list.current_selection(); - f.render_widget(title_block_centered(&title), prompt_area); + f.render_widget(title_block_centered(&title), area); - let chunks = vertical_chunks_with_margin( - vec![ + let [paragraph_area, monitored_area, min_availability_area, quality_profile_area, path_area, tags_area, _, buttons_area] = + Layout::vertical([ Constraint::Length(6), Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), Constraint::Length(3), - Constraint::Min(0), + Constraint::Fill(0), Constraint::Length(3), - ], - prompt_area, - 1, - ); + ]) + .margin(1) + .areas(area); let prompt_paragraph = layout_paragraph_borderless(&movie_overview); - f.render_widget(prompt_paragraph, chunks[0]); + f.render_widget(prompt_paragraph, paragraph_area); - let horizontal_chunks = horizontal_chunks( - vec![Constraint::Percentage(50), Constraint::Percentage(50)], - chunks[7], - ); + let [save_area, cancel_area] = + Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)]) + .areas(buttons_area); draw_checkbox_with_label( f, - chunks[1], + monitored_area, "Monitored", monitored.unwrap_or_default(), selected_block == &ActiveRadarrBlock::EditMovieToggleMonitored, @@ -163,14 +160,14 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, pro draw_drop_down_menu_button( f, - chunks[2], + min_availability_area, "Minimum Availability", selected_minimum_availability.to_display_str(), selected_block == &ActiveRadarrBlock::EditMovieSelectMinimumAvailability, ); draw_drop_down_menu_button( f, - chunks[3], + quality_profile_area, "Quality Profile", selected_quality_profile, selected_block == &ActiveRadarrBlock::EditMovieSelectQualityProfile, @@ -180,7 +177,7 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, pro draw_text_box_with_label( f, LabeledTextBoxProps { - area: chunks[4], + area: path_area, label: "Path", text: &path.text, offset: *path.offset.borrow(), @@ -192,7 +189,7 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, pro draw_text_box_with_label( f, LabeledTextBoxProps { - area: chunks[5], + area: tags_area, label: "Tags", text: &tags.text, offset: *tags.offset.borrow(), @@ -203,28 +200,18 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, pro ); } - draw_button( - f, - horizontal_chunks[0], - "Save", - yes_no_value && highlight_yes_no, - ); - draw_button( - f, - horizontal_chunks[1], - "Cancel", - !yes_no_value && highlight_yes_no, - ); + draw_button(f, save_area, "Save", yes_no_value && highlight_yes_no); + draw_button(f, cancel_area, "Cancel", !yes_no_value && highlight_yes_no); } fn draw_edit_movie_select_minimum_availability_popup( f: &mut Frame<'_>, app: &mut App<'_>, - popup_area: Rect, + area: Rect, ) { draw_selectable_list( f, - popup_area, + area, &mut app .data .radarr_data @@ -236,14 +223,10 @@ fn draw_edit_movie_select_minimum_availability_popup( ); } -fn draw_edit_movie_select_quality_profile_popup( - f: &mut Frame<'_>, - app: &mut App<'_>, - popup_area: Rect, -) { +fn draw_edit_movie_select_quality_profile_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_selectable_list( f, - popup_area, + area, &mut app .data .radarr_data diff --git a/src/ui/radarr_ui/library/mod.rs b/src/ui/radarr_ui/library/mod.rs index 4db6da5..e515b56 100644 --- a/src/ui/radarr_ui/library/mod.rs +++ b/src/ui/radarr_ui/library/mod.rs @@ -42,62 +42,46 @@ impl DrawUi for LibraryUi { false } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let route = *app.get_current_route(); let mut library_ui_matchers = |active_radarr_block: ActiveRadarrBlock| match active_radarr_block { - ActiveRadarrBlock::Movies => draw_library(f, app, content_rect), - ActiveRadarrBlock::SearchMovie => draw_popup_over( - f, - app, - content_rect, - draw_library, - draw_movie_search_box, - 30, - 13, - ), + ActiveRadarrBlock::Movies => draw_library(f, app, area), + ActiveRadarrBlock::SearchMovie => { + draw_popup_over(f, app, area, draw_library, draw_movie_search_box, 30, 13) + } ActiveRadarrBlock::SearchMovieError => draw_popup_over( f, app, - content_rect, + area, draw_library, draw_search_movie_error_box, 30, 8, ), - ActiveRadarrBlock::FilterMovies => draw_popup_over( - f, - app, - content_rect, - draw_library, - draw_filter_movies_box, - 30, - 13, - ), + ActiveRadarrBlock::FilterMovies => { + draw_popup_over(f, app, area, draw_library, draw_filter_movies_box, 30, 13) + } ActiveRadarrBlock::FilterMoviesError => draw_popup_over( f, app, - content_rect, + area, draw_library, draw_filter_movies_error_box, 30, 8, ), - ActiveRadarrBlock::UpdateAllMoviesPrompt => draw_prompt_popup_over( - f, - app, - content_rect, - draw_library, - draw_update_all_movies_prompt, - ), + ActiveRadarrBlock::UpdateAllMoviesPrompt => { + draw_prompt_popup_over(f, app, area, draw_library, draw_update_all_movies_prompt) + } _ => (), }; match route { - _ if MovieDetailsUi::accepts(route) => MovieDetailsUi::draw(f, app, content_rect), - _ if AddMovieUi::accepts(route) => AddMovieUi::draw(f, app, content_rect), - _ if EditMovieUi::accepts(route) => EditMovieUi::draw(f, app, content_rect), - _ if DeleteMovieUi::accepts(route) => DeleteMovieUi::draw(f, app, content_rect), + _ if MovieDetailsUi::accepts(route) => MovieDetailsUi::draw(f, app, area), + _ if AddMovieUi::accepts(route) => AddMovieUi::draw(f, app, area), + _ if EditMovieUi::accepts(route) => EditMovieUi::draw(f, app, area), + _ if DeleteMovieUi::accepts(route) => DeleteMovieUi::draw(f, app, area), Route::Radarr(active_radarr_block, _) if LIBRARY_BLOCKS.contains(&active_radarr_block) => { library_ui_matchers(active_radarr_block) } @@ -208,10 +192,10 @@ pub(super) fn draw_library(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { ); } -fn draw_update_all_movies_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_update_all_movies_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_prompt_box( f, - prompt_area, + area, "Update All Movies", "Do you want to update info and scan your disks for all of your movies?", app.data.radarr_data.prompt_confirm, diff --git a/src/ui/radarr_ui/library/movie_details_ui.rs b/src/ui/radarr_ui/library/movie_details_ui.rs index fe03739..ea8259e 100644 --- a/src/ui/radarr_ui/library/movie_details_ui.rs +++ b/src/ui/radarr_ui/library/movie_details_ui.rs @@ -1,6 +1,6 @@ use std::iter; -use ratatui::layout::{Alignment, Constraint, Rect}; +use ratatui::layout::{Alignment, Constraint, Layout, Rect}; use ratatui::style::{Style, Stylize}; use ratatui::text::{Line, Span, Text}; use ratatui::widgets::{Cell, ListItem, Paragraph, Row, Wrap}; @@ -14,7 +14,6 @@ use crate::ui::radarr_ui::library::draw_library; use crate::ui::styles::ManagarrStyle; use crate::ui::utils::{ borderless_block, get_width_from_percentage, layout_block_bottom_border, layout_block_top_border, - vertical_chunks, }; use crate::ui::{ draw_drop_down_popup, draw_large_popup_over, draw_prompt_box, draw_prompt_box_with_content, @@ -38,10 +37,10 @@ impl DrawUi for MovieDetailsUi { false } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() { let draw_movie_info_popup = |f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect| { - let (content_area, _) = draw_tabs( + let content_area = draw_tabs( f, popup_area, "Movie Info", @@ -94,7 +93,7 @@ impl DrawUi for MovieDetailsUi { } }; - draw_large_popup_over(f, app, content_rect, draw_library, draw_movie_info_popup); + draw_large_popup_over(f, app, area, draw_library, draw_movie_info_popup); } } } @@ -115,10 +114,10 @@ fn draw_movie_info(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { } } -fn draw_search_movie_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_search_movie_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_prompt_box( f, - prompt_area, + area, "Automatic Movie Search", format!( "Do you want to trigger an automatic search of your indexers for the movie: {}?", @@ -129,10 +128,10 @@ fn draw_search_movie_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R ); } -fn draw_update_and_scan_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_update_and_scan_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_prompt_box( f, - prompt_area, + area, "Update and Scan", format!( "Do you want to trigger an update and disk scan for the movie: {}?", @@ -143,7 +142,7 @@ fn draw_update_and_scan_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area ); } -fn draw_file_info(f: &mut Frame<'_>, app: &App<'_>, content_area: Rect) { +fn draw_file_info(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { match app.data.radarr_data.movie_details_modal.as_ref() { Some(movie_details_modal) if !movie_details_modal.file_details.is_empty() && !app.is_loading => @@ -151,17 +150,16 @@ fn draw_file_info(f: &mut Frame<'_>, app: &App<'_>, content_area: Rect) { let file_info = movie_details_modal.file_details.to_owned(); let audio_details = movie_details_modal.audio_details.to_owned(); let video_details = movie_details_modal.video_details.to_owned(); - let chunks = vertical_chunks( - vec![ + let [file_details_title_area, file_details_area, audio_details_title_area, audio_details_area, video_details_title_area, video_details_area] = + Layout::vertical([ Constraint::Length(2), Constraint::Length(5), Constraint::Length(1), Constraint::Length(6), Constraint::Length(1), Constraint::Length(7), - ], - content_area, - ); + ]) + .areas(area); let file_details_title_paragraph = Paragraph::new("File Details".bold()).block(layout_block_top_border()); @@ -184,18 +182,18 @@ fn draw_file_info(f: &mut Frame<'_>, app: &App<'_>, content_area: Rect) { .block(borderless_block()) .wrap(Wrap { trim: false }); - f.render_widget(file_details_title_paragraph, chunks[0]); - f.render_widget(file_details_paragraph, chunks[1]); - f.render_widget(audio_details_title_paragraph, chunks[2]); - f.render_widget(audio_details_paragraph, chunks[3]); - f.render_widget(video_details_title_paragraph, chunks[4]); - f.render_widget(video_details_paragraph, chunks[5]); + f.render_widget(file_details_title_paragraph, file_details_title_area); + f.render_widget(file_details_paragraph, file_details_area); + f.render_widget(audio_details_title_paragraph, audio_details_title_area); + f.render_widget(audio_details_paragraph, audio_details_area); + f.render_widget(video_details_title_paragraph, video_details_title_area); + f.render_widget(video_details_paragraph, video_details_area); } - _ => loading(f, layout_block_top_border(), content_area, app.is_loading), + _ => loading(f, layout_block_top_border(), area, app.is_loading), } } -fn draw_movie_details(f: &mut Frame<'_>, app: &App<'_>, content_area: Rect) { +fn draw_movie_details(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { let block = layout_block_top_border(); match app.data.radarr_data.movie_details_modal.as_ref() { @@ -230,18 +228,18 @@ fn draw_movie_details(f: &mut Frame<'_>, app: &App<'_>, content_area: Rect) { .wrap(Wrap { trim: false }) .scroll((movie_details.offset, 0)); - f.render_widget(paragraph, content_area); + f.render_widget(paragraph, area); } _ => loading( f, block, - content_area, + area, app.is_loading || app.data.radarr_data.movie_details_modal.is_none(), ), } } -fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) { +fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { if let Some(movie_details_modal) = app.data.radarr_data.movie_details_modal.as_mut() { let current_selection = if movie_details_modal.movie_history.items.is_empty() { MovieHistoryItem::default() @@ -254,7 +252,7 @@ fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) draw_table( f, - content_area, + area, layout_block_top_border(), TableProps { content: Some(&mut movie_details_modal.movie_history), @@ -283,7 +281,7 @@ fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) } = movie_history_item; movie_history_item.source_title.scroll_left_or_reset( - get_width_from_percentage(content_area, 34), + get_width_from_percentage(area, 34), current_selection == *movie_history_item, app.tick_count % app.ticks_until_scroll == 0, ); @@ -309,10 +307,10 @@ fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) } } -fn draw_movie_cast(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) { +fn draw_movie_cast(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_table( f, - content_area, + area, layout_block_top_border(), TableProps { content: Some( @@ -351,10 +349,10 @@ fn draw_movie_cast(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) { ); } -fn draw_movie_crew(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) { +fn draw_movie_crew(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_table( f, - content_area, + area, layout_block_top_border(), TableProps { content: Some( @@ -395,7 +393,7 @@ fn draw_movie_crew(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) { ); } -fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) { +fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let (current_selection, is_empty, sort_ascending) = match app.data.radarr_data.movie_details_modal.as_ref() { Some(movie_details_modal) if !movie_details_modal.movie_releases.items.is_empty() => ( @@ -447,7 +445,7 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) draw_table( f, - content_area, + area, layout_block_top_border(), TableProps { content: Some( @@ -494,7 +492,7 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) } = release; let age = format!("{age} days"); title.scroll_left_or_reset( - get_width_from_percentage(content_area, 30), + get_width_from_percentage(area, 30), current_selection == *release && current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(), app.tick_count % app.ticks_until_scroll == 0, @@ -539,7 +537,7 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect) ); } -fn draw_manual_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_manual_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let current_selection = app .data .radarr_data @@ -583,20 +581,14 @@ fn draw_manual_search_confirm_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp draw_prompt_box_with_content( f, - prompt_area, + area, title, &prompt, Some(content_paragraph), app.data.radarr_data.prompt_confirm, ); } else { - draw_prompt_box( - f, - prompt_area, - title, - &prompt, - app.data.radarr_data.prompt_confirm, - ); + draw_prompt_box(f, area, title, &prompt, app.data.radarr_data.prompt_confirm); } } diff --git a/src/ui/radarr_ui/mod.rs b/src/ui/radarr_ui/mod.rs index a44fdf8..f3d9899 100644 --- a/src/ui/radarr_ui/mod.rs +++ b/src/ui/radarr_ui/mod.rs @@ -23,7 +23,6 @@ use crate::ui::radarr_ui::system::SystemUi; use crate::ui::styles::ManagarrStyle; use crate::ui::utils::{ borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block, - vertical_chunks_with_margin, }; use crate::ui::DrawUi; use crate::utils::convert_to_gb; @@ -48,29 +47,30 @@ impl DrawUi for RadarrUi { } fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { - let (content_rect, _) = draw_tabs(f, area, "Movies", &app.data.radarr_data.main_tabs); + let content_area = draw_tabs(f, area, "Movies", &app.data.radarr_data.main_tabs); let route = *app.get_current_route(); match route { - _ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_rect), - _ if CollectionsUi::accepts(route) => CollectionsUi::draw(f, app, content_rect), - _ if DownloadsUi::accepts(route) => DownloadsUi::draw(f, app, content_rect), - _ if IndexersUi::accepts(route) => IndexersUi::draw(f, app, content_rect), - _ if RootFoldersUi::accepts(route) => RootFoldersUi::draw(f, app, content_rect), - _ if SystemUi::accepts(route) => SystemUi::draw(f, app, content_rect), + _ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_area), + _ if CollectionsUi::accepts(route) => CollectionsUi::draw(f, app, content_area), + _ if DownloadsUi::accepts(route) => DownloadsUi::draw(f, app, content_area), + _ if IndexersUi::accepts(route) => IndexersUi::draw(f, app, content_area), + _ if RootFoldersUi::accepts(route) => RootFoldersUi::draw(f, app, content_area), + _ if SystemUi::accepts(route) => SystemUi::draw(f, app, content_area), _ => (), } } fn draw_context_row(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { - let [main, logo] = Layout::horizontal([Constraint::Min(0), Constraint::Length(20)]).areas(area); + let [main_area, logo_area] = + Layout::horizontal([Constraint::Fill(0), Constraint::Length(20)]).areas(area); - let [stats, downloads] = - Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]).areas(main); + let [stats_area, downloads_area] = + Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]).areas(main_area); - draw_stats_context(f, app, stats); - draw_downloads_context(f, app, downloads); - draw_radarr_logo(f, logo); + draw_stats_context(f, app, stats_area); + draw_downloads_context(f, app, downloads_area); + draw_radarr_logo(f, logo_area); } } @@ -98,7 +98,7 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { .collect(), ); - let chunks = vertical_chunks_with_margin(constraints, area, 1); + let stat_item_areas = Layout::vertical(constraints).margin(1).split(area); let version_paragraph = Paragraph::new(Text::from(format!( "Radarr Version: {}", @@ -125,9 +125,9 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { let storage = Paragraph::new(Text::from("Storage:")).block(borderless_block().bold()); let folders = Paragraph::new(Text::from("Root Folders:")).block(borderless_block().bold()); - f.render_widget(version_paragraph, chunks[0]); - f.render_widget(uptime_paragraph, chunks[1]); - f.render_widget(storage, chunks[2]); + f.render_widget(version_paragraph, stat_item_areas[0]); + f.render_widget(uptime_paragraph, stat_item_areas[1]); + f.render_widget(storage, stat_item_areas[2]); for i in 0..disk_space_vec.len() { let DiskSpace { @@ -143,10 +143,10 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { let space_gauge = line_gauge_with_label(title.as_str(), ratio); - f.render_widget(space_gauge, chunks[i + 3]); + f.render_widget(space_gauge, stat_item_areas[i + 3]); } - f.render_widget(folders, chunks[disk_space_vec.len() + 3]); + f.render_widget(folders, stat_item_areas[disk_space_vec.len() + 3]); for i in 0..root_folders.items.len() { let RootFolder { @@ -157,7 +157,10 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { .block(borderless_block()) .default(); - f.render_widget(root_folder_space, chunks[i + disk_space_vec.len() + 4]) + f.render_widget( + root_folder_space, + stat_item_areas[i + disk_space_vec.len() + 4], + ) } } else { loading(f, block, area, app.is_loading); @@ -171,11 +174,13 @@ fn draw_downloads_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { if !downloads_vec.is_empty() { f.render_widget(block, area); - let constraints = iter::repeat(Constraint::Length(2)) - .take(downloads_vec.len()) - .collect::>(); - - let chunks = vertical_chunks_with_margin(constraints, area, 1); + let download_item_areas = Layout::vertical( + iter::repeat(Constraint::Length(2)) + .take(downloads_vec.len()) + .collect::>(), + ) + .margin(1) + .split(area); for i in 0..downloads_vec.len() { let DownloadRecord { @@ -187,7 +192,7 @@ fn draw_downloads_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { let percent = 1f64 - (*sizeleft as f64 / *size as f64); let download_gauge = line_gauge_with_title(title, percent); - f.render_widget(download_gauge, chunks[i]); + f.render_widget(download_gauge, download_item_areas[i]); } } else { loading(f, block, area, app.is_loading); diff --git a/src/ui/radarr_ui/root_folders/mod.rs b/src/ui/radarr_ui/root_folders/mod.rs index 906cfd1..d132552 100644 --- a/src/ui/radarr_ui/root_folders/mod.rs +++ b/src/ui/radarr_ui/root_folders/mod.rs @@ -6,12 +6,12 @@ use crate::app::App; use crate::models::radarr_models::RootFolder; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS}; use crate::models::Route; -use crate::ui::utils::{layout_block_top_border}; +use crate::ui::styles::ManagarrStyle; +use crate::ui::utils::layout_block_top_border; use crate::ui::{ draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps, }; -use crate::ui::styles::ManagarrStyle; use crate::utils::convert_to_gb; #[cfg(test)] @@ -29,14 +29,14 @@ impl DrawUi for RootFoldersUi { false } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { match active_radarr_block { - ActiveRadarrBlock::RootFolders => draw_root_folders(f, app, content_rect), + ActiveRadarrBlock::RootFolders => draw_root_folders(f, app, area), ActiveRadarrBlock::AddRootFolderPrompt => draw_popup_over( f, app, - content_rect, + area, draw_root_folders, draw_add_root_folder_prompt_box, 30, @@ -45,7 +45,7 @@ impl DrawUi for RootFoldersUi { ActiveRadarrBlock::DeleteRootFolderPrompt => draw_prompt_popup_over( f, app, - content_rect, + area, draw_root_folders, draw_delete_root_folder_prompt, ), @@ -95,7 +95,8 @@ fn draw_root_folders(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { .len() .to_string(), ), - ]).primary() + ]) + .primary() }, app.is_loading, true, @@ -111,10 +112,10 @@ fn draw_add_root_folder_prompt_box(f: &mut Frame<'_>, app: &mut App<'_>, area: R ); } -fn draw_delete_root_folder_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_delete_root_folder_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_prompt_box( f, - prompt_area, + area, "Delete Root Folder", format!( "Do you really want to delete this root folder: \n{}?", diff --git a/src/ui/radarr_ui/system/mod.rs b/src/ui/radarr_ui/system/mod.rs index 2a9c860..e550329 100644 --- a/src/ui/radarr_ui/system/mod.rs +++ b/src/ui/radarr_ui/system/mod.rs @@ -1,7 +1,7 @@ use std::ops::Sub; use chrono::Utc; -use ratatui::layout::Alignment; +use ratatui::layout::{Alignment, Layout}; use ratatui::text::{Span, Text}; use ratatui::widgets::{Cell, Paragraph, Row}; use ratatui::{ @@ -20,11 +20,7 @@ use crate::ui::utils::layout_block_top_border; use crate::ui::{draw_table, ListProps, TableProps}; use crate::{ models::Route, - ui::{ - draw_list_box, - utils::{horizontal_chunks, title_block, vertical_chunks}, - DrawUi, - }, + ui::{draw_list_box, utils::title_block, DrawUi}, }; mod system_details_ui; @@ -60,13 +56,13 @@ impl DrawUi for SystemUi { false } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let route = *app.get_current_route(); match route { - _ if SystemDetailsUi::accepts(route) => SystemDetailsUi::draw(f, app, content_rect), + _ if SystemDetailsUi::accepts(route) => SystemDetailsUi::draw(f, app, area), _ if matches!(route, Route::Radarr(ActiveRadarrBlock::System, _)) => { - draw_system_ui_layout(f, app, content_rect) + draw_system_ui_layout(f, app, area) } _ => (), } @@ -74,24 +70,20 @@ impl DrawUi for SystemUi { } pub(super) fn draw_system_ui_layout(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { - let vertical_chunks = vertical_chunks( - vec![ - Constraint::Ratio(1, 2), - Constraint::Ratio(1, 2), - Constraint::Min(2), - ], - area, - ); + let [activities_area, logs_area, help_area] = Layout::vertical([ + Constraint::Ratio(1, 2), + Constraint::Ratio(1, 2), + Constraint::Min(2), + ]) + .areas(area); - let horizontal_chunks = horizontal_chunks( - vec![Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)], - vertical_chunks[0], - ); + let [tasks_area, events_area] = + Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]).areas(activities_area); - draw_tasks(f, app, horizontal_chunks[0]); - draw_queued_events(f, app, horizontal_chunks[1]); - draw_logs(f, app, vertical_chunks[1]); - draw_help(f, app, vertical_chunks[2]); + draw_tasks(f, app, tasks_area); + draw_queued_events(f, app, events_area); + draw_logs(f, app, logs_area); + draw_help(f, app, help_area); } fn draw_tasks(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { diff --git a/src/ui/radarr_ui/system/system_details_ui.rs b/src/ui/radarr_ui/system/system_details_ui.rs index a8454be..e948bd8 100644 --- a/src/ui/radarr_ui/system/system_details_ui.rs +++ b/src/ui/radarr_ui/system/system_details_ui.rs @@ -16,8 +16,9 @@ use crate::ui::radarr_ui::system::{ use crate::ui::styles::ManagarrStyle; use crate::ui::utils::{borderless_block, title_block}; use crate::ui::{ - draw_help_and_get_content_rect, draw_large_popup_over, draw_list_box, draw_medium_popup_over, - draw_prompt_box, draw_prompt_popup_over, draw_table, loading, DrawUi, ListProps, TableProps, + draw_help_footer_and_get_content_area, draw_large_popup_over, draw_list_box, + draw_medium_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, loading, DrawUi, + ListProps, TableProps, }; #[cfg(test)] @@ -35,35 +36,21 @@ impl DrawUi for SystemDetailsUi { false } - fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { + fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { match active_radarr_block { ActiveRadarrBlock::SystemLogs => { - draw_large_popup_over(f, app, content_rect, draw_system_ui_layout, draw_logs_popup) + draw_large_popup_over(f, app, area, draw_system_ui_layout, draw_logs_popup) } ActiveRadarrBlock::SystemTasks | ActiveRadarrBlock::SystemTaskStartConfirmPrompt => { - draw_large_popup_over( - f, - app, - content_rect, - draw_system_ui_layout, - draw_tasks_popup, - ) + draw_large_popup_over(f, app, area, draw_system_ui_layout, draw_tasks_popup) + } + ActiveRadarrBlock::SystemQueuedEvents => { + draw_medium_popup_over(f, app, area, draw_system_ui_layout, draw_queued_events) + } + ActiveRadarrBlock::SystemUpdates => { + draw_large_popup_over(f, app, area, draw_system_ui_layout, draw_updates_popup) } - ActiveRadarrBlock::SystemQueuedEvents => draw_medium_popup_over( - f, - app, - content_rect, - draw_system_ui_layout, - draw_queued_events, - ), - ActiveRadarrBlock::SystemUpdates => draw_large_popup_over( - f, - app, - content_rect, - draw_system_ui_layout, - draw_updates_popup, - ), _ => (), } } @@ -97,7 +84,7 @@ fn draw_tasks_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { let tasks_popup_table = |f: &mut Frame<'_>, app: &mut App<'_>, area: Rect| { f.render_widget(title_block("Tasks"), area); - let context_area = draw_help_and_get_content_rect( + let context_area = draw_help_footer_and_get_content_area( f, area, Some(build_context_clue_string(&SYSTEM_TASKS_CONTEXT_CLUES)), @@ -141,10 +128,10 @@ fn draw_tasks_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { } } -fn draw_start_task_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect) { +fn draw_start_task_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { draw_prompt_box( f, - prompt_area, + area, "Start Task", format!( "Do you want to manually start this task: {}?", @@ -158,7 +145,7 @@ fn draw_start_task_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rec fn draw_updates_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { f.render_widget(title_block("Updates"), area); - let content_rect = draw_help_and_get_content_rect( + let content_area = draw_help_footer_and_get_content_area( f, area, Some(format!( @@ -174,8 +161,8 @@ fn draw_updates_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { .block(block) .scroll((app.data.radarr_data.updates.offset, 0)); - f.render_widget(updates_paragraph, content_rect); + f.render_widget(updates_paragraph, content_area); } else { - loading(f, block, content_rect, app.is_loading); + loading(f, block, content_area, app.is_loading); } } diff --git a/src/ui/utils.rs b/src/ui/utils.rs index 4048e99..f68c750 100644 --- a/src/ui/utils.rs +++ b/src/ui/utils.rs @@ -1,10 +1,9 @@ use crate::ui::styles::ManagarrStyle; -use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; +use ratatui::layout::{Alignment, Constraint, Layout, Rect}; use ratatui::style::{Color, Style, Stylize}; use ratatui::text::{Line, Span, Text}; use ratatui::widgets::{Block, BorderType, Borders, LineGauge, Paragraph, Wrap}; use ratatui::{symbols, Frame}; -use std::rc::Rc; pub const COLOR_TEAL: Color = Color::Rgb(35, 50, 55); @@ -12,46 +11,6 @@ pub const COLOR_TEAL: Color = Color::Rgb(35, 50, 55); #[path = "utils_tests.rs"] mod utils_tests; -pub fn horizontal_chunks(constraints: Vec, area: Rect) -> Rc<[Rect]> { - layout_with_constraints(constraints) - .direction(Direction::Horizontal) - .split(area) -} - -pub fn horizontal_chunks_with_margin( - constraints: Vec, - area: Rect, - margin: u16, -) -> Rc<[Rect]> { - layout_with_constraints(constraints) - .direction(Direction::Horizontal) - .margin(margin) - .split(area) -} - -pub fn vertical_chunks(constraints: Vec, area: Rect) -> Rc<[Rect]> { - layout_with_constraints(constraints) - .direction(Direction::Vertical) - .split(area) -} - -pub fn vertical_chunks_with_margin( - constraints: Vec, - area: Rect, - margin: u16, -) -> Rc<[Rect]> { - layout_with_constraints(constraints) - .direction(Direction::Vertical) - .margin(margin) - .split(area) -} - -fn layout_with_constraints(constraints: Vec) -> Layout { - Layout::default().constraints( as AsRef<[Constraint]>>::as_ref( - &constraints, - )) -} - pub fn background_block<'a>() -> Block<'a> { Block::new().white().bg(COLOR_TEAL) } @@ -140,27 +99,22 @@ pub fn logo_block<'a>() -> Block<'a> { )) } -pub fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect { - let popup_layout = vertical_chunks( - vec![ - Constraint::Percentage((100 - percent_y) / 2), - Constraint::Percentage(percent_y), - Constraint::Percentage((100 - percent_y) / 2), - ], - r, - ); +pub fn centered_rect(percent_x: u16, percent_y: u16, area: Rect) -> Rect { + let [_, vertical_area, _] = Layout::vertical([ + Constraint::Percentage((100 - percent_y) / 2), + Constraint::Percentage(percent_y), + Constraint::Percentage((100 - percent_y) / 2), + ]) + .areas(area); - Layout::default() - .direction(Direction::Horizontal) - .constraints( - [ - Constraint::Percentage((100 - percent_x) / 2), - Constraint::Percentage(percent_x), - Constraint::Percentage((100 - percent_x) / 2), - ] - .as_ref(), - ) - .split(popup_layout[1])[1] + let [_, horizontal_layout, _] = Layout::horizontal([ + Constraint::Percentage((100 - percent_x) / 2), + Constraint::Percentage(percent_x), + Constraint::Percentage((100 - percent_x) / 2), + ]) + .areas(vertical_area); + + horizontal_layout } pub fn line_gauge_with_title(title: &str, ratio: f64) -> LineGauge<'_> { diff --git a/src/ui/utils_tests.rs b/src/ui/utils_tests.rs index 035dcf2..888e6bd 100644 --- a/src/ui/utils_tests.rs +++ b/src/ui/utils_tests.rs @@ -1,113 +1,18 @@ #[cfg(test)] mod test { use pretty_assertions::assert_eq; - use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect}; + use ratatui::layout::{Alignment, Rect}; use ratatui::style::{Color, Modifier, Style}; use ratatui::text::Span; use ratatui::widgets::{Block, BorderType, Borders}; use crate::ui::utils::{ - borderless_block, centered_rect, get_width_from_percentage, horizontal_chunks, - horizontal_chunks_with_margin, layout_block, layout_block_bottom_border, - layout_block_top_border, layout_block_top_border_with_title, layout_block_with_title, - layout_with_constraints, logo_block, style_block_highlight, title_block, title_block_centered, - title_style, vertical_chunks, vertical_chunks_with_margin, + borderless_block, centered_rect, get_width_from_percentage, layout_block, + layout_block_bottom_border, layout_block_top_border, layout_block_top_border_with_title, + layout_block_with_title, logo_block, style_block_highlight, title_block, title_block_centered, + title_style, }; - #[test] - fn test_horizontal_chunks() { - let constraints = [ - Constraint::Percentage(10), - Constraint::Max(20), - Constraint::Min(10), - Constraint::Length(30), - Constraint::Ratio(3, 4), - ]; - let area = rect(); - let expected_layout = Layout::default() - .constraints(constraints) - .direction(Direction::Horizontal) - .split(area); - - assert_eq!(horizontal_chunks(constraints.into(), area), expected_layout); - } - - #[test] - fn test_horizontal_chunks_with_margin() { - let constraints = [ - Constraint::Percentage(10), - Constraint::Max(20), - Constraint::Min(10), - Constraint::Length(30), - Constraint::Ratio(3, 4), - ]; - let area = rect(); - let expected_layout = Layout::default() - .constraints(constraints) - .direction(Direction::Horizontal) - .margin(1) - .split(area); - - assert_eq!( - horizontal_chunks_with_margin(constraints.into(), area, 1), - expected_layout - ); - } - - #[test] - fn test_vertical_chunks() { - let constraints = [ - Constraint::Percentage(10), - Constraint::Max(20), - Constraint::Min(10), - Constraint::Length(30), - Constraint::Ratio(3, 4), - ]; - let area = rect(); - let expected_layout = Layout::default() - .constraints(constraints) - .direction(Direction::Vertical) - .split(area); - - assert_eq!(vertical_chunks(constraints.into(), area), expected_layout); - } - - #[test] - fn test_vertical_chunks_with_margin() { - let constraints = [ - Constraint::Percentage(10), - Constraint::Max(20), - Constraint::Min(10), - Constraint::Length(30), - Constraint::Ratio(3, 4), - ]; - let area = rect(); - let expected_layout = Layout::default() - .constraints(constraints) - .direction(Direction::Vertical) - .margin(1) - .split(area); - - assert_eq!( - vertical_chunks_with_margin(constraints.into(), area, 1), - expected_layout - ); - } - - #[test] - fn test_layout_with_constraints() { - let constraints = [ - Constraint::Percentage(10), - Constraint::Max(20), - Constraint::Min(10), - Constraint::Length(30), - Constraint::Ratio(3, 4), - ]; - let expected_layout = Layout::default().constraints(constraints); - - assert_eq!(layout_with_constraints(constraints.into()), expected_layout); - } - #[test] fn test_layout_block() { assert_eq!(