diff --git a/local/recipes/tui/tlc/source/src/filemanager/compare.rs b/local/recipes/tui/tlc/source/src/filemanager/compare.rs index 8e02834c67..a553ce4ad3 100644 --- a/local/recipes/tui/tlc/source/src/filemanager/compare.rs +++ b/local/recipes/tui/tlc/source/src/filemanager/compare.rs @@ -9,11 +9,12 @@ use std::path::Path; use ratatui::layout::{Alignment, Constraint, Layout, Rect}; use ratatui::style::{Modifier, Style}; use ratatui::text::{Line, Span}; -use ratatui::widgets::{Block, Borders, Clear, Paragraph}; +use ratatui::widgets::{Block, Borders, Paragraph}; use ratatui::Frame; use crate::key::Key; use crate::terminal::color::Theme; +use crate::terminal::popup::{centered_percent_rect, render_popup}; /// Maximum lines to read from each file (prevents OOM on huge files). const MAX_LINES: usize = 10_000; @@ -121,19 +122,18 @@ impl CompareDialog { /// Render the dialog. pub fn render(&self, frame: &mut Frame, area: Rect, theme: &Theme) { - let popup = centered_rect(area, 90, 80); - frame.render_widget(Clear, popup); + let popup = centered_percent_rect(area, 0.9, 0.8); + let title = if self.error.is_some() { + "Compare: error".to_string() + } else { + format!("Compare: {} ⟷ {}", self.left_name, self.right_name) + }; + let inner = render_popup(frame, popup, title.as_str(), theme); let chunks = Layout::default() .direction(ratatui::layout::Direction::Vertical) .constraints([Constraint::Length(2), Constraint::Min(1)]) - .split(popup); - - let title = if self.error.is_some() { - format!(" Compare: error ") - } else { - format!(" Compare: {} ⟷ {} ", self.left_name, self.right_name) - }; + .split(inner); let header = Paragraph::new(Line::from(vec![Span::styled( title, @@ -236,25 +236,6 @@ fn compute_diff(a: &[String], b: &[String]) -> Vec { result } -fn centered_rect(area: Rect, pct_x: u16, pct_y: u16) -> Rect { - let pop_rect = Layout::default() - .direction(ratatui::layout::Direction::Vertical) - .constraints([ - Constraint::Percentage((100 - pct_y) / 2), - Constraint::Percentage(pct_y), - Constraint::Percentage((100 - pct_y) / 2), - ]) - .split(area); - Layout::default() - .direction(ratatui::layout::Direction::Horizontal) - .constraints([ - Constraint::Percentage((100 - pct_x) / 2), - Constraint::Percentage(pct_x), - Constraint::Percentage((100 - pct_x) / 2), - ]) - .split(pop_rect[1])[1] -} - #[cfg(test)] mod tests { use super::*; diff --git a/local/recipes/tui/tlc/source/src/filemanager/config_dialog.rs b/local/recipes/tui/tlc/source/src/filemanager/config_dialog.rs index a24d875fa3..09ae7eaa2a 100644 --- a/local/recipes/tui/tlc/source/src/filemanager/config_dialog.rs +++ b/local/recipes/tui/tlc/source/src/filemanager/config_dialog.rs @@ -22,11 +22,12 @@ use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::style::{Modifier, Style}; use ratatui::text::{Line, Span}; -use ratatui::widgets::{Block, Borders, Clear, Paragraph}; +use ratatui::widgets::Paragraph; use ratatui::Frame; use crate::key::Key; use crate::terminal::color::Theme; +use crate::terminal::popup::{centered_cols_rect, render_popup}; /// The result of the configuration dialog after a key event. #[derive(Debug, Clone, PartialEq, Eq)] @@ -181,25 +182,8 @@ impl ConfigDialog { /// Render the dialog centered on `area`. pub fn render(&self, frame: &mut Frame, area: Rect, theme: &Theme) { - let w = 44u16.min(area.width.saturating_sub(2)); - let h = 12u16.min(area.height.saturating_sub(2)); - let x = area.x + (area.width - w) / 2; - let y = area.y + (area.height - h) / 2; - let dlg = Rect::new(x, y, w, h); - frame.render_widget(Clear, dlg); - - let block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(theme.title_fg)) - .title(Span::styled( - " Configuration ", - Style::default() - .fg(theme.title_fg) - .bg(theme.title_bg) - .add_modifier(Modifier::BOLD), - )); - let inner = block.inner(dlg); - frame.render_widget(block, dlg); + let dlg = centered_cols_rect(area, 44, 12); + let inner = render_popup(frame, dlg, "Configuration", theme); let rows = Layout::default() .direction(Direction::Vertical) diff --git a/local/recipes/tui/tlc/source/src/filemanager/connection_dialog.rs b/local/recipes/tui/tlc/source/src/filemanager/connection_dialog.rs index 99b733f8c6..f6b0641d2c 100644 --- a/local/recipes/tui/tlc/source/src/filemanager/connection_dialog.rs +++ b/local/recipes/tui/tlc/source/src/filemanager/connection_dialog.rs @@ -19,11 +19,12 @@ use std::path::PathBuf; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::style::{Modifier, Style}; use ratatui::text::{Line, Span}; -use ratatui::widgets::{Block, Borders, Clear, Paragraph}; +use ratatui::widgets::Paragraph; use ratatui::Frame; use crate::key::Key; use crate::terminal::color::Theme; +use crate::terminal::popup::{centered_cols_rect, render_popup}; use crate::vfs::VfsPath; use crate::widget::input::Input; @@ -205,25 +206,8 @@ impl ConnectionDialog { /// Render the dialog centered on `area`. pub fn render(&self, frame: &mut Frame, area: Rect, theme: &Theme) { - let w = 60u16.min(area.width.saturating_sub(2)); - let h = 8u16.min(area.height.saturating_sub(2)); - let x = area.x + (area.width.saturating_sub(w)) / 2; - let y = area.y + (area.height.saturating_sub(h)) / 2; - let dlg = Rect::new(x, y, w, h); - frame.render_widget(Clear, dlg); - - let block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(theme.title_fg)) - .title(Span::styled( - self.kind.title(), - Style::default() - .fg(theme.title_fg) - .bg(theme.title_bg) - .add_modifier(Modifier::BOLD), - )); - let inner = block.inner(dlg); - frame.render_widget(block, dlg); + let dlg = centered_cols_rect(area, 60, 8); + let inner = render_popup(frame, dlg, self.kind.title(), theme); let chunks = Layout::default() .direction(Direction::Vertical) diff --git a/local/recipes/tui/tlc/source/src/filemanager/owner.rs b/local/recipes/tui/tlc/source/src/filemanager/owner.rs index 327034ccb8..5bffd2605f 100644 --- a/local/recipes/tui/tlc/source/src/filemanager/owner.rs +++ b/local/recipes/tui/tlc/source/src/filemanager/owner.rs @@ -14,11 +14,12 @@ use std::path::PathBuf; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::style::{Modifier, Style}; use ratatui::text::{Line, Span}; -use ratatui::widgets::{Block, Borders, Clear, Paragraph, Wrap}; +use ratatui::widgets::{Paragraph, Wrap}; use ratatui::Frame; use crate::key::Key; use crate::terminal::color::Theme; +use crate::terminal::popup::{centered_percent_rect, render_popup}; use crate::widget::input::Input; /// Which field currently has focus. @@ -170,21 +171,13 @@ impl OwnerDialog { /// `theme` supplies the title, body, and hint colours so the /// dialog follows the active skin. pub fn render(&self, frame: &mut Frame, area: Rect, theme: &Theme) { - let popup = centered_rect(area, self.width_pct, self.height_pct); - frame.render_widget(Clear, popup); - - let block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(theme.title_fg)) - .title(Span::styled( - format!(" {} ", crate::locale::t("dialog_title_owner")), - Style::default() - .fg(theme.title_fg) - .bg(theme.title_bg) - .add_modifier(Modifier::BOLD), - )); - let inner = block.inner(popup); - frame.render_widget(block, popup); + let popup = centered_percent_rect(area, self.width_pct, self.height_pct); + let inner = render_popup( + frame, + popup, + crate::locale::t("dialog_title_owner"), + theme, + ); let chunks = Layout::default() .direction(Direction::Vertical) diff --git a/local/recipes/tui/tlc/source/src/filemanager/permission.rs b/local/recipes/tui/tlc/source/src/filemanager/permission.rs index 71f7d95fa6..c994f2eafc 100644 --- a/local/recipes/tui/tlc/source/src/filemanager/permission.rs +++ b/local/recipes/tui/tlc/source/src/filemanager/permission.rs @@ -15,11 +15,12 @@ use std::path::PathBuf; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::style::{Modifier, Style}; use ratatui::text::{Line, Span}; -use ratatui::widgets::{Block, Borders, Clear, Paragraph, Wrap}; +use ratatui::widgets::{Paragraph, Wrap}; use ratatui::Frame; use crate::key::Key; use crate::terminal::color::Theme; +use crate::terminal::popup::{centered_percent_rect, render_popup}; /// One cell in the 3x3 permission grid. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -211,21 +212,13 @@ impl PermissionDialog { /// `theme` supplies the title, border, hint, and button colours so /// the dialog follows the active skin. pub fn render(&self, frame: &mut Frame, area: Rect, theme: &Theme) { - let popup = centered_rect(area, self.width_pct, self.height_pct); - frame.render_widget(Clear, popup); - - let block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(theme.title_fg)) - .title(Span::styled( - format!(" {} ", crate::locale::t("dialog_title_permission")), - Style::default() - .fg(theme.title_fg) - .bg(theme.title_bg) - .add_modifier(Modifier::BOLD), - )); - let inner = block.inner(popup); - frame.render_widget(block, popup); + let popup = centered_percent_rect(area, self.width_pct, self.height_pct); + let inner = render_popup( + frame, + popup, + crate::locale::t("dialog_title_permission"), + theme, + ); // Inner split: header line, 3-row grid, hint line. let chunks = Layout::default() diff --git a/local/recipes/tui/tlc/source/src/filemanager/sort_dialog.rs b/local/recipes/tui/tlc/source/src/filemanager/sort_dialog.rs index ef83166edb..dd5a268ea3 100644 --- a/local/recipes/tui/tlc/source/src/filemanager/sort_dialog.rs +++ b/local/recipes/tui/tlc/source/src/filemanager/sort_dialog.rs @@ -3,7 +3,7 @@ use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::style::{Modifier, Style}; use ratatui::text::Span; -use ratatui::widgets::{Block, Borders, Clear, Paragraph}; +use ratatui::widgets::Paragraph; use ratatui::Frame; use crate::filemanager::panel::SortField; @@ -130,25 +130,8 @@ impl SortDialog { pub fn render(&self, frame: &mut Frame, area: Rect, theme: &Theme) { let w = 36u16.min(area.width.saturating_sub(2)); let h = 12u16.min(area.height.saturating_sub(2)); - let x = area.x + (area.width - w) / 2; - let y = area.y + (area.height - h) / 2; - let dlg = Rect::new(x, y, w, h); - frame.render_widget(Clear, dlg); - - let block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(theme.border)) - .title(Span::styled( - " Sort Order ", - Style::default() - .fg(theme.title_fg) - .bg(theme.title_bg) - .add_modifier(Modifier::BOLD), - )) - .style(Style::default().bg(theme.background).fg(theme.foreground)); - - let inner = block.inner(dlg); - frame.render_widget(block, dlg); + let dlg = crate::terminal::popup::centered_cols_rect(area, w, h); + let inner = crate::terminal::popup::render_popup(frame, dlg, "Sort Order", theme); let mut constraints = vec![Constraint::Length(1)]; for _ in 0..FIELDS.len() { diff --git a/local/recipes/tui/tlc/source/src/ops/progress.rs b/local/recipes/tui/tlc/source/src/ops/progress.rs index 446f220e3b..eb632380c5 100644 --- a/local/recipes/tui/tlc/source/src/ops/progress.rs +++ b/local/recipes/tui/tlc/source/src/ops/progress.rs @@ -7,12 +7,13 @@ use std::time::Instant; use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::style::{Modifier, Style}; use ratatui::text::{Line, Span}; -use ratatui::widgets::{Block, Borders, Clear, Paragraph, Wrap}; +use ratatui::widgets::{Paragraph, Wrap}; use ratatui::Frame; use crate::ops::OpHandle; use crate::terminal::color::Theme; +use crate::terminal::popup::{centered_percent_rect, render_popup}; use crate::widget::ProgressGauge; /// The progress dialog. Renders a running operation with a @@ -57,21 +58,8 @@ impl ProgressDialog { /// `theme` supplies the title, current file, gauge, and cancel /// button colours so the progress dialog follows the active skin. pub fn render(&self, frame: &mut Frame, area: Rect, theme: &Theme) { - let popup = centered_rect(area, self.width_pct, self.height_pct); - frame.render_widget(Clear, popup); - - let block = Block::default() - .borders(Borders::ALL) - .border_style(Style::default().fg(theme.title_fg)) - .title(Span::styled( - format!(" {} ", self.title), - Style::default() - .fg(theme.title_fg) - .bg(theme.title_bg) - .add_modifier(Modifier::BOLD), - )); - let inner = block.inner(popup); - frame.render_widget(block, popup); + let popup = centered_percent_rect(area, self.width_pct, self.height_pct); + let inner = render_popup(frame, popup, self.title.as_str(), theme); let chunks = Layout::default() .direction(Direction::Vertical)