Completed the refactor and upgrade to Ratatui v0.26. Next up: Refactoring all of the "draw_" functions into custom widgets for more ergonomic and extensible DevX

This commit is contained in:
2024-02-08 13:58:45 -07:00
parent c6f51ab9b6
commit 9b0c272e76
20 changed files with 529 additions and 870 deletions
+111 -154
View File
@@ -1,7 +1,6 @@
use std::iter; 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::style::{Modifier, Style, Stylize};
use ratatui::text::{Line, Text}; use ratatui::text::{Line, Text};
use ratatui::widgets::Paragraph; use ratatui::widgets::Paragraph;
@@ -17,10 +16,9 @@ use crate::models::{HorizontallyScrollableText, Route, StatefulList, StatefulTab
use crate::ui::radarr_ui::RadarrUi; use crate::ui::radarr_ui::RadarrUi;
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{ use crate::ui::utils::{
background_block, borderless_block, centered_rect, horizontal_chunks, background_block, borderless_block, centered_rect, layout_block, layout_block_top_border,
horizontal_chunks_with_margin, layout_block, layout_block_top_border, layout_button_paragraph, layout_button_paragraph, layout_button_paragraph_borderless, layout_paragraph_borderless,
layout_button_paragraph_borderless, layout_paragraph_borderless, logo_block, show_cursor, logo_block, show_cursor, style_block_highlight, title_block, title_block_centered,
style_block_highlight, title_block, title_block_centered, vertical_chunks_with_margin,
}; };
mod radarr_ui; mod radarr_ui;
@@ -31,46 +29,48 @@ static HIGHLIGHT_SYMBOL: &str = "=> ";
pub trait DrawUi { pub trait DrawUi {
fn accepts(route: Route) -> bool; 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) {} fn draw_context_row(_f: &mut Frame<'_>, _app: &App<'_>, _area: Rect) {}
} }
pub fn ui(f: &mut Frame<'_>, app: &mut App<'_>) { pub fn ui(f: &mut Frame<'_>, app: &mut App<'_>) {
f.render_widget(background_block(), f.size()); f.render_widget(background_block(), f.size());
let [header, context, table] = if !app.error.text.is_empty() { let [header_area, context_area, table_area] = if !app.error.text.is_empty() {
let [header, error, context, table] = Layout::vertical([ let [header_area, error_area, context_area, table_area] = Layout::vertical([
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(10), Constraint::Length(10),
Constraint::Fill(1), Constraint::Fill(0),
]) ])
.margin(1)
.areas(f.size()); .areas(f.size());
draw_error(f, app, error); draw_error(f, app, error_area);
[header, context, table] [header_area, context_area, table_area]
} else { } else {
Layout::vertical([ Layout::vertical([
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(10), Constraint::Length(10),
Constraint::Fill(1), Constraint::Fill(0),
]) ])
.margin(1)
.areas(f.size()) .areas(f.size())
}; };
draw_header_row(f, app, header); draw_header_row(f, app, header_area);
if RadarrUi::accepts(*app.get_current_route()) { if RadarrUi::accepts(*app.get_current_route()) {
RadarrUi::draw_context_row(f, app, context); RadarrUi::draw_context_row(f, app, context_area);
RadarrUi::draw(f, app, table); RadarrUi::draw(f, app, table_area);
} }
} }
fn draw_header_row(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_header_row(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let chunks = f.render_widget(logo_block(), area);
horizontal_chunks_with_margin(vec![Constraint::Length(75), Constraint::Min(0)], area, 1);
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 help_text = Text::from(app.server_tabs.get_active_tab_help().help());
let titles = app let titles = app
@@ -79,15 +79,15 @@ fn draw_header_row(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.iter() .iter()
.map(|tab| Line::from(tab.title.bold())); .map(|tab| Line::from(tab.title.bold()));
let tabs = Tabs::new(titles) let tabs = Tabs::new(titles)
.block(logo_block()) .block(borderless_block())
.highlight_style(Style::new().secondary()) .highlight_style(Style::new().secondary())
.select(app.server_tabs.index); .select(app.server_tabs.index);
let help = Paragraph::new(help_text) let help = Paragraph::new(help_text)
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Right); .alignment(Alignment::Right);
f.render_widget(tabs, area); f.render_widget(tabs, tabs_area);
f.render_widget(help, chunks[1]); f.render_widget(help, help_area);
} }
fn draw_error(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { 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); f.render_widget(error_message, prompt_area);
} }
fn draw_tabs<'a>( fn draw_tabs(f: &mut Frame<'_>, area: Rect, title: &str, tab_state: &TabState) -> Rect {
f: &mut Frame<'_>, f.render_widget(title_block(title), area);
area: Rect,
title: &str, let [header_area, content_area] = Layout::vertical([Constraint::Length(1), Constraint::Fill(0)])
tab_state: &TabState, .margin(1)
) -> (Rect, Block<'a>) { .areas(area);
let chunks = let [tabs_area, help_area] = Layout::horizontal([Constraint::Min(25), Constraint::Min(25)])
vertical_chunks_with_margin(vec![Constraint::Length(2), Constraint::Min(0)], area, 1); .flex(Flex::SpaceBetween)
let horizontal_chunks = horizontal_chunks_with_margin( .areas(header_area);
vec![Constraint::Percentage(10), Constraint::Fill(1)],
area,
1,
);
let block = title_block(title);
let titles = tab_state let titles = tab_state
.tabs .tabs
.iter() .iter()
.map(|tab_route| Line::from(tab_route.title.bold())); .map(|tab_route| Line::from(tab_route.title.bold()));
let tabs = Tabs::new(titles) let tabs = Tabs::new(titles)
.block(block) .block(borderless_block())
.highlight_style(Style::new().secondary()) .highlight_style(Style::new().secondary())
.select(tab_state.index); .select(tab_state.index);
let help = Paragraph::new(Text::from(tab_state.get_active_tab_help().help())) let help = Paragraph::new(Text::from(tab_state.get_active_tab_help().help()))
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Right); .alignment(Alignment::Right);
f.render_widget(tabs, area); f.render_widget(tabs, tabs_area);
f.render_widget(help, horizontal_chunks[1]); f.render_widget(help, help_area);
(chunks[1], layout_block_top_border()) content_area
} }
pub struct TableProps<'a, T> { pub struct TableProps<'a, T> {
@@ -295,7 +290,7 @@ pub struct ListProps<'a, T> {
fn draw_table<'a, T, F>( fn draw_table<'a, T, F>(
f: &mut Frame<'_>, f: &mut Frame<'_>,
content_area: Rect, area: Rect,
block: Block<'_>, block: Block<'_>,
table_props: TableProps<'a, T>, table_props: TableProps<'a, T>,
row_mapper: F, row_mapper: F,
@@ -312,7 +307,7 @@ fn draw_table<'a, T, F>(
help, help,
} = table_props; } = 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)] #[allow(clippy::unnecessary_unwrap)]
if wrapped_content.is_some() && wrapped_content.as_ref().unwrap().is_some() { 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<T>, content: &mut StatefulTable<T>,
table_headers: Vec<&str>, table_headers: Vec<&str>,
constraints: Vec<Constraint>, constraints: Vec<Constraint>,
content_area: Rect, area: Rect,
) where ) where
F: Fn(&T) -> Row<'a>, F: Fn(&T) -> Row<'a>,
{ {
@@ -367,7 +362,7 @@ fn draw_table_contents<'a, T, F>(
.highlight_symbol(HIGHLIGHT_SYMBOL); .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) { 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( pub fn draw_prompt_box(
f: &mut Frame<'_>, f: &mut Frame<'_>,
prompt_area: Rect, area: Rect,
title: &str, title: &str,
prompt: &str, prompt: &str,
yes_no_value: bool, 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( pub fn draw_prompt_box_with_content(
f: &mut Frame<'_>, f: &mut Frame<'_>,
prompt_area: Rect, area: Rect,
title: &str, title: &str,
prompt: &str, prompt: &str,
content: Option<Paragraph<'_>>, content: Option<Paragraph<'_>>,
yes_no_value: bool, 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 [prompt_area, buttons_area] = if let Some(content_paragraph) = content {
let vertical_chunks = vertical_chunks_with_margin( let [prompt_area, content_area, _, buttons_area] = Layout::vertical([
vec![
Constraint::Length(4), Constraint::Length(4),
Constraint::Length(7), Constraint::Length(7),
Constraint::Min(0), Constraint::Fill(0),
Constraint::Length(3), Constraint::Length(3),
], ])
prompt_area, .margin(1)
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 { } else {
vertical_chunks_with_margin( let [prompt_area, _, buttons_area] = Layout::vertical([
vec![
Constraint::Percentage(72), Constraint::Percentage(72),
Constraint::Min(0), Constraint::Fill(0),
Constraint::Length(3), Constraint::Length(3),
], ])
prompt_area, .margin(1)
1, .areas(area);
)
[prompt_area, buttons_area]
}; };
let prompt_paragraph = layout_paragraph_borderless(prompt); 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( let [yes_area, no_area] =
vec![Constraint::Percentage(50), Constraint::Percentage(50)], Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
chunks[2], .areas(buttons_area);
);
draw_button(f, horizontal_chunks[0], "Yes", yes_no_value); draw_button(f, yes_area, "Yes", yes_no_value);
draw_button(f, horizontal_chunks[1], "No", !yes_no_value); draw_button(f, no_area, "No", !yes_no_value);
} }
pub fn draw_prompt_box_with_checkboxes( pub fn draw_prompt_box_with_checkboxes(
f: &mut Frame<'_>, f: &mut Frame<'_>,
prompt_area: Rect, area: Rect,
title: &str, title: &str,
prompt: &str, prompt: &str,
checkboxes: Vec<(&str, bool, bool)>, checkboxes: Vec<(&str, bool, bool)>,
highlight_yes_no: bool, highlight_yes_no: bool,
yes_no_value: 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![ let mut constraints = vec![
Constraint::Length(4), Constraint::Length(4),
Constraint::Min(0), Constraint::Fill(0),
Constraint::Length(3), Constraint::Length(3),
]; ];
@@ -461,7 +453,7 @@ pub fn draw_prompt_box_with_checkboxes(
iter::repeat(Constraint::Length(3)).take(checkboxes.len()), 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); let prompt_paragraph = layout_paragraph_borderless(prompt);
f.render_widget(prompt_paragraph, chunks[0]); 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); draw_checkbox_with_label(f, chunks[i + 1], label, is_checked, is_selected);
} }
let horizontal_chunks = horizontal_chunks( let [yes_area, no_area] =
vec![Constraint::Percentage(50), Constraint::Percentage(50)], Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
chunks[checkboxes.len() + 2], .areas(chunks[checkboxes.len() + 2]);
);
draw_button( draw_button(f, yes_area, "Yes", highlight_yes_no && yes_no_value);
f, draw_button(f, no_area, "No", highlight_yes_no && !yes_no_value);
horizontal_chunks[0],
"Yes",
highlight_yes_no && yes_no_value,
);
draw_button(
f,
horizontal_chunks[1],
"No",
highlight_yes_no && !yes_no_value,
);
} }
pub fn draw_checkbox(f: &mut Frame<'_>, area: Rect, is_checked: bool, is_selected: bool) { 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_checked: bool,
is_selected: bool, is_selected: bool,
) { ) {
let horizontal_chunks = horizontal_chunks( let [label_area, checkbox_area] =
vec![ Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area);
Constraint::Percentage(48),
Constraint::Percentage(48),
Constraint::Percentage(4),
],
area,
);
let label_paragraph = Paragraph::new(Text::from(format!("\n{label}: "))) let label_paragraph = Paragraph::new(Text::from(format!("\n{label}: ")))
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Right) .alignment(Alignment::Right)
.primary(); .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) { 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 label_paragraph = layout_button_paragraph_borderless(is_selected, label, Alignment::Left);
let icon_paragraph = layout_button_paragraph_borderless(is_selected, icon, Alignment::Right); let icon_paragraph = layout_button_paragraph_borderless(is_selected, icon, Alignment::Right);
let horizontal_chunks = horizontal_chunks_with_margin( let [label_area, icon_area] =
vec![ Layout::horizontal([Constraint::Percentage(25), Constraint::Percentage(25)])
Constraint::Percentage(50), .flex(Flex::SpaceBetween)
Constraint::Percentage(49), .margin(1)
Constraint::Percentage(1), .areas(area);
],
area,
1,
);
f.render_widget( f.render_widget(
layout_block().style(style_block_highlight(is_selected)), layout_block().style(style_block_highlight(is_selected)),
area, area,
); );
f.render_widget(label_paragraph, horizontal_chunks[0]); f.render_widget(label_paragraph, label_area);
f.render_widget(icon_paragraph, horizontal_chunks[1]); f.render_widget(icon_paragraph, icon_area);
} }
pub fn draw_drop_down_menu_button( pub fn draw_drop_down_menu_button(
@@ -568,23 +539,17 @@ pub fn draw_drop_down_menu_button(
selection: &str, selection: &str,
is_selected: bool, is_selected: bool,
) { ) {
let horizontal_chunks = horizontal_chunks( let [label_area, button_area] =
vec![ Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area);
Constraint::Percentage(48),
Constraint::Percentage(48),
Constraint::Percentage(4),
],
area,
);
let description_paragraph = Paragraph::new(Text::from(format!("\n{description}: "))) let description_paragraph = Paragraph::new(Text::from(format!("\n{description}: ")))
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Right) .alignment(Alignment::Right)
.primary(); .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>( 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 { let (content_area, block) = if is_popup {
f.render_widget(title_block(title), area); 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(), borderless_block(),
) )
} else { } 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<String>) -> Rect { fn draw_help_footer_and_get_content_area(
f: &mut Frame<'_>,
area: Rect,
help: Option<String>,
) -> Rect {
if let Some(help_string) = help { if let Some(help_string) = help {
let chunks = let [content_area, help_footer_area] =
vertical_chunks_with_margin(vec![Constraint::Min(0), Constraint::Length(2)], area, 1); Layout::vertical([Constraint::Fill(0), Constraint::Length(2)]).areas(area);
let help_paragraph = Paragraph::new(Text::from(format!(" {help_string}").help())) let help_paragraph = Paragraph::new(Text::from(format!(" {help_string}").help()))
.block(layout_block_top_border()) .block(layout_block_top_border())
.alignment(Alignment::Left); .alignment(Alignment::Left);
f.render_widget(help_paragraph, chunks[1]); f.render_widget(help_paragraph, help_footer_area);
chunks[0] content_area
} else { } else {
area area
} }
@@ -721,26 +690,20 @@ pub fn draw_text_box_with_label(
should_show_cursor, should_show_cursor,
cursor_after_string, cursor_after_string,
} = labeled_text_box_props; } = labeled_text_box_props;
let horizontal_chunks = horizontal_chunks( let [label_area, text_box_area] =
vec![ Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area);
Constraint::Percentage(48),
Constraint::Percentage(48),
Constraint::Percentage(4),
],
area,
);
let label_paragraph = Paragraph::new(Text::from(format!("\n{label}: "))) let label_paragraph = Paragraph::new(Text::from(format!("\n{label}: ")))
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Right) .alignment(Alignment::Right)
.primary(); .primary();
f.render_widget(label_paragraph, horizontal_chunks[0]); f.render_widget(label_paragraph, label_area);
draw_text_box( draw_text_box(
f, f,
TextBoxProps { TextBoxProps {
text_box_area: horizontal_chunks[1], text_box_area,
block_title: None, block_title: None,
block_content: text, block_content: text,
offset, offset,
@@ -753,24 +716,18 @@ pub fn draw_text_box_with_label(
pub fn draw_input_box_popup( pub fn draw_input_box_popup(
f: &mut Frame<'_>, f: &mut Frame<'_>,
input_box_area: Rect, area: Rect,
box_title: &str, box_title: &str,
box_content: &HorizontallyScrollableText, box_content: &HorizontallyScrollableText,
) { ) {
let chunks = vertical_chunks_with_margin( let [text_box_area, help_area] = Layout::vertical([Constraint::Length(3), Constraint::Length(1)])
vec![ .margin(1)
Constraint::Length(3), .areas(area);
Constraint::Length(1),
Constraint::Min(0),
],
input_box_area,
1,
);
draw_text_box( draw_text_box(
f, f,
TextBoxProps { TextBoxProps {
text_box_area: chunks[0], text_box_area,
block_title: Some(box_title), block_title: Some(box_title),
block_content: &box_content.text, block_content: &box_content.text,
offset: *box_content.offset.borrow(), offset: *box_content.offset.borrow(),
@@ -784,14 +741,14 @@ pub fn draw_input_box_popup(
.help() .help()
.alignment(Alignment::Center) .alignment(Alignment::Center)
.block(borderless_block()); .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) let input = Paragraph::new(error_msg)
.failure() .failure()
.alignment(Alignment::Center) .alignment(Alignment::Center)
.block(layout_block()); .block(layout_block());
f.render_widget(input, error_message_area); f.render_widget(input, area);
} }
@@ -1,4 +1,4 @@
use ratatui::layout::{Alignment, Constraint, Rect}; use ratatui::layout::{Alignment, Constraint, Flex, Layout, Rect};
use ratatui::style::Stylize; use ratatui::style::Stylize;
use ratatui::text::{Line, Text}; use ratatui::text::{Line, Text};
use ratatui::widgets::{Cell, Paragraph, Row, Wrap}; 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::styles::ManagarrStyle;
use crate::ui::utils::{ use crate::ui::utils::{
borderless_block, get_width_from_percentage, layout_block_top_border_with_title, title_block, 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::ui::{draw_large_popup_over, draw_small_popup_over, draw_table, DrawUi, TableProps};
use crate::utils::convert_runtime; use crate::utils::convert_runtime;
@@ -36,7 +36,7 @@ impl DrawUi for CollectionDetailsUi {
false 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() { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() {
let draw_collection_details_popup = let draw_collection_details_popup =
|f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect| match context_option |f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect| match context_option
@@ -58,7 +58,7 @@ impl DrawUi for CollectionDetailsUi {
draw_large_popup_over( draw_large_popup_over(
f, f,
app, app,
content_rect, area,
draw_collections, draw_collections,
draw_collection_details_popup, 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) { pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let chunks = vertical_chunks_with_margin( let [description_area, table_area, help_footer_area] = Layout::vertical([
vec![
Constraint::Percentage(25), Constraint::Percentage(25),
Constraint::Percentage(70), Constraint::Percentage(70),
Constraint::Percentage(5), Constraint::Percentage(5),
], ])
content_area, .margin(1)
1, .areas(area);
);
let collection_selection = let collection_selection =
if let Some(filtered_collections) = app.data.radarr_data.filtered_collections.as_ref() { if let Some(filtered_collections) = app.data.radarr_data.filtered_collections.as_ref() {
filtered_collections.current_selection() filtered_collections.current_selection()
@@ -157,14 +155,14 @@ pub fn draw_collection_details(f: &mut Frame<'_>, app: &mut App<'_>, content_are
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Center); .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(description_paragraph, description_area);
f.render_widget(help_paragraph, chunks[2]); f.render_widget(help_paragraph, help_footer_area);
draw_table( draw_table(
f, f,
chunks[1], table_area,
layout_block_top_border_with_title(title_style("Movies")), layout_block_top_border_with_title(title_style("Movies")),
TableProps { TableProps {
content: Some(&mut app.data.radarr_data.collection_movies), 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( movie.title.scroll_left_or_reset(
get_width_from_percentage(chunks[1], 20), get_width_from_percentage(table_area, 20),
current_selection == *movie, current_selection == *movie,
app.tick_count % app.ticks_until_scroll == 0, 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"); 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( let [paragraph_area, help_area] =
vec![Constraint::Percentage(95), Constraint::Percentage(5)], Layout::vertical([Constraint::Percentage(95), Constraint::Length(1)])
content_area, .flex(Flex::SpaceBetween)
1, .margin(1)
); .areas(area);
let overview = Text::from( let overview = Text::from(
app app
.data .data
@@ -279,6 +277,6 @@ fn draw_movie_overview(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect)
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Center); .alignment(Alignment::Center);
f.render_widget(paragraph, chunks[0]); f.render_widget(paragraph, paragraph_area);
f.render_widget(help_paragraph, chunks[1]); f.render_widget(help_paragraph, help_area);
} }
@@ -1,4 +1,4 @@
use ratatui::layout::{Constraint, Rect}; use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::widgets::ListItem; use ratatui::widgets::ListItem;
use ratatui::Frame; use ratatui::Frame;
@@ -10,9 +10,7 @@ use crate::models::servarr_data::radarr::radarr_data::{
use crate::models::Route; use crate::models::Route;
use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi; use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi;
use crate::ui::radarr_ui::collections::draw_collections; use crate::ui::radarr_ui::collections::draw_collections;
use crate::ui::utils::{ use crate::ui::utils::{layout_paragraph_borderless, title_block_centered};
horizontal_chunks, layout_paragraph_borderless, title_block_centered, vertical_chunks_with_margin,
};
use crate::ui::{ use crate::ui::{
draw_button, draw_checkbox_with_label, draw_drop_down_menu_button, draw_drop_down_popup, 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, draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over, draw_popup,
@@ -34,7 +32,7 @@ impl DrawUi for EditCollectionUi {
false 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() { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() {
let draw_edit_collection_prompt = let draw_edit_collection_prompt =
|f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect| match active_radarr_block { |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 { if let Some(context) = context_option {
match context { match context {
ActiveRadarrBlock::Collections => draw_medium_popup_over( ActiveRadarrBlock::Collections => {
f, draw_medium_popup_over(f, app, area, draw_collections, draw_edit_collection_prompt)
app, }
content_rect,
draw_collections,
draw_edit_collection_prompt,
),
_ if COLLECTION_DETAILS_BLOCKS.contains(&context) => { _ if COLLECTION_DETAILS_BLOCKS.contains(&context) => {
draw_large_popup_over_background_fn_with_ui::<CollectionDetailsUi>( draw_large_popup_over_background_fn_with_ui::<CollectionDetailsUi>(
f, f,
app, app,
content_rect, area,
draw_collections, draw_collections,
); );
draw_popup(f, app, draw_edit_collection_prompt, 60, 60); draw_popup(f, app, draw_edit_collection_prompt, 60, 60);
@@ -90,11 +84,7 @@ impl DrawUi for EditCollectionUi {
} }
} }
fn draw_edit_collection_confirmation_prompt( fn draw_edit_collection_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
f: &mut Frame<'_>,
app: &mut App<'_>,
prompt_area: Rect,
) {
let (collection_title, collection_overview) = let (collection_title, collection_overview) =
if let Some(filtered_collections) = app.data.radarr_data.filtered_collections.as_ref() { 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_minimum_availability = minimum_availability_list.current_selection();
let selected_quality_profile = quality_profile_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( let [paragraph_area, monitored_area, min_availability_area, quality_profile_area, root_folder_area, search_on_add_area, _, buttons_area] =
vec![ Layout::vertical([
Constraint::Length(6), Constraint::Length(6),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Min(0), Constraint::Fill(0),
Constraint::Length(3), Constraint::Length(3),
], ])
prompt_area, .margin(1)
1, .areas(area);
);
let prompt_paragraph = layout_paragraph_borderless(&collection_overview); 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( let [save_area, cancel_area] =
vec![Constraint::Percentage(50), Constraint::Percentage(50)], Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
chunks[7], .areas(buttons_area);
);
draw_checkbox_with_label( draw_checkbox_with_label(
f, f,
chunks[1], monitored_area,
"Monitored", "Monitored",
monitored.unwrap_or_default(), monitored.unwrap_or_default(),
selected_block == &ActiveRadarrBlock::EditCollectionToggleMonitored, selected_block == &ActiveRadarrBlock::EditCollectionToggleMonitored,
@@ -175,14 +163,14 @@ fn draw_edit_collection_confirmation_prompt(
draw_drop_down_menu_button( draw_drop_down_menu_button(
f, f,
chunks[2], min_availability_area,
"Minimum Availability", "Minimum Availability",
selected_minimum_availability.to_display_str(), selected_minimum_availability.to_display_str(),
selected_block == &ActiveRadarrBlock::EditCollectionSelectMinimumAvailability, selected_block == &ActiveRadarrBlock::EditCollectionSelectMinimumAvailability,
); );
draw_drop_down_menu_button( draw_drop_down_menu_button(
f, f,
chunks[3], quality_profile_area,
"Quality Profile", "Quality Profile",
selected_quality_profile, selected_quality_profile,
selected_block == &ActiveRadarrBlock::EditCollectionSelectQualityProfile, selected_block == &ActiveRadarrBlock::EditCollectionSelectQualityProfile,
@@ -192,7 +180,7 @@ fn draw_edit_collection_confirmation_prompt(
draw_text_box_with_label( draw_text_box_with_label(
f, f,
LabeledTextBoxProps { LabeledTextBoxProps {
area: chunks[4], area: root_folder_area,
label: "Root Folder", label: "Root Folder",
text: &path.text, text: &path.text,
offset: *path.offset.borrow(), offset: *path.offset.borrow(),
@@ -206,34 +194,24 @@ fn draw_edit_collection_confirmation_prompt(
draw_checkbox_with_label( draw_checkbox_with_label(
f, f,
chunks[5], search_on_add_area,
"Search on Add", "Search on Add",
search_on_add.unwrap_or_default(), search_on_add.unwrap_or_default(),
selected_block == &ActiveRadarrBlock::EditCollectionToggleSearchOnAdd, selected_block == &ActiveRadarrBlock::EditCollectionToggleSearchOnAdd,
); );
draw_button( draw_button(f, save_area, "Save", yes_no_value && highlight_yes_no);
f, draw_button(f, cancel_area, "Cancel", !yes_no_value && highlight_yes_no);
horizontal_chunks[0],
"Save",
yes_no_value && highlight_yes_no,
);
draw_button(
f,
horizontal_chunks[1],
"Cancel",
!yes_no_value && highlight_yes_no,
);
} }
fn draw_edit_collection_select_minimum_availability_popup( fn draw_edit_collection_select_minimum_availability_popup(
f: &mut Frame<'_>, f: &mut Frame<'_>,
app: &mut App<'_>, app: &mut App<'_>,
popup_area: Rect, area: Rect,
) { ) {
draw_selectable_list( draw_selectable_list(
f, f,
popup_area, area,
&mut app &mut app
.data .data
.radarr_data .radarr_data
@@ -248,11 +226,11 @@ fn draw_edit_collection_select_minimum_availability_popup(
fn draw_edit_collection_select_quality_profile_popup( fn draw_edit_collection_select_quality_profile_popup(
f: &mut Frame<'_>, f: &mut Frame<'_>,
app: &mut App<'_>, app: &mut App<'_>,
popup_area: Rect, area: Rect,
) { ) {
draw_selectable_list( draw_selectable_list(
f, f,
popup_area, area,
&mut app &mut app
.data .data
.radarr_data .radarr_data
+14 -13
View File
@@ -10,12 +10,12 @@ use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, COLLEC
use crate::models::Route; use crate::models::Route;
use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi; use crate::ui::radarr_ui::collections::collection_details_ui::CollectionDetailsUi;
use crate::ui::radarr_ui::collections::edit_collection_ui::EditCollectionUi; 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::utils::{get_width_from_percentage, layout_block_top_border};
use crate::ui::{ use crate::ui::{
draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_error_message_popup, draw_input_box_popup, draw_popup_over, draw_prompt_box,
draw_prompt_popup_over, draw_table, DrawUi, TableProps, draw_prompt_popup_over, draw_table, DrawUi, TableProps,
}; };
use crate::ui::styles::ManagarrStyle;
mod collection_details_ui; mod collection_details_ui;
#[cfg(test)] #[cfg(test)]
@@ -36,14 +36,14 @@ impl DrawUi for CollectionsUi {
false 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 route = *app.get_current_route();
let mut collections_ui_matcher = |active_radarr_block| match active_radarr_block { 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( ActiveRadarrBlock::SearchCollection => draw_popup_over(
f, f,
app, app,
content_rect, area,
draw_collections, draw_collections,
draw_collection_search_box, draw_collection_search_box,
30, 30,
@@ -52,7 +52,7 @@ impl DrawUi for CollectionsUi {
ActiveRadarrBlock::SearchCollectionError => draw_popup_over( ActiveRadarrBlock::SearchCollectionError => draw_popup_over(
f, f,
app, app,
content_rect, area,
draw_collections, draw_collections,
draw_search_collection_error_box, draw_search_collection_error_box,
30, 30,
@@ -61,7 +61,7 @@ impl DrawUi for CollectionsUi {
ActiveRadarrBlock::FilterCollections => draw_popup_over( ActiveRadarrBlock::FilterCollections => draw_popup_over(
f, f,
app, app,
content_rect, area,
draw_collections, draw_collections,
draw_filter_collections_box, draw_filter_collections_box,
30, 30,
@@ -70,7 +70,7 @@ impl DrawUi for CollectionsUi {
ActiveRadarrBlock::FilterCollectionsError => draw_popup_over( ActiveRadarrBlock::FilterCollectionsError => draw_popup_over(
f, f,
app, app,
content_rect, area,
draw_collections, draw_collections,
draw_filter_collections_error_box, draw_filter_collections_error_box,
30, 30,
@@ -79,7 +79,7 @@ impl DrawUi for CollectionsUi {
ActiveRadarrBlock::UpdateAllCollectionsPrompt => draw_prompt_popup_over( ActiveRadarrBlock::UpdateAllCollectionsPrompt => draw_prompt_popup_over(
f, f,
app, app,
content_rect, area,
draw_collections, draw_collections,
draw_update_all_collections_prompt, draw_update_all_collections_prompt,
), ),
@@ -87,8 +87,8 @@ impl DrawUi for CollectionsUi {
}; };
match route { match route {
_ if CollectionDetailsUi::accepts(route) => CollectionDetailsUi::draw(f, app, content_rect), _ if CollectionDetailsUi::accepts(route) => CollectionDetailsUi::draw(f, app, area),
_ if EditCollectionUi::accepts(route) => EditCollectionUi::draw(f, app, content_rect), _ if EditCollectionUi::accepts(route) => EditCollectionUi::draw(f, app, area),
Route::Radarr(active_radarr_block, _) Route::Radarr(active_radarr_block, _)
if COLLECTIONS_BLOCKS.contains(&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(search_on_add),
Cell::from(monitored), Cell::from(monitored),
]).primary() ])
.primary()
}, },
app.is_loading, app.is_loading,
true, 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( draw_prompt_box(
f, f,
prompt_area, area,
"Update All Collections", "Update All Collections",
"Do you want to update all of your collections?", "Do you want to update all of your collections?",
app.data.radarr_data.prompt_confirm, app.data.radarr_data.prompt_confirm,
+15 -22
View File
@@ -6,9 +6,9 @@ use crate::app::App;
use crate::models::radarr_models::DownloadRecord; use crate::models::radarr_models::DownloadRecord;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, DOWNLOADS_BLOCKS};
use crate::models::{HorizontallyScrollableText, Route}; 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::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::{draw_prompt_box, draw_prompt_popup_over, draw_table, DrawUi, TableProps};
use crate::ui::styles::ManagarrStyle;
use crate::utils::convert_to_gb; use crate::utils::convert_to_gb;
#[cfg(test)] #[cfg(test)]
@@ -26,24 +26,16 @@ impl DrawUi for DownloadsUi {
false 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() { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
match active_radarr_block { match active_radarr_block {
ActiveRadarrBlock::Downloads => draw_downloads(f, app, content_rect), ActiveRadarrBlock::Downloads => draw_downloads(f, app, area),
ActiveRadarrBlock::DeleteDownloadPrompt => draw_prompt_popup_over( ActiveRadarrBlock::DeleteDownloadPrompt => {
f, draw_prompt_popup_over(f, app, area, draw_downloads, draw_delete_download_prompt)
app, }
content_rect, ActiveRadarrBlock::UpdateDownloadsPrompt => {
draw_downloads, draw_prompt_popup_over(f, app, area, draw_downloads, draw_update_downloads_prompt)
draw_delete_download_prompt, }
),
ActiveRadarrBlock::UpdateDownloadsPrompt => draw_prompt_popup_over(
f,
app,
content_rect,
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(indexer.to_owned()),
Cell::from(download_client.to_owned()), Cell::from(download_client.to_owned()),
]).primary() ])
.primary()
}, },
app.is_loading, app.is_loading,
true, 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( draw_prompt_box(
f, f,
prompt_area, area,
"Cancel Download", "Cancel Download",
format!( format!(
"Do you really want to delete this download: \n{}?", "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( draw_prompt_box(
f, f,
prompt_area, area,
"Update Downloads", "Update Downloads",
"Do you want to update your downloads?", "Do you want to update your downloads?",
app.data.radarr_data.prompt_confirm, app.data.radarr_data.prompt_confirm,
+32 -50
View File
@@ -2,17 +2,13 @@ use crate::app::App;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, EDIT_INDEXER_BLOCKS};
use crate::models::Route; use crate::models::Route;
use crate::ui::radarr_ui::indexers::draw_indexers; use crate::ui::radarr_ui::indexers::draw_indexers;
use crate::ui::utils::{ use crate::ui::utils::title_block_centered;
horizontal_chunks, horizontal_chunks_with_margin, title_block_centered, vertical_chunks,
vertical_chunks_with_margin,
};
use crate::ui::{ use crate::ui::{
draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading, draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading,
DrawUi, LabeledTextBoxProps, DrawUi, LabeledTextBoxProps,
}; };
use ratatui::layout::{Constraint, Layout, Rect}; use ratatui::layout::{Constraint, Flex, Layout, Rect};
use ratatui::Frame; use ratatui::Frame;
use std::iter;
#[cfg(test)] #[cfg(test)]
#[path = "edit_indexer_ui_tests.rs"] #[path = "edit_indexer_ui_tests.rs"]
@@ -29,11 +25,11 @@ impl DrawUi for EditIndexerUi {
false 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( draw_popup_over(
f, f,
app, app,
content_rect, area,
draw_indexers, draw_indexers,
draw_edit_indexer_prompt, draw_edit_indexer_prompt,
70, 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 block = title_block_centered("Edit Indexer");
let yes_no_value = app.data.radarr_data.prompt_confirm; let yes_no_value = app.data.radarr_data.prompt_confirm;
let selected_block = app.data.radarr_data.selected_block.get_active_block(); 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() { if edit_indexer_modal_option.is_some() {
let edit_indexer_modal = edit_indexer_modal_option.as_ref().unwrap(); 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( let [settings_area, buttons_area] =
vec![Constraint::Min(0), Constraint::Length(3)], Layout::vertical([Constraint::Fill(0), Constraint::Length(3)])
prompt_area, .margin(1)
1, .areas(area);
);
let split_chunks = horizontal_chunks_with_margin( let [left_side_area, right_side_area] =
vec![Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)], Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)])
chunks[0], .margin(1)
1, .areas(settings_area);
);
let [name, rss, auto_search, interactive_search, _] = Layout::vertical([ let [name, rss, auto_search, interactive_search, _] = Layout::vertical([
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Min(0), Constraint::Fill(0),
]) ])
.areas(split_chunks[0]); .areas(left_side_area);
let right_chunks = vertical_chunks( let [url_area, api_key_area, seed_ratio_area, tags_area, _] = Layout::vertical([
vec![
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Min(0), Constraint::Fill(0),
], ])
split_chunks[1], .areas(right_side_area);
);
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
draw_text_box_with_label( 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( draw_text_box_with_label(
f, f,
LabeledTextBoxProps { LabeledTextBoxProps {
area: right_chunks[0], area: url_area,
label: "URL", label: "URL",
text: &edit_indexer_modal.url.text, text: &edit_indexer_modal.url.text,
offset: *edit_indexer_modal.url.offset.borrow(), 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( draw_text_box_with_label(
f, f,
LabeledTextBoxProps { LabeledTextBoxProps {
area: right_chunks[1], area: api_key_area,
label: "API Key", label: "API Key",
text: &edit_indexer_modal.api_key.text, text: &edit_indexer_modal.api_key.text,
offset: *edit_indexer_modal.api_key.offset.borrow(), 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( draw_text_box_with_label(
f, f,
LabeledTextBoxProps { LabeledTextBoxProps {
area: right_chunks[2], area: seed_ratio_area,
label: "Seed Ratio", label: "Seed Ratio",
text: &edit_indexer_modal.seed_ratio.text, text: &edit_indexer_modal.seed_ratio.text,
offset: *edit_indexer_modal.seed_ratio.offset.borrow(), 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( draw_text_box_with_label(
f, f,
LabeledTextBoxProps { LabeledTextBoxProps {
area: right_chunks[3], area: tags_area,
label: "Tags", label: "Tags",
text: &edit_indexer_modal.tags.text, text: &edit_indexer_modal.tags.text,
offset: *edit_indexer_modal.tags.offset.borrow(), 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( draw_text_box_with_label(
f, f,
LabeledTextBoxProps { LabeledTextBoxProps {
area: right_chunks[2], area: seed_ratio_area,
label: "Tags", label: "Tags",
text: &edit_indexer_modal.tags.text, text: &edit_indexer_modal.tags.text,
offset: *edit_indexer_modal.tags.offset.borrow(), 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, selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch,
); );
let button_chunks = horizontal_chunks( let [save_area, cancel_area] =
iter::repeat(Constraint::Ratio(1, 4)).take(4).collect(), Layout::horizontal([Constraint::Percentage(25), Constraint::Percentage(25)])
chunks[1], .flex(Flex::Center)
); .areas(buttons_area);
draw_button( draw_button(f, save_area, "Save", yes_no_value && highlight_yes_no);
f, draw_button(f, cancel_area, "Cancel", !yes_no_value && highlight_yes_no);
button_chunks[1],
"Save",
yes_no_value && highlight_yes_no,
);
draw_button(
f,
button_chunks[2],
"Cancel",
!yes_no_value && highlight_yes_no,
);
} }
} else { } else {
loading(f, block, prompt_area, app.is_loading); loading(f, block, area, app.is_loading);
} }
} }
@@ -1,6 +1,5 @@
use ratatui::layout::{Constraint, Rect}; use ratatui::layout::{Constraint, Flex, Layout, Rect};
use ratatui::Frame; use ratatui::Frame;
use std::iter;
use crate::app::App; use crate::app::App;
use crate::models::servarr_data::radarr::radarr_data::{ 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::models::Route;
use crate::ui::radarr_ui::indexers::draw_indexers; use crate::ui::radarr_ui::indexers::draw_indexers;
use crate::ui::utils::{ use crate::ui::utils::title_block_centered;
horizontal_chunks, horizontal_chunks_with_margin, title_block_centered, vertical_chunks,
vertical_chunks_with_margin,
};
use crate::ui::{ use crate::ui::{
draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading, draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading,
DrawUi, LabeledTextBoxProps, DrawUi, LabeledTextBoxProps,
@@ -32,11 +28,11 @@ impl DrawUi for IndexerSettingsUi {
false 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( draw_popup_over(
f, f,
app, app,
content_rect, area,
draw_indexers, draw_indexers,
draw_edit_indexer_settings_prompt, draw_edit_indexer_settings_prompt,
70, 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 block = title_block_centered("Configure All Indexer Settings");
let yes_no_value = app.data.radarr_data.prompt_confirm; let yes_no_value = app.data.radarr_data.prompt_confirm;
let selected_block = app.data.radarr_data.selected_block.get_active_block(); 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() { if indexer_settings_option.is_some() {
let indexer_settings = indexer_settings_option.as_ref().unwrap(); 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( let [settings_area, buttons_area] =
vec![Constraint::Min(0), Constraint::Length(3)], Layout::vertical([Constraint::Fill(0), Constraint::Length(3)])
prompt_area, .margin(1)
1, .areas(area);
);
let split_chunks = horizontal_chunks_with_margin( let [left_side_area, right_side_area] =
vec![Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)], Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)])
chunks[0], .margin(1)
1, .areas(settings_area);
);
let left_chunks = vertical_chunks( let [min_age_area, retention_area, max_size_area, prefer_flags_area, _] = Layout::vertical([
vec![
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Min(0), Constraint::Fill(0),
], ])
split_chunks[0], .areas(left_side_area);
); let [availability_delay_area, rss_sync_interval_area, whitelisted_sub_tags_area, allow_hardcoded_subs_area, _] =
let right_chunks = vertical_chunks( Layout::vertical([
vec![
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Min(0), Constraint::Fill(0),
], ])
split_chunks[1], .areas(right_side_area);
);
if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
draw_text_box_with_label( draw_text_box_with_label(
f, f,
LabeledTextBoxProps { LabeledTextBoxProps {
area: left_chunks[0], area: min_age_area,
label: "Minimum Age (minutes) ▴▾", label: "Minimum Age (minutes) ▴▾",
text: &indexer_settings.minimum_age.to_string(), text: &indexer_settings.minimum_age.to_string(),
offset: 0, offset: 0,
@@ -106,7 +97,7 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp
draw_text_box_with_label( draw_text_box_with_label(
f, f,
LabeledTextBoxProps { LabeledTextBoxProps {
area: left_chunks[1], area: retention_area,
label: "Retention (days) ▴▾", label: "Retention (days) ▴▾",
text: &indexer_settings.retention.to_string(), text: &indexer_settings.retention.to_string(),
offset: 0, offset: 0,
@@ -119,7 +110,7 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp
draw_text_box_with_label( draw_text_box_with_label(
f, f,
LabeledTextBoxProps { LabeledTextBoxProps {
area: left_chunks[2], area: max_size_area,
label: "Maximum Size (MB) ▴▾", label: "Maximum Size (MB) ▴▾",
text: &indexer_settings.maximum_size.to_string(), text: &indexer_settings.maximum_size.to_string(),
offset: 0, offset: 0,
@@ -132,7 +123,7 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp
draw_text_box_with_label( draw_text_box_with_label(
f, f,
LabeledTextBoxProps { LabeledTextBoxProps {
area: right_chunks[0], area: availability_delay_area,
label: "Availability Delay (days) ▴▾", label: "Availability Delay (days) ▴▾",
text: &indexer_settings.availability_delay.to_string(), text: &indexer_settings.availability_delay.to_string(),
offset: 0, offset: 0,
@@ -145,7 +136,7 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp
draw_text_box_with_label( draw_text_box_with_label(
f, f,
LabeledTextBoxProps { LabeledTextBoxProps {
area: right_chunks[1], area: rss_sync_interval_area,
label: "RSS Sync Interval (minutes) ▴▾", label: "RSS Sync Interval (minutes) ▴▾",
text: &indexer_settings.rss_sync_interval.to_string(), text: &indexer_settings.rss_sync_interval.to_string(),
offset: 0, offset: 0,
@@ -158,7 +149,7 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, promp
draw_text_box_with_label( draw_text_box_with_label(
f, f,
LabeledTextBoxProps { LabeledTextBoxProps {
area: right_chunks[2], area: whitelisted_sub_tags_area,
label: "Whitelisted Subtitle Tags", label: "Whitelisted Subtitle Tags",
text: &indexer_settings.whitelisted_hardcoded_subs.text, text: &indexer_settings.whitelisted_hardcoded_subs.text,
offset: *indexer_settings.whitelisted_hardcoded_subs.offset.borrow(), 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( draw_checkbox_with_label(
f, f,
left_chunks[3], prefer_flags_area,
"Prefer Indexer Flags", "Prefer Indexer Flags",
indexer_settings.prefer_indexer_flags, indexer_settings.prefer_indexer_flags,
selected_block == &ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags, 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( draw_checkbox_with_label(
f, f,
right_chunks[3], allow_hardcoded_subs_area,
"Allow Hardcoded Subs", "Allow Hardcoded Subs",
indexer_settings.allow_hardcoded_subs, indexer_settings.allow_hardcoded_subs,
selected_block == &ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs, selected_block == &ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs,
); );
let button_chunks = horizontal_chunks( let [save_area, cancel_area] =
iter::repeat(Constraint::Ratio(1, 4)).take(4).collect(), Layout::horizontal([Constraint::Percentage(25), Constraint::Percentage(25)])
chunks[1], .flex(Flex::Center)
); .areas(buttons_area);
draw_button( draw_button(f, save_area, "Save", yes_no_value && highlight_yes_no);
f, draw_button(f, cancel_area, "Cancel", !yes_no_value && highlight_yes_no);
button_chunks[1],
"Save",
yes_no_value && highlight_yes_no,
);
draw_button(
f,
button_chunks[2],
"Cancel",
!yes_no_value && highlight_yes_no,
);
} else { } else {
loading(f, block, prompt_area, app.is_loading); loading(f, block, area, app.is_loading);
} }
} }
+10 -14
View File
@@ -36,24 +36,20 @@ impl DrawUi for IndexersUi {
false 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 route = *app.get_current_route();
let mut indexers_matchers = |active_radarr_block| match active_radarr_block { let mut indexers_matchers = |active_radarr_block| match active_radarr_block {
ActiveRadarrBlock::Indexers => draw_indexers(f, app, content_rect), ActiveRadarrBlock::Indexers => draw_indexers(f, app, area),
ActiveRadarrBlock::DeleteIndexerPrompt => draw_prompt_popup_over( ActiveRadarrBlock::DeleteIndexerPrompt => {
f, draw_prompt_popup_over(f, app, area, draw_indexers, draw_delete_indexer_prompt)
app, }
content_rect,
draw_indexers,
draw_delete_indexer_prompt,
),
_ => (), _ => (),
}; };
match route { match route {
_ if EditIndexerUi::accepts(route) => EditIndexerUi::draw(f, app, content_rect), _ if EditIndexerUi::accepts(route) => EditIndexerUi::draw(f, app, area),
_ if IndexerSettingsUi::accepts(route) => IndexerSettingsUi::draw(f, app, content_rect), _ if IndexerSettingsUi::accepts(route) => IndexerSettingsUi::draw(f, app, area),
_ if TestAllIndexersUi::accepts(route) => TestAllIndexersUi::draw(f, app, content_rect), _ if TestAllIndexersUi::accepts(route) => TestAllIndexersUi::draw(f, app, area),
Route::Radarr(active_radarr_block, _) if INDEXERS_BLOCKS.contains(&active_radarr_block) => { Route::Radarr(active_radarr_block, _) if INDEXERS_BLOCKS.contains(&active_radarr_block) => {
indexers_matchers(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( draw_prompt_box(
f, f,
prompt_area, area,
"Delete Indexer", "Delete Indexer",
format!( format!(
"Do you really want to delete this indexer: \n{}?", "Do you really want to delete this indexer: \n{}?",
@@ -7,7 +7,7 @@ use crate::ui::radarr_ui::indexers::draw_indexers;
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{borderless_block, get_width_from_percentage, title_block}; use crate::ui::utils::{borderless_block, get_width_from_percentage, title_block};
use crate::ui::{ 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::layout::{Constraint, Rect};
use ratatui::widgets::{Cell, Row}; use ratatui::widgets::{Cell, Row};
@@ -28,11 +28,11 @@ impl DrawUi for TestAllIndexersUi {
false 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( draw_large_popup_over(
f, f,
app, app,
content_rect, area,
draw_indexers, draw_indexers,
draw_test_all_indexers_test_results, 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 | {}", "<↑↓> scroll | {}",
build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES) 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( draw_table(
f, f,
+51 -81
View File
@@ -1,4 +1,4 @@
use ratatui::layout::{Alignment, Constraint, Rect}; use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::text::Text; use ratatui::text::Text;
use ratatui::widgets::{Cell, ListItem, Paragraph, Row}; use ratatui::widgets::{Cell, ListItem, Paragraph, Row};
use ratatui::Frame; 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::radarr_ui::library::draw_library;
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{ use crate::ui::utils::{
borderless_block, get_width_from_percentage, horizontal_chunks, layout_block, borderless_block, get_width_from_percentage, layout_block, layout_paragraph_borderless,
layout_paragraph_borderless, title_block_centered, vertical_chunks_with_margin, title_block_centered,
}; };
use crate::ui::{ use crate::ui::{
draw_button, draw_drop_down_menu_button, draw_drop_down_popup, draw_error_popup, draw_button, draw_drop_down_menu_button, draw_drop_down_popup, draw_error_popup,
@@ -40,7 +40,7 @@ impl DrawUi for AddMovieUi {
false 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() { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() {
let draw_add_movie_search_popup = let draw_add_movie_search_popup =
|f: &mut Frame<'_>, app: &mut App<'_>, area: Rect| match active_radarr_block { |f: &mut Frame<'_>, app: &mut App<'_>, area: Rect| match active_radarr_block {
@@ -80,21 +80,9 @@ impl DrawUi for AddMovieUi {
match active_radarr_block { match active_radarr_block {
_ if ADD_MOVIE_BLOCKS.contains(&active_radarr_block) => { _ if ADD_MOVIE_BLOCKS.contains(&active_radarr_block) => {
if context_option.is_some() { if context_option.is_some() {
draw_large_popup_over( draw_large_popup_over(f, app, area, draw_collections, draw_add_movie_search_popup)
f,
app,
content_rect,
draw_collections,
draw_add_movie_search_popup,
)
} else { } else {
draw_large_popup_over( draw_large_popup_over(f, app, area, draw_library, draw_add_movie_search_popup)
f,
app,
content_rect,
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() AddMovieSearchResult::default()
}; };
let chunks = vertical_chunks_with_margin( let [search_box_area, results_area, help_area] = Layout::vertical([
vec![
Constraint::Length(3), Constraint::Length(3),
Constraint::Min(0), Constraint::Fill(0),
Constraint::Length(3), Constraint::Length(3),
], ])
area, .margin(1)
1, .areas(area);
);
let block_content = &app.data.radarr_data.search.as_ref().unwrap().text; let block_content = &app.data.radarr_data.search.as_ref().unwrap().text;
let offset = *app let offset = *app
.data .data
@@ -137,7 +123,7 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
draw_text_box( draw_text_box(
f, f,
TextBoxProps { TextBoxProps {
text_box_area: chunks[0], text_box_area: search_box_area,
block_title: Some("Add Movie"), block_title: Some("Add Movie"),
block_content, block_content,
offset, offset,
@@ -146,16 +132,16 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
cursor_after_string: true, 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_text = Text::from(build_context_clue_string(&BARE_POPUP_CONTEXT_CLUES).help());
let help_paragraph = Paragraph::new(help_text) let help_paragraph = Paragraph::new(help_text)
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Center); .alignment(Alignment::Center);
f.render_widget(help_paragraph, chunks[2]); f.render_widget(help_paragraph, help_area);
} }
ActiveRadarrBlock::AddMovieEmptySearchResults => { 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!"); draw_error_popup(f, "No movies found matching your query!");
} }
ActiveRadarrBlock::AddMovieSearchResults 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) let help_paragraph = Paragraph::new(help_text)
.block(borderless_block()) .block(borderless_block())
.alignment(Alignment::Center); .alignment(Alignment::Center);
f.render_widget(help_paragraph, chunks[2]); f.render_widget(help_paragraph, help_area);
draw_table( draw_table(
f, f,
chunks[1], results_area,
layout_block(), layout_block(),
TableProps { TableProps {
content: None, content: None,
@@ -269,7 +255,7 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
draw_text_box( draw_text_box(
f, f,
TextBoxProps { TextBoxProps {
text_box_area: chunks[0], text_box_area: search_box_area,
block_title: Some("Add Movie"), block_title: Some("Add Movie"),
block_content, block_content,
offset, 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() { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
match active_radarr_block { match active_radarr_block {
ActiveRadarrBlock::AddMovieSelectMonitor => { ActiveRadarrBlock::AddMovieSelectMonitor => {
draw_drop_down_popup( draw_drop_down_popup(
f, f,
app, app,
prompt_area, area,
draw_confirmation_prompt, draw_confirmation_prompt,
draw_add_movie_select_monitor_popup, 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( draw_drop_down_popup(
f, f,
app, app,
prompt_area, area,
draw_confirmation_prompt, draw_confirmation_prompt,
draw_add_movie_select_minimum_availability_popup, 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( draw_drop_down_popup(
f, f,
app, app,
prompt_area, area,
draw_confirmation_prompt, draw_confirmation_prompt,
draw_add_movie_select_quality_profile_popup, 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( draw_drop_down_popup(
f, f,
app, app,
prompt_area, area,
draw_confirmation_prompt, draw_confirmation_prompt,
draw_add_movie_select_root_folder_popup, draw_add_movie_select_root_folder_popup,
); );
} }
ActiveRadarrBlock::AddMoviePrompt | ActiveRadarrBlock::AddMovieTagsInput => { 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() { let (movie_title, movie_overview) = if let Route::Radarr(_, Some(_)) = app.get_current_route() {
( (
&app &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_quality_profile = quality_profile_list.current_selection();
let selected_root_folder = root_folder_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( let [paragraph_area, root_folder_area, monitor_area, min_availability_area, quality_profile_area, tags_area, _, buttons_area] =
vec![ Layout::vertical([
Constraint::Length(6), Constraint::Length(6),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Min(0), Constraint::Fill(0),
Constraint::Length(3), Constraint::Length(3),
], ])
prompt_area, .margin(1)
1, .areas(area);
);
let prompt_paragraph = layout_paragraph_borderless(&prompt); 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( let [add_area, cancel_area] =
vec![Constraint::Percentage(50), Constraint::Percentage(50)], Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
chunks[7], .areas(buttons_area);
);
draw_drop_down_menu_button( draw_drop_down_menu_button(
f, f,
chunks[1], root_folder_area,
"Root Folder", "Root Folder",
&selected_root_folder.path, &selected_root_folder.path,
selected_block == &ActiveRadarrBlock::AddMovieSelectRootFolder, 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( draw_drop_down_menu_button(
f, f,
chunks[2], monitor_area,
"Monitor", "Monitor",
selected_monitor.to_display_str(), selected_monitor.to_display_str(),
selected_block == &ActiveRadarrBlock::AddMovieSelectMonitor, 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( draw_drop_down_menu_button(
f, f,
chunks[3], min_availability_area,
"Minimum Availability", "Minimum Availability",
selected_minimum_availability.to_display_str(), selected_minimum_availability.to_display_str(),
selected_block == &ActiveRadarrBlock::AddMovieSelectMinimumAvailability, selected_block == &ActiveRadarrBlock::AddMovieSelectMinimumAvailability,
); );
draw_drop_down_menu_button( draw_drop_down_menu_button(
f, f,
chunks[4], quality_profile_area,
"Quality Profile", "Quality Profile",
selected_quality_profile, selected_quality_profile,
selected_block == &ActiveRadarrBlock::AddMovieSelectQualityProfile, 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( draw_text_box_with_label(
f, f,
LabeledTextBoxProps { LabeledTextBoxProps {
area: chunks[5], area: tags_area,
label: "Tags", label: "Tags",
text: &tags.text, text: &tags.text,
offset: *tags.offset.borrow(), offset: *tags.offset.borrow(),
@@ -457,24 +441,14 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: R
); );
} }
draw_button( draw_button(f, add_area, "Add", yes_no_value && highlight_yes_no);
f, draw_button(f, cancel_area, "Cancel", !yes_no_value && highlight_yes_no);
horizontal_chunks[0],
"Add",
yes_no_value && highlight_yes_no,
);
draw_button(
f,
horizontal_chunks[1],
"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( draw_selectable_list(
f, f,
popup_area, area,
&mut app &mut app
.data .data
.radarr_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( fn draw_add_movie_select_minimum_availability_popup(
f: &mut Frame<'_>, f: &mut Frame<'_>,
app: &mut App<'_>, app: &mut App<'_>,
popup_area: Rect, area: Rect,
) { ) {
draw_selectable_list( draw_selectable_list(
f, f,
popup_area, area,
&mut app &mut app
.data .data
.radarr_data .radarr_data
@@ -505,14 +479,10 @@ fn draw_add_movie_select_minimum_availability_popup(
); );
} }
fn draw_add_movie_select_quality_profile_popup( fn draw_add_movie_select_quality_profile_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
f: &mut Frame<'_>,
app: &mut App<'_>,
popup_area: Rect,
) {
draw_selectable_list( draw_selectable_list(
f, f,
popup_area, area,
&mut app &mut app
.data .data
.radarr_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( draw_selectable_list(
f, f,
popup_area, area,
&mut app &mut app
.data .data
.radarr_data .radarr_data
+2 -2
View File
@@ -22,7 +22,7 @@ impl DrawUi for DeleteMovieUi {
false false
} }
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, content_rect: Rect) { fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
if matches!( if matches!(
*app.get_current_route(), *app.get_current_route(),
Route::Radarr(ActiveRadarrBlock::DeleteMoviePrompt, _) 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);
} }
} }
} }
+28 -45
View File
@@ -1,4 +1,5 @@
use ratatui::layout::{Constraint, Rect}; use ratatui::layout::{Constraint, Rect};
use ratatui::prelude::Layout;
use ratatui::widgets::ListItem; use ratatui::widgets::ListItem;
use ratatui::Frame; 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::draw_library;
use crate::ui::radarr_ui::library::movie_details_ui::MovieDetailsUi; use crate::ui::radarr_ui::library::movie_details_ui::MovieDetailsUi;
use crate::ui::utils::{ use crate::ui::utils::{layout_paragraph_borderless, title_block_centered};
horizontal_chunks, layout_paragraph_borderless, title_block_centered, vertical_chunks_with_margin,
};
use crate::ui::{ use crate::ui::{
draw_button, draw_checkbox_with_label, draw_drop_down_menu_button, draw_drop_down_popup, 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, draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over, draw_popup,
@@ -35,7 +34,7 @@ impl DrawUi for EditMovieUi {
false 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() { if let Route::Radarr(active_radarr_block, context_option) = *app.get_current_route() {
let draw_edit_movie_prompt = let draw_edit_movie_prompt =
|f: &mut Frame<'_>, app: &mut App<'_>, prompt_area: Rect| match active_radarr_block { |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 { if let Some(context) = context_option {
match context { match context {
ActiveRadarrBlock::Movies => { 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) => { _ if MOVIE_DETAILS_BLOCKS.contains(&context) => {
draw_large_popup_over_background_fn_with_ui::<MovieDetailsUi>( draw_large_popup_over_background_fn_with_ui::<MovieDetailsUi>(
f, f,
app, app,
content_rect, area,
draw_library, draw_library,
); );
draw_popup(f, app, draw_edit_movie_prompt, 60, 60); 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) = let (movie_title, movie_overview) =
if let Some(filtered_movies) = app.data.radarr_data.filtered_movies.as_ref() { 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_minimum_availability = minimum_availability_list.current_selection();
let selected_quality_profile = quality_profile_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( let [paragraph_area, monitored_area, min_availability_area, quality_profile_area, path_area, tags_area, _, buttons_area] =
vec![ Layout::vertical([
Constraint::Length(6), Constraint::Length(6),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(3), Constraint::Length(3),
Constraint::Min(0), Constraint::Fill(0),
Constraint::Length(3), Constraint::Length(3),
], ])
prompt_area, .margin(1)
1, .areas(area);
);
let prompt_paragraph = layout_paragraph_borderless(&movie_overview); 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( let [save_area, cancel_area] =
vec![Constraint::Percentage(50), Constraint::Percentage(50)], Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
chunks[7], .areas(buttons_area);
);
draw_checkbox_with_label( draw_checkbox_with_label(
f, f,
chunks[1], monitored_area,
"Monitored", "Monitored",
monitored.unwrap_or_default(), monitored.unwrap_or_default(),
selected_block == &ActiveRadarrBlock::EditMovieToggleMonitored, 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( draw_drop_down_menu_button(
f, f,
chunks[2], min_availability_area,
"Minimum Availability", "Minimum Availability",
selected_minimum_availability.to_display_str(), selected_minimum_availability.to_display_str(),
selected_block == &ActiveRadarrBlock::EditMovieSelectMinimumAvailability, selected_block == &ActiveRadarrBlock::EditMovieSelectMinimumAvailability,
); );
draw_drop_down_menu_button( draw_drop_down_menu_button(
f, f,
chunks[3], quality_profile_area,
"Quality Profile", "Quality Profile",
selected_quality_profile, selected_quality_profile,
selected_block == &ActiveRadarrBlock::EditMovieSelectQualityProfile, 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( draw_text_box_with_label(
f, f,
LabeledTextBoxProps { LabeledTextBoxProps {
area: chunks[4], area: path_area,
label: "Path", label: "Path",
text: &path.text, text: &path.text,
offset: *path.offset.borrow(), 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( draw_text_box_with_label(
f, f,
LabeledTextBoxProps { LabeledTextBoxProps {
area: chunks[5], area: tags_area,
label: "Tags", label: "Tags",
text: &tags.text, text: &tags.text,
offset: *tags.offset.borrow(), offset: *tags.offset.borrow(),
@@ -203,28 +200,18 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, pro
); );
} }
draw_button( draw_button(f, save_area, "Save", yes_no_value && highlight_yes_no);
f, draw_button(f, cancel_area, "Cancel", !yes_no_value && highlight_yes_no);
horizontal_chunks[0],
"Save",
yes_no_value && highlight_yes_no,
);
draw_button(
f,
horizontal_chunks[1],
"Cancel",
!yes_no_value && highlight_yes_no,
);
} }
fn draw_edit_movie_select_minimum_availability_popup( fn draw_edit_movie_select_minimum_availability_popup(
f: &mut Frame<'_>, f: &mut Frame<'_>,
app: &mut App<'_>, app: &mut App<'_>,
popup_area: Rect, area: Rect,
) { ) {
draw_selectable_list( draw_selectable_list(
f, f,
popup_area, area,
&mut app &mut app
.data .data
.radarr_data .radarr_data
@@ -236,14 +223,10 @@ fn draw_edit_movie_select_minimum_availability_popup(
); );
} }
fn draw_edit_movie_select_quality_profile_popup( fn draw_edit_movie_select_quality_profile_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
f: &mut Frame<'_>,
app: &mut App<'_>,
popup_area: Rect,
) {
draw_selectable_list( draw_selectable_list(
f, f,
popup_area, area,
&mut app &mut app
.data .data
.radarr_data .radarr_data
+19 -35
View File
@@ -42,62 +42,46 @@ impl DrawUi for LibraryUi {
false 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 route = *app.get_current_route();
let mut library_ui_matchers = |active_radarr_block: ActiveRadarrBlock| match active_radarr_block let mut library_ui_matchers = |active_radarr_block: ActiveRadarrBlock| match active_radarr_block
{ {
ActiveRadarrBlock::Movies => draw_library(f, app, content_rect), ActiveRadarrBlock::Movies => draw_library(f, app, area),
ActiveRadarrBlock::SearchMovie => draw_popup_over( ActiveRadarrBlock::SearchMovie => {
f, draw_popup_over(f, app, area, draw_library, draw_movie_search_box, 30, 13)
app, }
content_rect,
draw_library,
draw_movie_search_box,
30,
13,
),
ActiveRadarrBlock::SearchMovieError => draw_popup_over( ActiveRadarrBlock::SearchMovieError => draw_popup_over(
f, f,
app, app,
content_rect, area,
draw_library, draw_library,
draw_search_movie_error_box, draw_search_movie_error_box,
30, 30,
8, 8,
), ),
ActiveRadarrBlock::FilterMovies => draw_popup_over( ActiveRadarrBlock::FilterMovies => {
f, draw_popup_over(f, app, area, draw_library, draw_filter_movies_box, 30, 13)
app, }
content_rect,
draw_library,
draw_filter_movies_box,
30,
13,
),
ActiveRadarrBlock::FilterMoviesError => draw_popup_over( ActiveRadarrBlock::FilterMoviesError => draw_popup_over(
f, f,
app, app,
content_rect, area,
draw_library, draw_library,
draw_filter_movies_error_box, draw_filter_movies_error_box,
30, 30,
8, 8,
), ),
ActiveRadarrBlock::UpdateAllMoviesPrompt => draw_prompt_popup_over( ActiveRadarrBlock::UpdateAllMoviesPrompt => {
f, draw_prompt_popup_over(f, app, area, draw_library, draw_update_all_movies_prompt)
app, }
content_rect,
draw_library,
draw_update_all_movies_prompt,
),
_ => (), _ => (),
}; };
match route { match route {
_ if MovieDetailsUi::accepts(route) => MovieDetailsUi::draw(f, app, content_rect), _ if MovieDetailsUi::accepts(route) => MovieDetailsUi::draw(f, app, area),
_ if AddMovieUi::accepts(route) => AddMovieUi::draw(f, app, content_rect), _ if AddMovieUi::accepts(route) => AddMovieUi::draw(f, app, area),
_ if EditMovieUi::accepts(route) => EditMovieUi::draw(f, app, content_rect), _ if EditMovieUi::accepts(route) => EditMovieUi::draw(f, app, area),
_ if DeleteMovieUi::accepts(route) => DeleteMovieUi::draw(f, app, content_rect), _ if DeleteMovieUi::accepts(route) => DeleteMovieUi::draw(f, app, area),
Route::Radarr(active_radarr_block, _) if LIBRARY_BLOCKS.contains(&active_radarr_block) => { Route::Radarr(active_radarr_block, _) if LIBRARY_BLOCKS.contains(&active_radarr_block) => {
library_ui_matchers(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( draw_prompt_box(
f, f,
prompt_area, area,
"Update All Movies", "Update All Movies",
"Do you want to update info and scan your disks for all of your movies?", "Do you want to update info and scan your disks for all of your movies?",
app.data.radarr_data.prompt_confirm, app.data.radarr_data.prompt_confirm,
+36 -44
View File
@@ -1,6 +1,6 @@
use std::iter; use std::iter;
use ratatui::layout::{Alignment, Constraint, Rect}; use ratatui::layout::{Alignment, Constraint, Layout, Rect};
use ratatui::style::{Style, Stylize}; use ratatui::style::{Style, Stylize};
use ratatui::text::{Line, Span, Text}; use ratatui::text::{Line, Span, Text};
use ratatui::widgets::{Cell, ListItem, Paragraph, Row, Wrap}; 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::styles::ManagarrStyle;
use crate::ui::utils::{ use crate::ui::utils::{
borderless_block, get_width_from_percentage, layout_block_bottom_border, layout_block_top_border, borderless_block, get_width_from_percentage, layout_block_bottom_border, layout_block_top_border,
vertical_chunks,
}; };
use crate::ui::{ use crate::ui::{
draw_drop_down_popup, draw_large_popup_over, draw_prompt_box, draw_prompt_box_with_content, 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 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() { 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 draw_movie_info_popup = |f: &mut Frame<'_>, app: &mut App<'_>, popup_area: Rect| {
let (content_area, _) = draw_tabs( let content_area = draw_tabs(
f, f,
popup_area, popup_area,
"Movie Info", "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( draw_prompt_box(
f, f,
prompt_area, area,
"Automatic Movie Search", "Automatic Movie Search",
format!( format!(
"Do you want to trigger an automatic search of your indexers for the movie: {}?", "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( draw_prompt_box(
f, f,
prompt_area, area,
"Update and Scan", "Update and Scan",
format!( format!(
"Do you want to trigger an update and disk scan for the movie: {}?", "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() { match app.data.radarr_data.movie_details_modal.as_ref() {
Some(movie_details_modal) Some(movie_details_modal)
if !movie_details_modal.file_details.is_empty() && !app.is_loading => 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 file_info = movie_details_modal.file_details.to_owned();
let audio_details = movie_details_modal.audio_details.to_owned(); let audio_details = movie_details_modal.audio_details.to_owned();
let video_details = movie_details_modal.video_details.to_owned(); let video_details = movie_details_modal.video_details.to_owned();
let chunks = vertical_chunks( let [file_details_title_area, file_details_area, audio_details_title_area, audio_details_area, video_details_title_area, video_details_area] =
vec![ Layout::vertical([
Constraint::Length(2), Constraint::Length(2),
Constraint::Length(5), Constraint::Length(5),
Constraint::Length(1), Constraint::Length(1),
Constraint::Length(6), Constraint::Length(6),
Constraint::Length(1), Constraint::Length(1),
Constraint::Length(7), Constraint::Length(7),
], ])
content_area, .areas(area);
);
let file_details_title_paragraph = let file_details_title_paragraph =
Paragraph::new("File Details".bold()).block(layout_block_top_border()); 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()) .block(borderless_block())
.wrap(Wrap { trim: false }); .wrap(Wrap { trim: false });
f.render_widget(file_details_title_paragraph, chunks[0]); f.render_widget(file_details_title_paragraph, file_details_title_area);
f.render_widget(file_details_paragraph, chunks[1]); f.render_widget(file_details_paragraph, file_details_area);
f.render_widget(audio_details_title_paragraph, chunks[2]); f.render_widget(audio_details_title_paragraph, audio_details_title_area);
f.render_widget(audio_details_paragraph, chunks[3]); f.render_widget(audio_details_paragraph, audio_details_area);
f.render_widget(video_details_title_paragraph, chunks[4]); f.render_widget(video_details_title_paragraph, video_details_title_area);
f.render_widget(video_details_paragraph, chunks[5]); 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(); let block = layout_block_top_border();
match app.data.radarr_data.movie_details_modal.as_ref() { 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 }) .wrap(Wrap { trim: false })
.scroll((movie_details.offset, 0)); .scroll((movie_details.offset, 0));
f.render_widget(paragraph, content_area); f.render_widget(paragraph, area);
} }
_ => loading( _ => loading(
f, f,
block, block,
content_area, area,
app.is_loading || app.data.radarr_data.movie_details_modal.is_none(), 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() { 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() { let current_selection = if movie_details_modal.movie_history.items.is_empty() {
MovieHistoryItem::default() MovieHistoryItem::default()
@@ -254,7 +252,7 @@ fn draw_movie_history(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect)
draw_table( draw_table(
f, f,
content_area, area,
layout_block_top_border(), layout_block_top_border(),
TableProps { TableProps {
content: Some(&mut movie_details_modal.movie_history), 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;
movie_history_item.source_title.scroll_left_or_reset( 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, current_selection == *movie_history_item,
app.tick_count % app.ticks_until_scroll == 0, 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( draw_table(
f, f,
content_area, area,
layout_block_top_border(), layout_block_top_border(),
TableProps { TableProps {
content: Some( 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( draw_table(
f, f,
content_area, area,
layout_block_top_border(), layout_block_top_border(),
TableProps { TableProps {
content: Some( 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) = let (current_selection, is_empty, sort_ascending) =
match app.data.radarr_data.movie_details_modal.as_ref() { match app.data.radarr_data.movie_details_modal.as_ref() {
Some(movie_details_modal) if !movie_details_modal.movie_releases.items.is_empty() => ( 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( draw_table(
f, f,
content_area, area,
layout_block_top_border(), layout_block_top_border(),
TableProps { TableProps {
content: Some( content: Some(
@@ -494,7 +492,7 @@ fn draw_movie_releases(f: &mut Frame<'_>, app: &mut App<'_>, content_area: Rect)
} = release; } = release;
let age = format!("{age} days"); let age = format!("{age} days");
title.scroll_left_or_reset( title.scroll_left_or_reset(
get_width_from_percentage(content_area, 30), get_width_from_percentage(area, 30),
current_selection == *release current_selection == *release
&& current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(), && current_route != ActiveRadarrBlock::ManualSearchConfirmPrompt.into(),
app.tick_count % app.ticks_until_scroll == 0, 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 let current_selection = app
.data .data
.radarr_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( draw_prompt_box_with_content(
f, f,
prompt_area, area,
title, title,
&prompt, &prompt,
Some(content_paragraph), Some(content_paragraph),
app.data.radarr_data.prompt_confirm, app.data.radarr_data.prompt_confirm,
); );
} else { } else {
draw_prompt_box( draw_prompt_box(f, area, title, &prompt, app.data.radarr_data.prompt_confirm);
f,
prompt_area,
title,
&prompt,
app.data.radarr_data.prompt_confirm,
);
} }
} }
+31 -26
View File
@@ -23,7 +23,6 @@ use crate::ui::radarr_ui::system::SystemUi;
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{ use crate::ui::utils::{
borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block, borderless_block, layout_block, line_gauge_with_label, line_gauge_with_title, title_block,
vertical_chunks_with_margin,
}; };
use crate::ui::DrawUi; use crate::ui::DrawUi;
use crate::utils::convert_to_gb; use crate::utils::convert_to_gb;
@@ -48,29 +47,30 @@ impl DrawUi for RadarrUi {
} }
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { 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(); let route = *app.get_current_route();
match route { match route {
_ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_rect), _ if LibraryUi::accepts(route) => LibraryUi::draw(f, app, content_area),
_ if CollectionsUi::accepts(route) => CollectionsUi::draw(f, app, content_rect), _ if CollectionsUi::accepts(route) => CollectionsUi::draw(f, app, content_area),
_ if DownloadsUi::accepts(route) => DownloadsUi::draw(f, app, content_rect), _ if DownloadsUi::accepts(route) => DownloadsUi::draw(f, app, content_area),
_ if IndexersUi::accepts(route) => IndexersUi::draw(f, app, content_rect), _ if IndexersUi::accepts(route) => IndexersUi::draw(f, app, content_area),
_ if RootFoldersUi::accepts(route) => RootFoldersUi::draw(f, app, content_rect), _ if RootFoldersUi::accepts(route) => RootFoldersUi::draw(f, app, content_area),
_ if SystemUi::accepts(route) => SystemUi::draw(f, app, content_rect), _ if SystemUi::accepts(route) => SystemUi::draw(f, app, content_area),
_ => (), _ => (),
} }
} }
fn draw_context_row(f: &mut Frame<'_>, app: &App<'_>, area: Rect) { 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] = let [stats_area, downloads_area] =
Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]).areas(main); Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]).areas(main_area);
draw_stats_context(f, app, stats); draw_stats_context(f, app, stats_area);
draw_downloads_context(f, app, downloads); draw_downloads_context(f, app, downloads_area);
draw_radarr_logo(f, logo); draw_radarr_logo(f, logo_area);
} }
} }
@@ -98,7 +98,7 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
.collect(), .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!( let version_paragraph = Paragraph::new(Text::from(format!(
"Radarr Version: {}", "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 storage = Paragraph::new(Text::from("Storage:")).block(borderless_block().bold());
let folders = Paragraph::new(Text::from("Root Folders:")).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(version_paragraph, stat_item_areas[0]);
f.render_widget(uptime_paragraph, chunks[1]); f.render_widget(uptime_paragraph, stat_item_areas[1]);
f.render_widget(storage, chunks[2]); f.render_widget(storage, stat_item_areas[2]);
for i in 0..disk_space_vec.len() { for i in 0..disk_space_vec.len() {
let DiskSpace { 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); 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() { for i in 0..root_folders.items.len() {
let RootFolder { let RootFolder {
@@ -157,7 +157,10 @@ fn draw_stats_context(f: &mut Frame<'_>, app: &App<'_>, area: Rect) {
.block(borderless_block()) .block(borderless_block())
.default(); .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 { } else {
loading(f, block, area, app.is_loading); 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() { if !downloads_vec.is_empty() {
f.render_widget(block, area); f.render_widget(block, area);
let constraints = iter::repeat(Constraint::Length(2)) let download_item_areas = Layout::vertical(
iter::repeat(Constraint::Length(2))
.take(downloads_vec.len()) .take(downloads_vec.len())
.collect::<Vec<Constraint>>(); .collect::<Vec<Constraint>>(),
)
let chunks = vertical_chunks_with_margin(constraints, area, 1); .margin(1)
.split(area);
for i in 0..downloads_vec.len() { for i in 0..downloads_vec.len() {
let DownloadRecord { 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 percent = 1f64 - (*sizeleft as f64 / *size as f64);
let download_gauge = line_gauge_with_title(title, percent); 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 { } else {
loading(f, block, area, app.is_loading); loading(f, block, area, app.is_loading);
+10 -9
View File
@@ -6,12 +6,12 @@ use crate::app::App;
use crate::models::radarr_models::RootFolder; use crate::models::radarr_models::RootFolder;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS}; use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, ROOT_FOLDERS_BLOCKS};
use crate::models::Route; 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::{ use crate::ui::{
draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, draw_input_box_popup, draw_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table,
DrawUi, TableProps, DrawUi, TableProps,
}; };
use crate::ui::styles::ManagarrStyle;
use crate::utils::convert_to_gb; use crate::utils::convert_to_gb;
#[cfg(test)] #[cfg(test)]
@@ -29,14 +29,14 @@ impl DrawUi for RootFoldersUi {
false 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() { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
match active_radarr_block { 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( ActiveRadarrBlock::AddRootFolderPrompt => draw_popup_over(
f, f,
app, app,
content_rect, area,
draw_root_folders, draw_root_folders,
draw_add_root_folder_prompt_box, draw_add_root_folder_prompt_box,
30, 30,
@@ -45,7 +45,7 @@ impl DrawUi for RootFoldersUi {
ActiveRadarrBlock::DeleteRootFolderPrompt => draw_prompt_popup_over( ActiveRadarrBlock::DeleteRootFolderPrompt => draw_prompt_popup_over(
f, f,
app, app,
content_rect, area,
draw_root_folders, draw_root_folders,
draw_delete_root_folder_prompt, draw_delete_root_folder_prompt,
), ),
@@ -95,7 +95,8 @@ fn draw_root_folders(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.len() .len()
.to_string(), .to_string(),
), ),
]).primary() ])
.primary()
}, },
app.is_loading, app.is_loading,
true, 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( draw_prompt_box(
f, f,
prompt_area, area,
"Delete Root Folder", "Delete Root Folder",
format!( format!(
"Do you really want to delete this root folder: \n{}?", "Do you really want to delete this root folder: \n{}?",
+14 -22
View File
@@ -1,7 +1,7 @@
use std::ops::Sub; use std::ops::Sub;
use chrono::Utc; use chrono::Utc;
use ratatui::layout::Alignment; use ratatui::layout::{Alignment, Layout};
use ratatui::text::{Span, Text}; use ratatui::text::{Span, Text};
use ratatui::widgets::{Cell, Paragraph, Row}; use ratatui::widgets::{Cell, Paragraph, Row};
use ratatui::{ use ratatui::{
@@ -20,11 +20,7 @@ use crate::ui::utils::layout_block_top_border;
use crate::ui::{draw_table, ListProps, TableProps}; use crate::ui::{draw_table, ListProps, TableProps};
use crate::{ use crate::{
models::Route, models::Route,
ui::{ ui::{draw_list_box, utils::title_block, DrawUi},
draw_list_box,
utils::{horizontal_chunks, title_block, vertical_chunks},
DrawUi,
},
}; };
mod system_details_ui; mod system_details_ui;
@@ -60,13 +56,13 @@ impl DrawUi for SystemUi {
false 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 route = *app.get_current_route();
match 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, _)) => { _ 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) { pub(super) fn draw_system_ui_layout(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let vertical_chunks = vertical_chunks( let [activities_area, logs_area, help_area] = Layout::vertical([
vec![
Constraint::Ratio(1, 2), Constraint::Ratio(1, 2),
Constraint::Ratio(1, 2), Constraint::Ratio(1, 2),
Constraint::Min(2), Constraint::Min(2),
], ])
area, .areas(area);
);
let horizontal_chunks = horizontal_chunks( let [tasks_area, events_area] =
vec![Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)], Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]).areas(activities_area);
vertical_chunks[0],
);
draw_tasks(f, app, horizontal_chunks[0]); draw_tasks(f, app, tasks_area);
draw_queued_events(f, app, horizontal_chunks[1]); draw_queued_events(f, app, events_area);
draw_logs(f, app, vertical_chunks[1]); draw_logs(f, app, logs_area);
draw_help(f, app, vertical_chunks[2]); draw_help(f, app, help_area);
} }
fn draw_tasks(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) { fn draw_tasks(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
+18 -31
View File
@@ -16,8 +16,9 @@ use crate::ui::radarr_ui::system::{
use crate::ui::styles::ManagarrStyle; use crate::ui::styles::ManagarrStyle;
use crate::ui::utils::{borderless_block, title_block}; use crate::ui::utils::{borderless_block, title_block};
use crate::ui::{ use crate::ui::{
draw_help_and_get_content_rect, draw_large_popup_over, draw_list_box, draw_medium_popup_over, draw_help_footer_and_get_content_area, draw_large_popup_over, draw_list_box,
draw_prompt_box, draw_prompt_popup_over, draw_table, loading, DrawUi, ListProps, TableProps, draw_medium_popup_over, draw_prompt_box, draw_prompt_popup_over, draw_table, loading, DrawUi,
ListProps, TableProps,
}; };
#[cfg(test)] #[cfg(test)]
@@ -35,35 +36,21 @@ impl DrawUi for SystemDetailsUi {
false 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() { if let Route::Radarr(active_radarr_block, _) = *app.get_current_route() {
match active_radarr_block { match active_radarr_block {
ActiveRadarrBlock::SystemLogs => { 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 => { ActiveRadarrBlock::SystemTasks | ActiveRadarrBlock::SystemTaskStartConfirmPrompt => {
draw_large_popup_over( draw_large_popup_over(f, app, area, draw_system_ui_layout, draw_tasks_popup)
f, }
app, ActiveRadarrBlock::SystemQueuedEvents => {
content_rect, draw_medium_popup_over(f, app, area, draw_system_ui_layout, draw_queued_events)
draw_system_ui_layout, }
draw_tasks_popup, 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| { let tasks_popup_table = |f: &mut Frame<'_>, app: &mut App<'_>, area: Rect| {
f.render_widget(title_block("Tasks"), area); 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, f,
area, area,
Some(build_context_clue_string(&SYSTEM_TASKS_CONTEXT_CLUES)), 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( draw_prompt_box(
f, f,
prompt_area, area,
"Start Task", "Start Task",
format!( format!(
"Do you want to manually start this task: {}?", "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) { fn draw_updates_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
f.render_widget(title_block("Updates"), area); 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, f,
area, area,
Some(format!( Some(format!(
@@ -174,8 +161,8 @@ fn draw_updates_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
.block(block) .block(block)
.scroll((app.data.radarr_data.updates.offset, 0)); .scroll((app.data.radarr_data.updates.offset, 0));
f.render_widget(updates_paragraph, content_rect); f.render_widget(updates_paragraph, content_area);
} else { } else {
loading(f, block, content_rect, app.is_loading); loading(f, block, content_area, app.is_loading);
} }
} }
+10 -56
View File
@@ -1,10 +1,9 @@
use crate::ui::styles::ManagarrStyle; 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::style::{Color, Style, Stylize};
use ratatui::text::{Line, Span, Text}; use ratatui::text::{Line, Span, Text};
use ratatui::widgets::{Block, BorderType, Borders, LineGauge, Paragraph, Wrap}; use ratatui::widgets::{Block, BorderType, Borders, LineGauge, Paragraph, Wrap};
use ratatui::{symbols, Frame}; use ratatui::{symbols, Frame};
use std::rc::Rc;
pub const COLOR_TEAL: Color = Color::Rgb(35, 50, 55); 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"] #[path = "utils_tests.rs"]
mod utils_tests; mod utils_tests;
pub fn horizontal_chunks(constraints: Vec<Constraint>, area: Rect) -> Rc<[Rect]> {
layout_with_constraints(constraints)
.direction(Direction::Horizontal)
.split(area)
}
pub fn horizontal_chunks_with_margin(
constraints: Vec<Constraint>,
area: Rect,
margin: u16,
) -> Rc<[Rect]> {
layout_with_constraints(constraints)
.direction(Direction::Horizontal)
.margin(margin)
.split(area)
}
pub fn vertical_chunks(constraints: Vec<Constraint>, area: Rect) -> Rc<[Rect]> {
layout_with_constraints(constraints)
.direction(Direction::Vertical)
.split(area)
}
pub fn vertical_chunks_with_margin(
constraints: Vec<Constraint>,
area: Rect,
margin: u16,
) -> Rc<[Rect]> {
layout_with_constraints(constraints)
.direction(Direction::Vertical)
.margin(margin)
.split(area)
}
fn layout_with_constraints(constraints: Vec<Constraint>) -> Layout {
Layout::default().constraints(<Vec<Constraint> as AsRef<[Constraint]>>::as_ref(
&constraints,
))
}
pub fn background_block<'a>() -> Block<'a> { pub fn background_block<'a>() -> Block<'a> {
Block::new().white().bg(COLOR_TEAL) 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 { pub fn centered_rect(percent_x: u16, percent_y: u16, area: Rect) -> Rect {
let popup_layout = vertical_chunks( let [_, vertical_area, _] = Layout::vertical([
vec![
Constraint::Percentage((100 - percent_y) / 2), Constraint::Percentage((100 - percent_y) / 2),
Constraint::Percentage(percent_y), Constraint::Percentage(percent_y),
Constraint::Percentage((100 - percent_y) / 2), Constraint::Percentage((100 - percent_y) / 2),
], ])
r, .areas(area);
);
Layout::default() let [_, horizontal_layout, _] = Layout::horizontal([
.direction(Direction::Horizontal)
.constraints(
[
Constraint::Percentage((100 - percent_x) / 2), Constraint::Percentage((100 - percent_x) / 2),
Constraint::Percentage(percent_x), Constraint::Percentage(percent_x),
Constraint::Percentage((100 - percent_x) / 2), Constraint::Percentage((100 - percent_x) / 2),
] ])
.as_ref(), .areas(vertical_area);
)
.split(popup_layout[1])[1] horizontal_layout
} }
pub fn line_gauge_with_title(title: &str, ratio: f64) -> LineGauge<'_> { pub fn line_gauge_with_title(title: &str, ratio: f64) -> LineGauge<'_> {
+5 -100
View File
@@ -1,113 +1,18 @@
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use pretty_assertions::assert_eq; 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::style::{Color, Modifier, Style};
use ratatui::text::Span; use ratatui::text::Span;
use ratatui::widgets::{Block, BorderType, Borders}; use ratatui::widgets::{Block, BorderType, Borders};
use crate::ui::utils::{ use crate::ui::utils::{
borderless_block, centered_rect, get_width_from_percentage, horizontal_chunks, borderless_block, centered_rect, get_width_from_percentage, layout_block,
horizontal_chunks_with_margin, layout_block, layout_block_bottom_border, layout_block_bottom_border, layout_block_top_border, layout_block_top_border_with_title,
layout_block_top_border, layout_block_top_border_with_title, layout_block_with_title, layout_block_with_title, logo_block, style_block_highlight, title_block, title_block_centered,
layout_with_constraints, logo_block, style_block_highlight, title_block, title_block_centered, title_style,
title_style, vertical_chunks, vertical_chunks_with_margin,
}; };
#[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] #[test]
fn test_layout_block() { fn test_layout_block() {
assert_eq!( assert_eq!(