Began the Great Widget Refactor of 2024 and introduced custom widgets for buttons, checkboxes, and input boxes. Up next: loading and table widgets
This commit is contained in:
+32
-195
@@ -1,7 +1,7 @@
|
|||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
use ratatui::layout::{Alignment, Constraint, Flex, Layout, Rect};
|
use ratatui::layout::{Alignment, Constraint, Flex, Layout, Rect};
|
||||||
use ratatui::style::{Modifier, Style, Stylize};
|
use ratatui::style::{Style, Stylize};
|
||||||
use ratatui::text::{Line, Text};
|
use ratatui::text::{Line, Text};
|
||||||
use ratatui::widgets::Paragraph;
|
use ratatui::widgets::Paragraph;
|
||||||
use ratatui::widgets::Row;
|
use ratatui::widgets::Row;
|
||||||
@@ -17,13 +17,16 @@ 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, layout_block, layout_block_top_border,
|
background_block, borderless_block, centered_rect, layout_block, layout_block_top_border,
|
||||||
layout_button_paragraph, layout_button_paragraph_borderless, layout_paragraph_borderless,
|
layout_paragraph_borderless, logo_block, title_block, title_block_centered,
|
||||||
logo_block, show_cursor, style_block_highlight, title_block, title_block_centered,
|
|
||||||
};
|
};
|
||||||
|
use crate::ui::widgets::button::Button;
|
||||||
|
use crate::ui::widgets::checkbox::Checkbox;
|
||||||
|
use crate::ui::widgets::input_box::InputBox;
|
||||||
|
|
||||||
mod radarr_ui;
|
mod radarr_ui;
|
||||||
mod styles;
|
mod styles;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
mod widgets;
|
||||||
|
|
||||||
static HIGHLIGHT_SYMBOL: &str = "=> ";
|
static HIGHLIGHT_SYMBOL: &str = "=> ";
|
||||||
|
|
||||||
@@ -428,8 +431,11 @@ pub fn draw_prompt_box_with_content(
|
|||||||
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
.areas(buttons_area);
|
.areas(buttons_area);
|
||||||
|
|
||||||
draw_button(f, yes_area, "Yes", yes_no_value);
|
let yes_button = Button::new().title("Yes").selected(yes_no_value);
|
||||||
draw_button(f, no_area, "No", !yes_no_value);
|
let no_button = Button::new().title("No").selected(!yes_no_value);
|
||||||
|
|
||||||
|
f.render_widget(yes_button, yes_area);
|
||||||
|
f.render_widget(no_button, no_area);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_prompt_box_with_checkboxes(
|
pub fn draw_prompt_box_with_checkboxes(
|
||||||
@@ -441,115 +447,41 @@ pub fn draw_prompt_box_with_checkboxes(
|
|||||||
highlight_yes_no: bool,
|
highlight_yes_no: bool,
|
||||||
yes_no_value: bool,
|
yes_no_value: bool,
|
||||||
) {
|
) {
|
||||||
f.render_widget(title_block_centered(title), area);
|
|
||||||
let mut constraints = vec![
|
let mut constraints = vec![
|
||||||
Constraint::Length(4),
|
Constraint::Length(4),
|
||||||
Constraint::Fill(0),
|
Constraint::Fill(0),
|
||||||
Constraint::Length(3),
|
Constraint::Length(3),
|
||||||
];
|
];
|
||||||
|
|
||||||
constraints.splice(
|
constraints.splice(
|
||||||
1..1,
|
1..1,
|
||||||
iter::repeat(Constraint::Length(3)).take(checkboxes.len()),
|
iter::repeat(Constraint::Length(3)).take(checkboxes.len()),
|
||||||
);
|
);
|
||||||
|
|
||||||
let chunks = Layout::vertical(constraints).margin(1).split(area);
|
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]);
|
|
||||||
|
|
||||||
for i in 0..checkboxes.len() {
|
for i in 0..checkboxes.len() {
|
||||||
let (label, is_checked, is_selected) = checkboxes[i];
|
let (label, is_checked, is_highlighted) = checkboxes[i];
|
||||||
draw_checkbox_with_label(f, chunks[i + 1], label, is_checked, is_selected);
|
let checkbox = Checkbox::new(label)
|
||||||
|
.checked(is_checked)
|
||||||
|
.highlighted(is_highlighted);
|
||||||
|
f.render_widget(checkbox, chunks[i + 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let [yes_area, no_area] =
|
let [yes_area, no_area] =
|
||||||
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
.areas(chunks[checkboxes.len() + 2]);
|
.areas(chunks[checkboxes.len() + 2]);
|
||||||
|
|
||||||
draw_button(f, yes_area, "Yes", highlight_yes_no && yes_no_value);
|
let yes_button = Button::new()
|
||||||
draw_button(f, no_area, "No", highlight_yes_no && !yes_no_value);
|
.title("Yes")
|
||||||
}
|
.selected(yes_no_value && highlight_yes_no);
|
||||||
|
let no_button = Button::new()
|
||||||
|
.title("No")
|
||||||
|
.selected(!yes_no_value && highlight_yes_no);
|
||||||
|
|
||||||
pub fn draw_checkbox(f: &mut Frame<'_>, area: Rect, is_checked: bool, is_selected: bool) {
|
f.render_widget(title_block_centered(title), area);
|
||||||
let check = if is_checked { "✔" } else { "" };
|
f.render_widget(prompt_paragraph, chunks[0]);
|
||||||
let label_paragraph = Paragraph::new(Text::from(check))
|
f.render_widget(yes_button, yes_area);
|
||||||
.block(layout_block())
|
f.render_widget(no_button, no_area);
|
||||||
.alignment(Alignment::Center)
|
|
||||||
.style(style_block_highlight(is_selected).add_modifier(Modifier::BOLD));
|
|
||||||
let checkbox_area = Rect { width: 5, ..area };
|
|
||||||
|
|
||||||
f.render_widget(label_paragraph, checkbox_area);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_checkbox_with_label(
|
|
||||||
f: &mut Frame<'_>,
|
|
||||||
area: Rect,
|
|
||||||
label: &str,
|
|
||||||
is_checked: bool,
|
|
||||||
is_selected: bool,
|
|
||||||
) {
|
|
||||||
let [label_area, checkbox_area] =
|
|
||||||
Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area);
|
|
||||||
|
|
||||||
let label_paragraph = Paragraph::new(Text::from(format!("\n{label}: ")))
|
|
||||||
.block(borderless_block())
|
|
||||||
.alignment(Alignment::Right)
|
|
||||||
.primary();
|
|
||||||
|
|
||||||
f.render_widget(label_paragraph, label_area);
|
|
||||||
|
|
||||||
draw_checkbox(f, checkbox_area, is_checked, is_selected);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_button(f: &mut Frame<'_>, area: Rect, label: &str, is_selected: bool) {
|
|
||||||
let label_paragraph = layout_button_paragraph(is_selected, label, Alignment::Center);
|
|
||||||
|
|
||||||
f.render_widget(label_paragraph, area);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_button_with_icon(
|
|
||||||
f: &mut Frame<'_>,
|
|
||||||
area: Rect,
|
|
||||||
label: &str,
|
|
||||||
icon: &str,
|
|
||||||
is_selected: bool,
|
|
||||||
) {
|
|
||||||
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 [label_area, icon_area] =
|
|
||||||
Layout::horizontal([Constraint::Percentage(25), Constraint::Percentage(25)])
|
|
||||||
.flex(Flex::SpaceBetween)
|
|
||||||
.margin(1)
|
|
||||||
.areas(area);
|
|
||||||
|
|
||||||
f.render_widget(
|
|
||||||
layout_block().style(style_block_highlight(is_selected)),
|
|
||||||
area,
|
|
||||||
);
|
|
||||||
f.render_widget(label_paragraph, label_area);
|
|
||||||
f.render_widget(icon_paragraph, icon_area);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_drop_down_menu_button(
|
|
||||||
f: &mut Frame<'_>,
|
|
||||||
area: Rect,
|
|
||||||
description: &str,
|
|
||||||
selection: &str,
|
|
||||||
is_selected: bool,
|
|
||||||
) {
|
|
||||||
let [label_area, button_area] =
|
|
||||||
Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area);
|
|
||||||
|
|
||||||
let description_paragraph = Paragraph::new(Text::from(format!("\n{description}: ")))
|
|
||||||
.block(borderless_block())
|
|
||||||
.alignment(Alignment::Right)
|
|
||||||
.primary();
|
|
||||||
|
|
||||||
f.render_widget(description_paragraph, label_area);
|
|
||||||
|
|
||||||
draw_button_with_icon(f, button_area, selection, "▼ ", is_selected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_selectable_list<'a, T>(
|
pub fn draw_selectable_list<'a, T>(
|
||||||
@@ -625,95 +557,6 @@ fn draw_help_footer_and_get_content_area(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TextBoxProps<'a> {
|
|
||||||
pub text_box_area: Rect,
|
|
||||||
pub block_title: Option<&'a str>,
|
|
||||||
pub block_content: &'a str,
|
|
||||||
pub offset: usize,
|
|
||||||
pub should_show_cursor: bool,
|
|
||||||
pub is_selected: bool,
|
|
||||||
pub cursor_after_string: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_text_box(f: &mut Frame<'_>, text_box_props: TextBoxProps<'_>) {
|
|
||||||
let TextBoxProps {
|
|
||||||
text_box_area,
|
|
||||||
block_title,
|
|
||||||
block_content,
|
|
||||||
offset,
|
|
||||||
should_show_cursor,
|
|
||||||
is_selected,
|
|
||||||
cursor_after_string,
|
|
||||||
} = text_box_props;
|
|
||||||
let (block, style) = if let Some(title) = block_title {
|
|
||||||
(title_block_centered(title), Style::new().default())
|
|
||||||
} else {
|
|
||||||
(
|
|
||||||
layout_block(),
|
|
||||||
if should_show_cursor {
|
|
||||||
Style::new().default()
|
|
||||||
} else {
|
|
||||||
style_block_highlight(is_selected)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
};
|
|
||||||
let paragraph = Paragraph::new(Text::from(block_content))
|
|
||||||
.style(style)
|
|
||||||
.block(block);
|
|
||||||
f.render_widget(paragraph, text_box_area);
|
|
||||||
|
|
||||||
if should_show_cursor {
|
|
||||||
show_cursor(f, text_box_area, offset, block_content, cursor_after_string);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct LabeledTextBoxProps<'a> {
|
|
||||||
pub area: Rect,
|
|
||||||
pub label: &'a str,
|
|
||||||
pub text: &'a str,
|
|
||||||
pub offset: usize,
|
|
||||||
pub is_selected: bool,
|
|
||||||
pub should_show_cursor: bool,
|
|
||||||
pub cursor_after_string: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_text_box_with_label(
|
|
||||||
f: &mut Frame<'_>,
|
|
||||||
labeled_text_box_props: LabeledTextBoxProps<'_>,
|
|
||||||
) {
|
|
||||||
let LabeledTextBoxProps {
|
|
||||||
area,
|
|
||||||
label,
|
|
||||||
text,
|
|
||||||
offset,
|
|
||||||
is_selected,
|
|
||||||
should_show_cursor,
|
|
||||||
cursor_after_string,
|
|
||||||
} = labeled_text_box_props;
|
|
||||||
let [label_area, text_box_area] =
|
|
||||||
Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area);
|
|
||||||
|
|
||||||
let label_paragraph = Paragraph::new(Text::from(format!("\n{label}: ")))
|
|
||||||
.block(borderless_block())
|
|
||||||
.alignment(Alignment::Right)
|
|
||||||
.primary();
|
|
||||||
|
|
||||||
f.render_widget(label_paragraph, label_area);
|
|
||||||
|
|
||||||
draw_text_box(
|
|
||||||
f,
|
|
||||||
TextBoxProps {
|
|
||||||
text_box_area,
|
|
||||||
block_title: None,
|
|
||||||
block_content: text,
|
|
||||||
offset,
|
|
||||||
should_show_cursor,
|
|
||||||
is_selected,
|
|
||||||
cursor_after_string,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_input_box_popup(
|
pub fn draw_input_box_popup(
|
||||||
f: &mut Frame<'_>,
|
f: &mut Frame<'_>,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
@@ -724,18 +567,12 @@ pub fn draw_input_box_popup(
|
|||||||
.margin(1)
|
.margin(1)
|
||||||
.areas(area);
|
.areas(area);
|
||||||
|
|
||||||
draw_text_box(
|
let input_box = InputBox::new(&box_content.text)
|
||||||
f,
|
.offset(*box_content.offset.borrow())
|
||||||
TextBoxProps {
|
.block(title_block_centered(box_title));
|
||||||
text_box_area,
|
|
||||||
block_title: Some(box_title),
|
input_box.show_cursor(f, text_box_area);
|
||||||
block_content: &box_content.text,
|
f.render_widget(input_box, text_box_area);
|
||||||
offset: *box_content.offset.borrow(),
|
|
||||||
should_show_cursor: true,
|
|
||||||
is_selected: false,
|
|
||||||
cursor_after_string: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let help = Paragraph::new("<esc> cancel")
|
let help = Paragraph::new("<esc> cancel")
|
||||||
.help()
|
.help()
|
||||||
|
|||||||
@@ -8,13 +8,17 @@ use crate::models::servarr_data::radarr::radarr_data::{
|
|||||||
ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS, EDIT_COLLECTION_BLOCKS,
|
ActiveRadarrBlock, COLLECTION_DETAILS_BLOCKS, EDIT_COLLECTION_BLOCKS,
|
||||||
};
|
};
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
|
use crate::render_selectable_input_box;
|
||||||
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::styles::ManagarrStyle;
|
||||||
use crate::ui::utils::{layout_paragraph_borderless, title_block_centered};
|
use crate::ui::utils::{layout_paragraph_borderless, title_block_centered};
|
||||||
|
use crate::ui::widgets::button::Button;
|
||||||
|
use crate::ui::widgets::checkbox::Checkbox;
|
||||||
|
use crate::ui::widgets::input_box::InputBox;
|
||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
draw_button, draw_checkbox_with_label, draw_drop_down_menu_button, draw_drop_down_popup,
|
draw_drop_down_popup, draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over,
|
||||||
draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over, draw_popup,
|
draw_popup, draw_selectable_list, DrawUi,
|
||||||
draw_selectable_list, draw_text_box_with_label, DrawUi, LabeledTextBoxProps,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -126,12 +130,9 @@ fn draw_edit_collection_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>
|
|||||||
search_on_add,
|
search_on_add,
|
||||||
path,
|
path,
|
||||||
} = app.data.radarr_data.edit_collection_modal.as_ref().unwrap();
|
} = app.data.radarr_data.edit_collection_modal.as_ref().unwrap();
|
||||||
|
|
||||||
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), area);
|
|
||||||
|
|
||||||
let [paragraph_area, monitored_area, min_availability_area, quality_profile_area, root_folder_area, search_on_add_area, _, buttons_area] =
|
let [paragraph_area, monitored_area, min_availability_area, quality_profile_area, root_folder_area, search_on_add_area, _, buttons_area] =
|
||||||
Layout::vertical([
|
Layout::vertical([
|
||||||
Constraint::Length(6),
|
Constraint::Length(6),
|
||||||
@@ -145,63 +146,52 @@ fn draw_edit_collection_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>
|
|||||||
])
|
])
|
||||||
.margin(1)
|
.margin(1)
|
||||||
.areas(area);
|
.areas(area);
|
||||||
|
|
||||||
let prompt_paragraph = layout_paragraph_borderless(&collection_overview);
|
|
||||||
f.render_widget(prompt_paragraph, paragraph_area);
|
|
||||||
|
|
||||||
let [save_area, cancel_area] =
|
let [save_area, cancel_area] =
|
||||||
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
.areas(buttons_area);
|
.areas(buttons_area);
|
||||||
|
|
||||||
draw_checkbox_with_label(
|
let prompt_paragraph = layout_paragraph_borderless(&collection_overview);
|
||||||
f,
|
let monitored_checkbox = Checkbox::new("Monitored")
|
||||||
monitored_area,
|
.highlighted(selected_block == &ActiveRadarrBlock::EditCollectionToggleMonitored)
|
||||||
"Monitored",
|
.checked(monitored.unwrap_or_default());
|
||||||
monitored.unwrap_or_default(),
|
let min_availability_drop_down_button = Button::new()
|
||||||
selected_block == &ActiveRadarrBlock::EditCollectionToggleMonitored,
|
.title(selected_minimum_availability.to_display_str())
|
||||||
);
|
.label("Minimum Availability")
|
||||||
|
.icon("▼")
|
||||||
draw_drop_down_menu_button(
|
.selected(selected_block == &ActiveRadarrBlock::EditCollectionSelectMinimumAvailability);
|
||||||
f,
|
let quality_profile_drop_down_button = Button::new()
|
||||||
min_availability_area,
|
.title(selected_quality_profile)
|
||||||
"Minimum Availability",
|
.label("Quality Profile")
|
||||||
selected_minimum_availability.to_display_str(),
|
.icon("▼")
|
||||||
selected_block == &ActiveRadarrBlock::EditCollectionSelectMinimumAvailability,
|
.selected(selected_block == &ActiveRadarrBlock::EditCollectionSelectQualityProfile);
|
||||||
);
|
|
||||||
draw_drop_down_menu_button(
|
|
||||||
f,
|
|
||||||
quality_profile_area,
|
|
||||||
"Quality Profile",
|
|
||||||
selected_quality_profile,
|
|
||||||
selected_block == &ActiveRadarrBlock::EditCollectionSelectQualityProfile,
|
|
||||||
);
|
|
||||||
|
|
||||||
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(
|
let root_folder_input_box = InputBox::new(&path.text)
|
||||||
f,
|
.offset(*path.offset.borrow())
|
||||||
LabeledTextBoxProps {
|
.label("Root Folder")
|
||||||
area: root_folder_area,
|
.highlighted(selected_block == &ActiveRadarrBlock::EditCollectionRootFolderPathInput)
|
||||||
label: "Root Folder",
|
.selected(active_radarr_block == ActiveRadarrBlock::EditCollectionRootFolderPathInput);
|
||||||
text: &path.text,
|
render_selectable_input_box!(root_folder_input_box, f, root_folder_area);
|
||||||
offset: *path.offset.borrow(),
|
|
||||||
is_selected: selected_block == &ActiveRadarrBlock::EditCollectionRootFolderPathInput,
|
|
||||||
should_show_cursor: active_radarr_block
|
|
||||||
== ActiveRadarrBlock::EditCollectionRootFolderPathInput,
|
|
||||||
cursor_after_string: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_checkbox_with_label(
|
let search_on_add_checkbox = Checkbox::new("Search on Add")
|
||||||
f,
|
.highlighted(selected_block == &ActiveRadarrBlock::EditCollectionToggleSearchOnAdd)
|
||||||
search_on_add_area,
|
.checked(search_on_add.unwrap_or_default());
|
||||||
"Search on Add",
|
let save_button = Button::new()
|
||||||
search_on_add.unwrap_or_default(),
|
.title("Save")
|
||||||
selected_block == &ActiveRadarrBlock::EditCollectionToggleSearchOnAdd,
|
.selected(yes_no_value && highlight_yes_no);
|
||||||
);
|
let cancel_button = Button::new()
|
||||||
|
.title("Cancel")
|
||||||
|
.selected(!yes_no_value && highlight_yes_no);
|
||||||
|
|
||||||
draw_button(f, save_area, "Save", yes_no_value && highlight_yes_no);
|
f.render_widget(title_block_centered(&title), area);
|
||||||
draw_button(f, cancel_area, "Cancel", !yes_no_value && highlight_yes_no);
|
f.render_widget(prompt_paragraph, paragraph_area);
|
||||||
|
f.render_widget(monitored_checkbox, monitored_area);
|
||||||
|
f.render_widget(min_availability_drop_down_button, min_availability_area);
|
||||||
|
f.render_widget(quality_profile_drop_down_button, quality_profile_area);
|
||||||
|
f.render_widget(search_on_add_checkbox, search_on_add_area);
|
||||||
|
f.render_widget(save_button, save_area);
|
||||||
|
f.render_widget(cancel_button, cancel_area);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_edit_collection_select_minimum_availability_popup(
|
fn draw_edit_collection_select_minimum_availability_popup(
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
use crate::app::App;
|
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::render_selectable_input_box;
|
||||||
use crate::ui::radarr_ui::indexers::draw_indexers;
|
use crate::ui::radarr_ui::indexers::draw_indexers;
|
||||||
|
use crate::ui::styles::ManagarrStyle;
|
||||||
use crate::ui::utils::title_block_centered;
|
use crate::ui::utils::title_block_centered;
|
||||||
use crate::ui::{
|
use crate::ui::widgets::button::Button;
|
||||||
draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading,
|
use crate::ui::widgets::checkbox::Checkbox;
|
||||||
DrawUi, LabeledTextBoxProps,
|
use crate::ui::widgets::input_box::InputBox;
|
||||||
};
|
use crate::ui::{draw_popup_over, loading, DrawUi};
|
||||||
use ratatui::layout::{Constraint, Flex, Layout, Rect};
|
use ratatui::layout::{Constraint, Flex, Layout, Rect};
|
||||||
use ratatui::Frame;
|
use ratatui::Frame;
|
||||||
|
|
||||||
@@ -48,19 +50,16 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
|
|
||||||
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, area);
|
|
||||||
|
|
||||||
let [settings_area, buttons_area] =
|
let [settings_area, buttons_area] =
|
||||||
Layout::vertical([Constraint::Fill(0), Constraint::Length(3)])
|
Layout::vertical([Constraint::Fill(0), Constraint::Length(3)])
|
||||||
.margin(1)
|
.margin(1)
|
||||||
.areas(area);
|
.areas(area);
|
||||||
|
|
||||||
let [left_side_area, right_side_area] =
|
let [left_side_area, right_side_area] =
|
||||||
Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)])
|
Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)])
|
||||||
.margin(1)
|
.margin(1)
|
||||||
.areas(settings_area);
|
.areas(settings_area);
|
||||||
|
let [name_area, rss_area, auto_search_area, interactive_search_area, _] = 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),
|
||||||
@@ -78,115 +77,87 @@ fn draw_edit_indexer_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
.areas(right_side_area);
|
.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(
|
let name_input_box = InputBox::new(&edit_indexer_modal.name.text)
|
||||||
f,
|
.offset(*edit_indexer_modal.name.offset.borrow())
|
||||||
LabeledTextBoxProps {
|
.label("Name")
|
||||||
area: name,
|
.highlighted(selected_block == &ActiveRadarrBlock::EditIndexerNameInput)
|
||||||
label: "Name",
|
.selected(active_radarr_block == ActiveRadarrBlock::EditIndexerNameInput);
|
||||||
text: &edit_indexer_modal.name.text,
|
let url_input_box = InputBox::new(&edit_indexer_modal.url.text)
|
||||||
offset: *edit_indexer_modal.name.offset.borrow(),
|
.offset(*edit_indexer_modal.url.offset.borrow())
|
||||||
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerNameInput,
|
.label("URL")
|
||||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerNameInput,
|
.highlighted(selected_block == &ActiveRadarrBlock::EditIndexerUrlInput)
|
||||||
cursor_after_string: true,
|
.selected(active_radarr_block == ActiveRadarrBlock::EditIndexerUrlInput);
|
||||||
},
|
let api_key_input_box = InputBox::new(&edit_indexer_modal.api_key.text)
|
||||||
);
|
.offset(*edit_indexer_modal.api_key.offset.borrow())
|
||||||
draw_text_box_with_label(
|
.label("API Key")
|
||||||
f,
|
.highlighted(selected_block == &ActiveRadarrBlock::EditIndexerApiKeyInput)
|
||||||
LabeledTextBoxProps {
|
.selected(active_radarr_block == ActiveRadarrBlock::EditIndexerApiKeyInput);
|
||||||
area: url_area,
|
let tags_input_box = InputBox::new(&edit_indexer_modal.tags.text)
|
||||||
label: "URL",
|
.offset(*edit_indexer_modal.tags.offset.borrow())
|
||||||
text: &edit_indexer_modal.url.text,
|
.label("Tags")
|
||||||
offset: *edit_indexer_modal.url.offset.borrow(),
|
.highlighted(selected_block == &ActiveRadarrBlock::EditIndexerTagsInput)
|
||||||
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerUrlInput,
|
.selected(active_radarr_block == ActiveRadarrBlock::EditIndexerTagsInput);
|
||||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerUrlInput,
|
|
||||||
cursor_after_string: true,
|
render_selectable_input_box!(name_input_box, f, name_area);
|
||||||
},
|
render_selectable_input_box!(url_input_box, f, url_area);
|
||||||
);
|
render_selectable_input_box!(api_key_input_box, f, api_key_area);
|
||||||
draw_text_box_with_label(
|
|
||||||
f,
|
|
||||||
LabeledTextBoxProps {
|
|
||||||
area: api_key_area,
|
|
||||||
label: "API Key",
|
|
||||||
text: &edit_indexer_modal.api_key.text,
|
|
||||||
offset: *edit_indexer_modal.api_key.offset.borrow(),
|
|
||||||
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerApiKeyInput,
|
|
||||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerApiKeyInput,
|
|
||||||
cursor_after_string: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if protocol == "torrent" {
|
if protocol == "torrent" {
|
||||||
draw_text_box_with_label(
|
let seed_ratio_input_box = InputBox::new(&edit_indexer_modal.seed_ratio.text)
|
||||||
f,
|
.offset(*edit_indexer_modal.seed_ratio.offset.borrow())
|
||||||
LabeledTextBoxProps {
|
.label("Seed Ratio")
|
||||||
area: seed_ratio_area,
|
.highlighted(selected_block == &ActiveRadarrBlock::EditIndexerSeedRatioInput)
|
||||||
label: "Seed Ratio",
|
.selected(active_radarr_block == ActiveRadarrBlock::EditIndexerSeedRatioInput);
|
||||||
text: &edit_indexer_modal.seed_ratio.text,
|
let tags_input_box = InputBox::new(&edit_indexer_modal.tags.text)
|
||||||
offset: *edit_indexer_modal.seed_ratio.offset.borrow(),
|
.offset(*edit_indexer_modal.tags.offset.borrow())
|
||||||
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerSeedRatioInput,
|
.label("Tags")
|
||||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerSeedRatioInput,
|
.highlighted(selected_block == &ActiveRadarrBlock::EditIndexerTagsInput)
|
||||||
cursor_after_string: true,
|
.selected(active_radarr_block == ActiveRadarrBlock::EditIndexerTagsInput);
|
||||||
},
|
|
||||||
);
|
render_selectable_input_box!(seed_ratio_input_box, f, seed_ratio_area);
|
||||||
draw_text_box_with_label(
|
render_selectable_input_box!(tags_input_box, f, tags_area);
|
||||||
f,
|
|
||||||
LabeledTextBoxProps {
|
|
||||||
area: tags_area,
|
|
||||||
label: "Tags",
|
|
||||||
text: &edit_indexer_modal.tags.text,
|
|
||||||
offset: *edit_indexer_modal.tags.offset.borrow(),
|
|
||||||
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerTagsInput,
|
|
||||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerTagsInput,
|
|
||||||
cursor_after_string: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
draw_text_box_with_label(
|
render_selectable_input_box!(tags_input_box, f, seed_ratio_area);
|
||||||
f,
|
|
||||||
LabeledTextBoxProps {
|
|
||||||
area: seed_ratio_area,
|
|
||||||
label: "Tags",
|
|
||||||
text: &edit_indexer_modal.tags.text,
|
|
||||||
offset: *edit_indexer_modal.tags.offset.borrow(),
|
|
||||||
is_selected: selected_block == &ActiveRadarrBlock::EditIndexerTagsInput,
|
|
||||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditIndexerTagsInput,
|
|
||||||
cursor_after_string: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_checkbox_with_label(
|
let rss_checkbox = Checkbox::new("Enable RSS")
|
||||||
f,
|
.checked(edit_indexer_modal.enable_rss.unwrap_or_default())
|
||||||
rss,
|
.highlighted(selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableRss);
|
||||||
"Enable RSS",
|
let auto_search_checkbox = Checkbox::new("Enable Automatic Search")
|
||||||
edit_indexer_modal.enable_rss.unwrap_or_default(),
|
.checked(
|
||||||
selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableRss,
|
edit_indexer_modal
|
||||||
);
|
.enable_automatic_search
|
||||||
draw_checkbox_with_label(
|
.unwrap_or_default(),
|
||||||
f,
|
)
|
||||||
auto_search,
|
.highlighted(selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableAutomaticSearch);
|
||||||
"Enable Automatic Search",
|
let interactive_search_checkbox = Checkbox::new("Enable Interactive Search")
|
||||||
edit_indexer_modal
|
.checked(
|
||||||
.enable_automatic_search
|
edit_indexer_modal
|
||||||
.unwrap_or_default(),
|
.enable_interactive_search
|
||||||
selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableAutomaticSearch,
|
.unwrap_or_default(),
|
||||||
);
|
)
|
||||||
draw_checkbox_with_label(
|
.highlighted(
|
||||||
f,
|
selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch,
|
||||||
interactive_search,
|
);
|
||||||
"Enable Interactive Search",
|
|
||||||
edit_indexer_modal
|
|
||||||
.enable_interactive_search
|
|
||||||
.unwrap_or_default(),
|
|
||||||
selected_block == &ActiveRadarrBlock::EditIndexerToggleEnableInteractiveSearch,
|
|
||||||
);
|
|
||||||
|
|
||||||
let [save_area, cancel_area] =
|
let [save_area, cancel_area] =
|
||||||
Layout::horizontal([Constraint::Percentage(25), Constraint::Percentage(25)])
|
Layout::horizontal([Constraint::Percentage(25), Constraint::Percentage(25)])
|
||||||
.flex(Flex::Center)
|
.flex(Flex::Center)
|
||||||
.areas(buttons_area);
|
.areas(buttons_area);
|
||||||
|
|
||||||
draw_button(f, save_area, "Save", yes_no_value && highlight_yes_no);
|
let save_button = Button::new()
|
||||||
draw_button(f, cancel_area, "Cancel", !yes_no_value && highlight_yes_no);
|
.title("Save")
|
||||||
|
.selected(yes_no_value && highlight_yes_no);
|
||||||
|
let cancel_button = Button::new()
|
||||||
|
.title("Cancel")
|
||||||
|
.selected(!yes_no_value && highlight_yes_no);
|
||||||
|
|
||||||
|
f.render_widget(block, area);
|
||||||
|
f.render_widget(rss_checkbox, rss_area);
|
||||||
|
f.render_widget(auto_search_checkbox, auto_search_area);
|
||||||
|
f.render_widget(interactive_search_checkbox, interactive_search_area);
|
||||||
|
f.render_widget(save_button, save_area);
|
||||||
|
f.render_widget(cancel_button, cancel_area);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
loading(f, block, area, app.is_loading);
|
loading(f, block, area, app.is_loading);
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ use crate::models::servarr_data::radarr::radarr_data::{
|
|||||||
ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS,
|
ActiveRadarrBlock, INDEXER_SETTINGS_BLOCKS,
|
||||||
};
|
};
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
|
use crate::render_selectable_input_box;
|
||||||
use crate::ui::radarr_ui::indexers::draw_indexers;
|
use crate::ui::radarr_ui::indexers::draw_indexers;
|
||||||
|
use crate::ui::styles::ManagarrStyle;
|
||||||
use crate::ui::utils::title_block_centered;
|
use crate::ui::utils::title_block_centered;
|
||||||
use crate::ui::{
|
use crate::ui::widgets::button::Button;
|
||||||
draw_button, draw_checkbox_with_label, draw_popup_over, draw_text_box_with_label, loading,
|
use crate::ui::widgets::checkbox::Checkbox;
|
||||||
DrawUi, LabeledTextBoxProps,
|
use crate::ui::widgets::input_box::InputBox;
|
||||||
};
|
use crate::ui::{draw_popup_over, loading, DrawUi};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "indexer_settings_ui_tests.rs"]
|
#[path = "indexer_settings_ui_tests.rs"]
|
||||||
@@ -50,18 +52,15 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area:
|
|||||||
|
|
||||||
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, area);
|
|
||||||
|
|
||||||
let [settings_area, buttons_area] =
|
let [settings_area, buttons_area] =
|
||||||
Layout::vertical([Constraint::Fill(0), Constraint::Length(3)])
|
Layout::vertical([Constraint::Fill(0), Constraint::Length(3)])
|
||||||
.margin(1)
|
.margin(1)
|
||||||
.areas(area);
|
.areas(area);
|
||||||
|
|
||||||
let [left_side_area, right_side_area] =
|
let [left_side_area, right_side_area] =
|
||||||
Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)])
|
Layout::horizontal([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)])
|
||||||
.margin(1)
|
.margin(1)
|
||||||
.areas(settings_area);
|
.areas(settings_area);
|
||||||
|
|
||||||
let [min_age_area, retention_area, max_size_area, prefer_flags_area, _] = Layout::vertical([
|
let [min_age_area, retention_area, max_size_area, prefer_flags_area, _] = Layout::vertical([
|
||||||
Constraint::Length(3),
|
Constraint::Length(3),
|
||||||
Constraint::Length(3),
|
Constraint::Length(3),
|
||||||
@@ -81,110 +80,80 @@ fn draw_edit_indexer_settings_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area:
|
|||||||
.areas(right_side_area);
|
.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(
|
let min_age = indexer_settings.minimum_age.to_string();
|
||||||
f,
|
let retention = indexer_settings.retention.to_string();
|
||||||
LabeledTextBoxProps {
|
let max_size = indexer_settings.maximum_size.to_string();
|
||||||
area: min_age_area,
|
let availability_delay = indexer_settings.availability_delay.to_string();
|
||||||
label: "Minimum Age (minutes) ▴▾",
|
let rss_sync_interval = indexer_settings.rss_sync_interval.to_string();
|
||||||
text: &indexer_settings.minimum_age.to_string(),
|
|
||||||
offset: 0,
|
let min_age_text_box = InputBox::new(&min_age)
|
||||||
is_selected: selected_block == &ActiveRadarrBlock::IndexerSettingsMinimumAgeInput,
|
.cursor_after_string(false)
|
||||||
should_show_cursor: active_radarr_block
|
.label("Minimum Age (minutes) ▴▾")
|
||||||
== ActiveRadarrBlock::IndexerSettingsMinimumAgeInput,
|
.highlighted(selected_block == &ActiveRadarrBlock::IndexerSettingsMinimumAgeInput)
|
||||||
cursor_after_string: false,
|
.selected(active_radarr_block == ActiveRadarrBlock::IndexerSettingsMinimumAgeInput);
|
||||||
},
|
let retention_input_box = InputBox::new(&retention)
|
||||||
);
|
.cursor_after_string(false)
|
||||||
draw_text_box_with_label(
|
.label("Retention (days) ▴▾")
|
||||||
f,
|
.highlighted(selected_block == &ActiveRadarrBlock::IndexerSettingsRetentionInput)
|
||||||
LabeledTextBoxProps {
|
.selected(active_radarr_block == ActiveRadarrBlock::IndexerSettingsRetentionInput);
|
||||||
area: retention_area,
|
let max_size_input_box = InputBox::new(&max_size)
|
||||||
label: "Retention (days) ▴▾",
|
.cursor_after_string(false)
|
||||||
text: &indexer_settings.retention.to_string(),
|
.label("Maximum Size (MB) ▴▾")
|
||||||
offset: 0,
|
.highlighted(selected_block == &ActiveRadarrBlock::IndexerSettingsMaximumSizeInput)
|
||||||
is_selected: selected_block == &ActiveRadarrBlock::IndexerSettingsRetentionInput,
|
.selected(active_radarr_block == ActiveRadarrBlock::IndexerSettingsMaximumSizeInput);
|
||||||
should_show_cursor: active_radarr_block
|
let availability_delay_input_box = InputBox::new(&availability_delay)
|
||||||
== ActiveRadarrBlock::IndexerSettingsRetentionInput,
|
.cursor_after_string(false)
|
||||||
cursor_after_string: false,
|
.label("Availability Delay (days) ▴▾")
|
||||||
},
|
.highlighted(selected_block == &ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput)
|
||||||
);
|
.selected(active_radarr_block == ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput);
|
||||||
draw_text_box_with_label(
|
let rss_sync_interval_input_box = InputBox::new(&rss_sync_interval)
|
||||||
f,
|
.cursor_after_string(false)
|
||||||
LabeledTextBoxProps {
|
.label("RSS Sync Interval (minutes) ▴▾")
|
||||||
area: max_size_area,
|
.highlighted(selected_block == &ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput)
|
||||||
label: "Maximum Size (MB) ▴▾",
|
.selected(active_radarr_block == ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput);
|
||||||
text: &indexer_settings.maximum_size.to_string(),
|
let whitelisted_subs_input_box =
|
||||||
offset: 0,
|
InputBox::new(&indexer_settings.whitelisted_hardcoded_subs.text)
|
||||||
is_selected: selected_block == &ActiveRadarrBlock::IndexerSettingsMaximumSizeInput,
|
.offset(*indexer_settings.whitelisted_hardcoded_subs.offset.borrow())
|
||||||
should_show_cursor: active_radarr_block
|
.label("Whitelisted Subtitle Tags")
|
||||||
== ActiveRadarrBlock::IndexerSettingsMaximumSizeInput,
|
.highlighted(
|
||||||
cursor_after_string: false,
|
selected_block == &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
|
||||||
},
|
)
|
||||||
);
|
.selected(
|
||||||
draw_text_box_with_label(
|
active_radarr_block == ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
|
||||||
f,
|
);
|
||||||
LabeledTextBoxProps {
|
|
||||||
area: availability_delay_area,
|
render_selectable_input_box!(min_age_text_box, f, min_age_area);
|
||||||
label: "Availability Delay (days) ▴▾",
|
render_selectable_input_box!(retention_input_box, f, retention_area);
|
||||||
text: &indexer_settings.availability_delay.to_string(),
|
render_selectable_input_box!(max_size_input_box, f, max_size_area);
|
||||||
offset: 0,
|
render_selectable_input_box!(availability_delay_input_box, f, availability_delay_area);
|
||||||
is_selected: selected_block == &ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput,
|
render_selectable_input_box!(rss_sync_interval_input_box, f, rss_sync_interval_area);
|
||||||
should_show_cursor: active_radarr_block
|
render_selectable_input_box!(whitelisted_subs_input_box, f, whitelisted_sub_tags_area);
|
||||||
== ActiveRadarrBlock::IndexerSettingsAvailabilityDelayInput,
|
|
||||||
cursor_after_string: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
draw_text_box_with_label(
|
|
||||||
f,
|
|
||||||
LabeledTextBoxProps {
|
|
||||||
area: rss_sync_interval_area,
|
|
||||||
label: "RSS Sync Interval (minutes) ▴▾",
|
|
||||||
text: &indexer_settings.rss_sync_interval.to_string(),
|
|
||||||
offset: 0,
|
|
||||||
is_selected: selected_block == &ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput,
|
|
||||||
should_show_cursor: active_radarr_block
|
|
||||||
== ActiveRadarrBlock::IndexerSettingsRssSyncIntervalInput,
|
|
||||||
cursor_after_string: false,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
draw_text_box_with_label(
|
|
||||||
f,
|
|
||||||
LabeledTextBoxProps {
|
|
||||||
area: whitelisted_sub_tags_area,
|
|
||||||
label: "Whitelisted Subtitle Tags",
|
|
||||||
text: &indexer_settings.whitelisted_hardcoded_subs.text,
|
|
||||||
offset: *indexer_settings.whitelisted_hardcoded_subs.offset.borrow(),
|
|
||||||
is_selected: selected_block
|
|
||||||
== &ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
|
|
||||||
should_show_cursor: active_radarr_block
|
|
||||||
== ActiveRadarrBlock::IndexerSettingsWhitelistedSubtitleTagsInput,
|
|
||||||
cursor_after_string: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_checkbox_with_label(
|
let prefer_indexer_flags_checkbox = Checkbox::new("Prefer Indexer Flags")
|
||||||
f,
|
.highlighted(selected_block == &ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags)
|
||||||
prefer_flags_area,
|
.checked(indexer_settings.prefer_indexer_flags);
|
||||||
"Prefer Indexer Flags",
|
let allow_hardcoded_subs_checkbox = Checkbox::new("Allow Hardcoded Subs")
|
||||||
indexer_settings.prefer_indexer_flags,
|
.highlighted(selected_block == &ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs)
|
||||||
selected_block == &ActiveRadarrBlock::IndexerSettingsTogglePreferIndexerFlags,
|
.checked(indexer_settings.allow_hardcoded_subs);
|
||||||
);
|
|
||||||
|
|
||||||
draw_checkbox_with_label(
|
|
||||||
f,
|
|
||||||
allow_hardcoded_subs_area,
|
|
||||||
"Allow Hardcoded Subs",
|
|
||||||
indexer_settings.allow_hardcoded_subs,
|
|
||||||
selected_block == &ActiveRadarrBlock::IndexerSettingsToggleAllowHardcodedSubs,
|
|
||||||
);
|
|
||||||
|
|
||||||
let [save_area, cancel_area] =
|
let [save_area, cancel_area] =
|
||||||
Layout::horizontal([Constraint::Percentage(25), Constraint::Percentage(25)])
|
Layout::horizontal([Constraint::Percentage(25), Constraint::Percentage(25)])
|
||||||
.flex(Flex::Center)
|
.flex(Flex::Center)
|
||||||
.areas(buttons_area);
|
.areas(buttons_area);
|
||||||
|
|
||||||
draw_button(f, save_area, "Save", yes_no_value && highlight_yes_no);
|
let save_button = Button::new()
|
||||||
draw_button(f, cancel_area, "Cancel", !yes_no_value && highlight_yes_no);
|
.title("Save")
|
||||||
|
.selected(yes_no_value && highlight_yes_no);
|
||||||
|
let cancel_button = Button::new()
|
||||||
|
.title("Cancel")
|
||||||
|
.selected(!yes_no_value && highlight_yes_no);
|
||||||
|
|
||||||
|
f.render_widget(block, area);
|
||||||
|
f.render_widget(prefer_indexer_flags_checkbox, prefer_flags_area);
|
||||||
|
f.render_widget(allow_hardcoded_subs_checkbox, allow_hardcoded_subs_area);
|
||||||
|
f.render_widget(save_button, save_area);
|
||||||
|
f.render_widget(cancel_button, cancel_area);
|
||||||
} else {
|
} else {
|
||||||
loading(f, block, area, app.is_loading);
|
loading(f, block, area, app.is_loading);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,14 +16,14 @@ use crate::ui::utils::{
|
|||||||
borderless_block, get_width_from_percentage, layout_block, layout_paragraph_borderless,
|
borderless_block, get_width_from_percentage, layout_block, layout_paragraph_borderless,
|
||||||
title_block_centered,
|
title_block_centered,
|
||||||
};
|
};
|
||||||
|
use crate::ui::widgets::button::Button;
|
||||||
|
use crate::ui::widgets::input_box::InputBox;
|
||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
draw_button, draw_drop_down_menu_button, draw_drop_down_popup, draw_error_popup,
|
draw_drop_down_popup, draw_error_popup, draw_error_popup_over, draw_large_popup_over,
|
||||||
draw_error_popup_over, draw_large_popup_over, draw_medium_popup_over, draw_selectable_list,
|
draw_medium_popup_over, draw_selectable_list, draw_table, DrawUi, TableProps,
|
||||||
draw_table, draw_text_box, draw_text_box_with_label, DrawUi, LabeledTextBoxProps, TableProps,
|
|
||||||
TextBoxProps,
|
|
||||||
};
|
};
|
||||||
use crate::utils::convert_runtime;
|
use crate::utils::convert_runtime;
|
||||||
use crate::App;
|
use crate::{render_selectable_input_box, App};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "add_movie_ui_tests.rs"]
|
#[path = "add_movie_ui_tests.rs"]
|
||||||
@@ -120,24 +120,17 @@ fn draw_add_movie_search(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::AddMovieSearchInput => {
|
ActiveRadarrBlock::AddMovieSearchInput => {
|
||||||
draw_text_box(
|
let search_box = InputBox::new(block_content)
|
||||||
f,
|
.offset(offset)
|
||||||
TextBoxProps {
|
.block(title_block_centered("Add Movie"));
|
||||||
text_box_area: search_box_area,
|
|
||||||
block_title: Some("Add Movie"),
|
|
||||||
block_content,
|
|
||||||
offset,
|
|
||||||
should_show_cursor: true,
|
|
||||||
is_selected: false,
|
|
||||||
cursor_after_string: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
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);
|
||||||
|
|
||||||
|
search_box.show_cursor(f, search_box_area);
|
||||||
|
f.render_widget(layout_block(), results_area);
|
||||||
|
f.render_widget(search_box, search_box_area);
|
||||||
f.render_widget(help_paragraph, help_area);
|
f.render_widget(help_paragraph, help_area);
|
||||||
}
|
}
|
||||||
ActiveRadarrBlock::AddMovieEmptySearchResults => {
|
ActiveRadarrBlock::AddMovieEmptySearchResults => {
|
||||||
@@ -252,17 +245,11 @@ fn draw_add_movie_search(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_text_box(
|
f.render_widget(
|
||||||
f,
|
InputBox::new(block_content)
|
||||||
TextBoxProps {
|
.offset(offset)
|
||||||
text_box_area: search_box_area,
|
.block(title_block_centered("Add Movie")),
|
||||||
block_title: Some("Add Movie"),
|
search_box_area,
|
||||||
block_content,
|
|
||||||
offset,
|
|
||||||
should_show_cursor: false,
|
|
||||||
is_selected: false,
|
|
||||||
cursor_after_string: true,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,54 +382,50 @@ fn draw_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
|||||||
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
.areas(buttons_area);
|
.areas(buttons_area);
|
||||||
|
|
||||||
draw_drop_down_menu_button(
|
let root_folder_drop_down_button = Button::new()
|
||||||
f,
|
.title(&selected_root_folder.path)
|
||||||
root_folder_area,
|
.label("Root Folder")
|
||||||
"Root Folder",
|
.icon("▼")
|
||||||
&selected_root_folder.path,
|
.selected(selected_block == &ActiveRadarrBlock::AddMovieSelectRootFolder);
|
||||||
selected_block == &ActiveRadarrBlock::AddMovieSelectRootFolder,
|
let monitor_drop_down_button = Button::new()
|
||||||
);
|
.title(selected_monitor.to_display_str())
|
||||||
|
.label("Monitor")
|
||||||
|
.icon("▼")
|
||||||
|
.selected(selected_block == &ActiveRadarrBlock::AddMovieSelectMonitor);
|
||||||
|
let min_availability_drop_down_button = Button::new()
|
||||||
|
.title(selected_minimum_availability.to_display_str())
|
||||||
|
.label("Minimum Availability")
|
||||||
|
.icon("▼")
|
||||||
|
.selected(selected_block == &ActiveRadarrBlock::AddMovieSelectMinimumAvailability);
|
||||||
|
let quality_profile_drop_down_button = Button::new()
|
||||||
|
.title(selected_quality_profile)
|
||||||
|
.label("Quality Profile")
|
||||||
|
.icon("▼")
|
||||||
|
.selected(selected_block == &ActiveRadarrBlock::AddMovieSelectQualityProfile);
|
||||||
|
|
||||||
draw_drop_down_menu_button(
|
f.render_widget(root_folder_drop_down_button, root_folder_area);
|
||||||
f,
|
f.render_widget(monitor_drop_down_button, monitor_area);
|
||||||
monitor_area,
|
f.render_widget(min_availability_drop_down_button, min_availability_area);
|
||||||
"Monitor",
|
f.render_widget(quality_profile_drop_down_button, quality_profile_area);
|
||||||
selected_monitor.to_display_str(),
|
|
||||||
selected_block == &ActiveRadarrBlock::AddMovieSelectMonitor,
|
|
||||||
);
|
|
||||||
|
|
||||||
draw_drop_down_menu_button(
|
|
||||||
f,
|
|
||||||
min_availability_area,
|
|
||||||
"Minimum Availability",
|
|
||||||
selected_minimum_availability.to_display_str(),
|
|
||||||
selected_block == &ActiveRadarrBlock::AddMovieSelectMinimumAvailability,
|
|
||||||
);
|
|
||||||
draw_drop_down_menu_button(
|
|
||||||
f,
|
|
||||||
quality_profile_area,
|
|
||||||
"Quality Profile",
|
|
||||||
selected_quality_profile,
|
|
||||||
selected_block == &ActiveRadarrBlock::AddMovieSelectQualityProfile,
|
|
||||||
);
|
|
||||||
|
|
||||||
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(
|
let tags_input_box = InputBox::new(&tags.text)
|
||||||
f,
|
.offset(*tags.offset.borrow())
|
||||||
LabeledTextBoxProps {
|
.label("Tags")
|
||||||
area: tags_area,
|
.highlighted(selected_block == &ActiveRadarrBlock::AddMovieTagsInput)
|
||||||
label: "Tags",
|
.selected(active_radarr_block == ActiveRadarrBlock::AddMovieTagsInput);
|
||||||
text: &tags.text,
|
render_selectable_input_box!(tags_input_box, f, tags_area);
|
||||||
offset: *tags.offset.borrow(),
|
|
||||||
is_selected: selected_block == &ActiveRadarrBlock::AddMovieTagsInput,
|
|
||||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::AddMovieTagsInput,
|
|
||||||
cursor_after_string: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_button(f, add_area, "Add", yes_no_value && highlight_yes_no);
|
let add_button = Button::new()
|
||||||
draw_button(f, cancel_area, "Cancel", !yes_no_value && highlight_yes_no);
|
.title("Add")
|
||||||
|
.selected(yes_no_value && highlight_yes_no);
|
||||||
|
let cancel_button = Button::new()
|
||||||
|
.title("Cancel")
|
||||||
|
.selected(!yes_no_value && highlight_yes_no);
|
||||||
|
|
||||||
|
f.render_widget(add_button, add_area);
|
||||||
|
f.render_widget(cancel_button, cancel_area);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_add_movie_select_monitor_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
fn draw_add_movie_select_monitor_popup(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
|
||||||
|
|||||||
@@ -9,14 +9,18 @@ use crate::models::servarr_data::radarr::radarr_data::{
|
|||||||
ActiveRadarrBlock, EDIT_MOVIE_BLOCKS, MOVIE_DETAILS_BLOCKS,
|
ActiveRadarrBlock, EDIT_MOVIE_BLOCKS, MOVIE_DETAILS_BLOCKS,
|
||||||
};
|
};
|
||||||
use crate::models::Route;
|
use crate::models::Route;
|
||||||
|
use crate::render_selectable_input_box;
|
||||||
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::styles::ManagarrStyle;
|
||||||
use crate::ui::utils::{layout_paragraph_borderless, title_block_centered};
|
use crate::ui::utils::{layout_paragraph_borderless, title_block_centered};
|
||||||
|
use crate::ui::widgets::button::Button;
|
||||||
|
use crate::ui::widgets::checkbox::Checkbox;
|
||||||
|
use crate::ui::widgets::input_box::InputBox;
|
||||||
use crate::ui::{
|
use crate::ui::{
|
||||||
draw_button, draw_checkbox_with_label, draw_drop_down_menu_button, draw_drop_down_popup,
|
draw_drop_down_popup, draw_large_popup_over_background_fn_with_ui,
|
||||||
draw_large_popup_over_background_fn_with_ui, draw_medium_popup_over, draw_popup,
|
draw_medium_popup_over, draw_popup, draw_selectable_list, DrawUi,
|
||||||
draw_selectable_list, draw_text_box_with_label, DrawUi, LabeledTextBoxProps,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -123,12 +127,9 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, are
|
|||||||
path,
|
path,
|
||||||
tags,
|
tags,
|
||||||
} = app.data.radarr_data.edit_movie_modal.as_ref().unwrap();
|
} = app.data.radarr_data.edit_movie_modal.as_ref().unwrap();
|
||||||
|
|
||||||
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), area);
|
|
||||||
|
|
||||||
let [paragraph_area, monitored_area, min_availability_area, quality_profile_area, path_area, tags_area, _, buttons_area] =
|
let [paragraph_area, monitored_area, min_availability_area, quality_profile_area, path_area, tags_area, _, buttons_area] =
|
||||||
Layout::vertical([
|
Layout::vertical([
|
||||||
Constraint::Length(6),
|
Constraint::Length(6),
|
||||||
@@ -142,66 +143,61 @@ fn draw_edit_movie_confirmation_prompt(f: &mut Frame<'_>, app: &mut App<'_>, are
|
|||||||
])
|
])
|
||||||
.margin(1)
|
.margin(1)
|
||||||
.areas(area);
|
.areas(area);
|
||||||
|
|
||||||
let prompt_paragraph = layout_paragraph_borderless(&movie_overview);
|
|
||||||
f.render_widget(prompt_paragraph, paragraph_area);
|
|
||||||
|
|
||||||
let [save_area, cancel_area] =
|
let [save_area, cancel_area] =
|
||||||
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
Layout::horizontal([Constraint::Percentage(50), Constraint::Percentage(50)])
|
||||||
.areas(buttons_area);
|
.areas(buttons_area);
|
||||||
|
|
||||||
draw_checkbox_with_label(
|
let prompt_paragraph = layout_paragraph_borderless(&movie_overview);
|
||||||
f,
|
let monitored_checkbox = Checkbox::new("Monitored")
|
||||||
monitored_area,
|
.checked(monitored.unwrap_or_default())
|
||||||
"Monitored",
|
.highlighted(selected_block == &ActiveRadarrBlock::EditMovieToggleMonitored);
|
||||||
monitored.unwrap_or_default(),
|
let min_availability_drop_down_button = Button::new()
|
||||||
selected_block == &ActiveRadarrBlock::EditMovieToggleMonitored,
|
.title(selected_minimum_availability.to_display_str())
|
||||||
);
|
.label("Minimum Availability")
|
||||||
|
.icon("▼")
|
||||||
draw_drop_down_menu_button(
|
.selected(selected_block == &ActiveRadarrBlock::EditMovieSelectMinimumAvailability);
|
||||||
f,
|
let quality_profile_drop_down_button = Button::new()
|
||||||
min_availability_area,
|
.title(selected_quality_profile)
|
||||||
"Minimum Availability",
|
.label("Quality Profile")
|
||||||
selected_minimum_availability.to_display_str(),
|
.icon("▼")
|
||||||
selected_block == &ActiveRadarrBlock::EditMovieSelectMinimumAvailability,
|
.selected(selected_block == &ActiveRadarrBlock::EditMovieSelectQualityProfile);
|
||||||
);
|
|
||||||
draw_drop_down_menu_button(
|
|
||||||
f,
|
|
||||||
quality_profile_area,
|
|
||||||
"Quality Profile",
|
|
||||||
selected_quality_profile,
|
|
||||||
selected_block == &ActiveRadarrBlock::EditMovieSelectQualityProfile,
|
|
||||||
);
|
|
||||||
|
|
||||||
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(
|
let path_input_box = InputBox::new(&path.text)
|
||||||
f,
|
.offset(*path.offset.borrow())
|
||||||
LabeledTextBoxProps {
|
.label("Path")
|
||||||
area: path_area,
|
.highlighted(selected_block == &ActiveRadarrBlock::EditMoviePathInput)
|
||||||
label: "Path",
|
.selected(active_radarr_block == ActiveRadarrBlock::EditMoviePathInput);
|
||||||
text: &path.text,
|
let tags_input_box = InputBox::new(&tags.text)
|
||||||
offset: *path.offset.borrow(),
|
.offset(*tags.offset.borrow())
|
||||||
is_selected: selected_block == &ActiveRadarrBlock::EditMoviePathInput,
|
.label("Tags")
|
||||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditMoviePathInput,
|
.highlighted(selected_block == &ActiveRadarrBlock::EditMovieTagsInput)
|
||||||
cursor_after_string: true,
|
.selected(active_radarr_block == ActiveRadarrBlock::EditMovieTagsInput);
|
||||||
},
|
|
||||||
);
|
match active_radarr_block {
|
||||||
draw_text_box_with_label(
|
ActiveRadarrBlock::EditMoviePathInput => path_input_box.show_cursor(f, path_area),
|
||||||
f,
|
ActiveRadarrBlock::EditMovieTagsInput => tags_input_box.show_cursor(f, tags_area),
|
||||||
LabeledTextBoxProps {
|
_ => (),
|
||||||
area: tags_area,
|
}
|
||||||
label: "Tags",
|
|
||||||
text: &tags.text,
|
render_selectable_input_box!(path_input_box, f, path_area);
|
||||||
offset: *tags.offset.borrow(),
|
render_selectable_input_box!(tags_input_box, f, tags_area);
|
||||||
is_selected: selected_block == &ActiveRadarrBlock::EditMovieTagsInput,
|
|
||||||
should_show_cursor: active_radarr_block == ActiveRadarrBlock::EditMovieTagsInput,
|
|
||||||
cursor_after_string: true,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_button(f, save_area, "Save", yes_no_value && highlight_yes_no);
|
let save_button = Button::new()
|
||||||
draw_button(f, cancel_area, "Cancel", !yes_no_value && highlight_yes_no);
|
.title("Save")
|
||||||
|
.selected(yes_no_value && highlight_yes_no);
|
||||||
|
let cancel_button = Button::new()
|
||||||
|
.title("Cancel")
|
||||||
|
.selected(!yes_no_value && highlight_yes_no);
|
||||||
|
|
||||||
|
f.render_widget(title_block_centered(&title), area);
|
||||||
|
f.render_widget(prompt_paragraph, paragraph_area);
|
||||||
|
f.render_widget(monitored_checkbox, monitored_area);
|
||||||
|
f.render_widget(min_availability_drop_down_button, min_availability_area);
|
||||||
|
f.render_widget(quality_profile_drop_down_button, quality_profile_area);
|
||||||
|
f.render_widget(save_button, save_area);
|
||||||
|
f.render_widget(cancel_button, cancel_area);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_edit_movie_select_minimum_availability_popup(
|
fn draw_edit_movie_select_minimum_availability_popup(
|
||||||
|
|||||||
+1
-37
@@ -1,9 +1,9 @@
|
|||||||
use crate::ui::styles::ManagarrStyle;
|
use crate::ui::styles::ManagarrStyle;
|
||||||
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||||
use ratatui::style::{Color, Style, Stylize};
|
use ratatui::style::{Color, Style, Stylize};
|
||||||
|
use ratatui::symbols;
|
||||||
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};
|
|
||||||
|
|
||||||
pub const COLOR_TEAL: Color = Color::Rgb(35, 50, 55);
|
pub const COLOR_TEAL: Color = Color::Rgb(35, 50, 55);
|
||||||
|
|
||||||
@@ -37,28 +37,6 @@ pub fn layout_block_bottom_border<'a>() -> Block<'a> {
|
|||||||
Block::new().borders(Borders::BOTTOM)
|
Block::new().borders(Borders::BOTTOM)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn layout_button_paragraph(
|
|
||||||
is_selected: bool,
|
|
||||||
label: &str,
|
|
||||||
alignment: Alignment,
|
|
||||||
) -> Paragraph<'_> {
|
|
||||||
Paragraph::new(Text::from(label))
|
|
||||||
.block(layout_block())
|
|
||||||
.alignment(alignment)
|
|
||||||
.style(style_block_highlight(is_selected))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn layout_button_paragraph_borderless(
|
|
||||||
is_selected: bool,
|
|
||||||
label: &str,
|
|
||||||
alignment: Alignment,
|
|
||||||
) -> Paragraph<'_> {
|
|
||||||
Paragraph::new(Text::from(label))
|
|
||||||
.block(borderless_block())
|
|
||||||
.alignment(alignment)
|
|
||||||
.style(style_block_highlight(is_selected))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn layout_paragraph_borderless(string: &str) -> Paragraph<'_> {
|
pub fn layout_paragraph_borderless(string: &str) -> Paragraph<'_> {
|
||||||
Paragraph::new(Text::from(string))
|
Paragraph::new(Text::from(string))
|
||||||
.block(borderless_block())
|
.block(borderless_block())
|
||||||
@@ -135,20 +113,6 @@ pub fn line_gauge_with_label(title: &str, ratio: f64) -> LineGauge<'_> {
|
|||||||
.label(Line::from(format!("{title}: {:.0}%", ratio * 100.0)))
|
.label(Line::from(format!("{title}: {:.0}%", ratio * 100.0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_cursor(
|
|
||||||
f: &mut Frame<'_>,
|
|
||||||
area: Rect,
|
|
||||||
offset: usize,
|
|
||||||
string: &str,
|
|
||||||
cursor_after_string: bool,
|
|
||||||
) {
|
|
||||||
if cursor_after_string {
|
|
||||||
f.set_cursor(area.x + (string.len() - offset) as u16 + 1, area.y + 1);
|
|
||||||
} else {
|
|
||||||
f.set_cursor(area.x + 1u16, area.y + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_width_from_percentage(area: Rect, percentage: u16) -> usize {
|
pub fn get_width_from_percentage(area: Rect, percentage: u16) -> usize {
|
||||||
(area.width as f64 * (percentage as f64 / 100.0)) as usize
|
(area.width as f64 * (percentage as f64 / 100.0)) as usize
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
use crate::ui::styles::ManagarrStyle;
|
||||||
|
use crate::ui::utils::{layout_block, style_block_highlight};
|
||||||
|
use ratatui::buffer::Buffer;
|
||||||
|
use ratatui::layout::{Alignment, Constraint, Flex, Layout, Rect};
|
||||||
|
use ratatui::prelude::{Style, Styled, Text, Widget};
|
||||||
|
use ratatui::widgets::Paragraph;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Button<'a> {
|
||||||
|
title: &'a str,
|
||||||
|
label: Option<&'a str>,
|
||||||
|
icon: Option<&'a str>,
|
||||||
|
style: Style,
|
||||||
|
is_selected: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Button<'a> {
|
||||||
|
pub fn title(mut self, title: &'a str) -> Button<'a> {
|
||||||
|
self.title = title;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn label(mut self, label: &'a str) -> Button<'a> {
|
||||||
|
self.label = Some(label);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn icon(mut self, icon: &'a str) -> Button<'a> {
|
||||||
|
self.icon = Some(icon);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn style<S: Into<Style>>(mut self, style: S) -> Button<'a> {
|
||||||
|
self.style = style.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selected(mut self, is_selected: bool) -> Button<'a> {
|
||||||
|
self.is_selected = is_selected;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_button_with_icon(&self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let [title_area, icon_area] = Layout::horizontal([
|
||||||
|
Constraint::Length(self.title.len() as u16),
|
||||||
|
Constraint::Percentage(25),
|
||||||
|
])
|
||||||
|
.flex(Flex::SpaceBetween)
|
||||||
|
.margin(1)
|
||||||
|
.areas(area);
|
||||||
|
let style = style_block_highlight(self.is_selected);
|
||||||
|
|
||||||
|
if let Some(icon) = self.icon {
|
||||||
|
layout_block().style(style).render(area, buf);
|
||||||
|
Paragraph::new(Text::from(self.title))
|
||||||
|
.alignment(Alignment::Left)
|
||||||
|
.style(style)
|
||||||
|
.render(title_area, buf);
|
||||||
|
Paragraph::new(Text::from(format!("{icon} ")))
|
||||||
|
.alignment(Alignment::Right)
|
||||||
|
.style(style)
|
||||||
|
.render(icon_area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_labeled_button(&self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let [label_area, button_area] =
|
||||||
|
Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area);
|
||||||
|
let label_paragraph = Paragraph::new(Text::from(format!("\n{}: ", self.label.unwrap())))
|
||||||
|
.alignment(Alignment::Right)
|
||||||
|
.primary();
|
||||||
|
|
||||||
|
if self.icon.is_some() {
|
||||||
|
self.render_button_with_icon(button_area, buf);
|
||||||
|
label_paragraph.render(label_area, buf);
|
||||||
|
} else {
|
||||||
|
self.render_button(button_area, buf);
|
||||||
|
label_paragraph.render(label_area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_button(&self, area: Rect, buf: &mut Buffer) {
|
||||||
|
Paragraph::new(Text::from(self.title))
|
||||||
|
.block(layout_block())
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.style(style_block_highlight(self.is_selected))
|
||||||
|
.render(area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Widget for Button<'a> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer)
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
if self.label.is_some() {
|
||||||
|
self.render_labeled_button(area, buf);
|
||||||
|
} else if self.icon.is_some() {
|
||||||
|
self.render_button_with_icon(area, buf);
|
||||||
|
} else {
|
||||||
|
self.render_button(area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Styled for Button<'a> {
|
||||||
|
type Item = Button<'a>;
|
||||||
|
|
||||||
|
fn style(&self) -> Style {
|
||||||
|
self.style
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
|
||||||
|
self.style(style)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
use crate::ui::styles::ManagarrStyle;
|
||||||
|
use crate::ui::utils::{borderless_block, layout_block, style_block_highlight};
|
||||||
|
use ratatui::buffer::Buffer;
|
||||||
|
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||||
|
use ratatui::prelude::Text;
|
||||||
|
use ratatui::style::Stylize;
|
||||||
|
use ratatui::widgets::{Paragraph, Widget};
|
||||||
|
|
||||||
|
pub struct Checkbox<'a> {
|
||||||
|
label: &'a str,
|
||||||
|
is_checked: bool,
|
||||||
|
is_highlighted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Checkbox<'a> {
|
||||||
|
pub fn new(label: &'a str) -> Checkbox<'a> {
|
||||||
|
Checkbox {
|
||||||
|
label,
|
||||||
|
is_checked: false,
|
||||||
|
is_highlighted: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checked(mut self, is_checked: bool) -> Checkbox<'a> {
|
||||||
|
self.is_checked = is_checked;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn highlighted(mut self, is_selected: bool) -> Checkbox<'a> {
|
||||||
|
self.is_highlighted = is_selected;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_checkbox(&self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let check = if self.is_checked { "✔" } else { "" };
|
||||||
|
let [label_area, checkbox_area] =
|
||||||
|
Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area);
|
||||||
|
let checkbox_box_area = Rect {
|
||||||
|
width: 5,
|
||||||
|
..checkbox_area
|
||||||
|
};
|
||||||
|
|
||||||
|
Paragraph::new(Text::from(format!("\n{}: ", self.label)))
|
||||||
|
.block(borderless_block())
|
||||||
|
.alignment(Alignment::Right)
|
||||||
|
.primary()
|
||||||
|
.render(label_area, buf);
|
||||||
|
|
||||||
|
Paragraph::new(Text::from(check))
|
||||||
|
.block(layout_block())
|
||||||
|
.alignment(Alignment::Center)
|
||||||
|
.style(style_block_highlight(self.is_highlighted).bold())
|
||||||
|
.render(checkbox_box_area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Widget for Checkbox<'a> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
self.render_checkbox(area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
use ratatui::buffer::Buffer;
|
||||||
|
use ratatui::layout::{Alignment, Constraint, Layout, Rect};
|
||||||
|
use ratatui::prelude::Text;
|
||||||
|
use ratatui::style::{Style, Styled, Stylize};
|
||||||
|
use ratatui::widgets::{Block, Paragraph, Widget};
|
||||||
|
use ratatui::Frame;
|
||||||
|
|
||||||
|
use crate::ui::styles::ManagarrStyle;
|
||||||
|
use crate::ui::utils::{borderless_block, layout_block};
|
||||||
|
|
||||||
|
pub struct InputBox<'a> {
|
||||||
|
content: &'a str,
|
||||||
|
offset: usize,
|
||||||
|
style: Style,
|
||||||
|
block: Block<'a>,
|
||||||
|
label: Option<&'a str>,
|
||||||
|
cursor_after_string: bool,
|
||||||
|
is_highlighted: Option<bool>,
|
||||||
|
is_selected: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> InputBox<'a> {
|
||||||
|
pub fn new(content: &'a str) -> InputBox<'_> {
|
||||||
|
InputBox {
|
||||||
|
content,
|
||||||
|
offset: 0,
|
||||||
|
style: Style::new().default(),
|
||||||
|
block: layout_block(),
|
||||||
|
label: None,
|
||||||
|
cursor_after_string: true,
|
||||||
|
is_highlighted: None,
|
||||||
|
is_selected: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn style<S: Into<Style>>(mut self, style: S) -> InputBox<'a> {
|
||||||
|
self.style = style.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn block(mut self, block: Block<'a>) -> InputBox<'a> {
|
||||||
|
self.block = block;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn label(mut self, label: &'a str) -> InputBox<'a> {
|
||||||
|
self.label = Some(label);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn offset(mut self, offset: usize) -> InputBox<'a> {
|
||||||
|
self.offset = offset;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cursor_after_string(mut self, cursor_after_string: bool) -> InputBox<'a> {
|
||||||
|
self.cursor_after_string = cursor_after_string;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn highlighted(mut self, is_highlighted: bool) -> InputBox<'a> {
|
||||||
|
self.is_highlighted = Some(is_highlighted);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selected(mut self, is_selected: bool) -> InputBox<'a> {
|
||||||
|
self.is_selected = Some(is_selected);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_selected(&self) -> bool {
|
||||||
|
self.is_selected.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show_cursor(&self, f: &mut Frame<'_>, area: Rect) {
|
||||||
|
let area = if self.label.is_some() {
|
||||||
|
Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).split(area)[1]
|
||||||
|
} else {
|
||||||
|
area
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.cursor_after_string {
|
||||||
|
f.set_cursor(
|
||||||
|
area.x + (self.content.len() - self.offset) as u16 + 1,
|
||||||
|
area.y + 1,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
f.set_cursor(area.x + 1u16, area.y + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_input_box(&self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let style =
|
||||||
|
if matches!(self.is_highlighted, Some(true)) && matches!(self.is_selected, Some(false)) {
|
||||||
|
Style::new().system_function().bold()
|
||||||
|
} else {
|
||||||
|
self.style
|
||||||
|
};
|
||||||
|
|
||||||
|
let input_box_paragraph = Paragraph::new(Text::from(self.content))
|
||||||
|
.style(style)
|
||||||
|
.block(self.block.clone());
|
||||||
|
|
||||||
|
if let Some(label) = self.label {
|
||||||
|
let [label_area, text_box_area] =
|
||||||
|
Layout::horizontal([Constraint::Percentage(48), Constraint::Percentage(48)]).areas(area);
|
||||||
|
|
||||||
|
Paragraph::new(Text::from(format!("\n{label}: ")))
|
||||||
|
.block(borderless_block())
|
||||||
|
.alignment(Alignment::Right)
|
||||||
|
.primary()
|
||||||
|
.render(label_area, buf);
|
||||||
|
input_box_paragraph.render(text_box_area, buf);
|
||||||
|
} else {
|
||||||
|
input_box_paragraph.render(area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Widget for InputBox<'a> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer)
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.render_input_box(area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Styled for InputBox<'a> {
|
||||||
|
type Item = InputBox<'a>;
|
||||||
|
|
||||||
|
fn style(&self) -> Style {
|
||||||
|
self.style
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
|
||||||
|
self.style(style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! render_selectable_input_box {
|
||||||
|
($input_box:ident, $frame:ident, $area:ident) => {
|
||||||
|
if $input_box.is_selected() {
|
||||||
|
$input_box.show_cursor($frame, $area);
|
||||||
|
}
|
||||||
|
|
||||||
|
$frame.render_widget($input_box, $area);
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
pub(super) mod button;
|
||||||
|
pub(super) mod input_box;
|
||||||
|
pub(super) mod checkbox;
|
||||||
Reference in New Issue
Block a user