diff --git a/local/recipes/tui/tlc/source/src/filemanager/skin_dialog.rs b/local/recipes/tui/tlc/source/src/filemanager/skin_dialog.rs index a162e8319c..0da5f28e1e 100644 --- a/local/recipes/tui/tlc/source/src/filemanager/skin_dialog.rs +++ b/local/recipes/tui/tlc/source/src/filemanager/skin_dialog.rs @@ -19,11 +19,12 @@ use ratatui::layout::{Constraint, Direction, Layout, Rect}; use ratatui::style::{Modifier, Style}; use ratatui::text::{Line, Span}; -use ratatui::widgets::{Block, Borders, Clear, List, ListItem, Paragraph}; +use ratatui::widgets::{List, ListItem, Paragraph}; use ratatui::Frame; use crate::key::Key; use crate::terminal::color::{all_skins, find_skin_index, SkinEntry, Theme}; +use crate::terminal::popup::{centered_percent_rect, render_popup}; /// A skin entry surfaced in the selection dialog. The dialog stores /// the list as `Vec` (see [`crate::terminal::color::SkinEntry`]) @@ -193,23 +194,18 @@ impl SkinDialog { /// `theme` supplies the title, list, and hint colours so the /// dialog follows the active skin. The dialog itself does not /// use a hardcoded colour anywhere in its render path; every - /// style derives from the supplied `theme`. + /// style derives from the supplied `theme`. The popup shell + /// (rounded borders + MC-matching drop shadow) is rendered by + /// [`crate::terminal::popup::render_popup`] so all dialogs share + /// the same premium chrome. 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.border)) - .title(Span::styled( - format!(" {} ", crate::locale::t("dialog_title_skin")), - 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_skin"), + theme, + ); let chunks = Layout::default() .direction(Direction::Vertical) @@ -308,15 +304,6 @@ impl SkinDialog { } } -/// Center a popup of `width_pct` × `height_pct` of `area`. -fn centered_rect(area: Rect, width_pct: f32, height_pct: f32) -> Rect { - let w = (area.width as f32 * width_pct.clamp(0.1, 1.0)) as u16; - let h = (area.height as f32 * height_pct.clamp(0.1, 1.0)) as u16; - let x = area.x + area.width.saturating_sub(w) / 2; - let y = area.y + area.height.saturating_sub(h) / 2; - Rect::new(x, y, w, h) -} - #[cfg(test)] mod tests { use super::*; @@ -503,4 +490,35 @@ mod tests { }) .expect("render"); } + + #[test] + fn render_drops_shadow_outside_popup() { + // After migrating to render_popup the popup shell includes an + // MC-matching drop shadow (right strip 2 cells wide + bottom + // strip 1 row tall offset by 2 cols). Verify the cells just + // outside the popup's bottom-right carry the theme.shadow + // background. + let d = SkinDialog::new("julia256"); + let backend = ratatui::backend::TestBackend::new(80, 24); + let mut terminal = + ratatui::Terminal::new(backend).expect("create test terminal"); + terminal + .draw(|f| { + d.render(f, f.area(), &DEFAULT_THEME); + }) + .expect("render"); + let buf = terminal.backend().buffer().clone(); + let popup = centered_percent_rect(buf.area, d.width_pct, d.height_pct); + let shadow_y = popup.y + popup.height; + let shadow_x = popup.x + popup.width; + let cell = buf + .cell((shadow_x, shadow_y)) + .expect("bottom-right shadow cell exists"); + assert_eq!( + cell.bg, + DEFAULT_THEME.shadow, + "shadow cell at ({shadow_x},{shadow_y}) should carry shadow bg; got {:?}", + cell.bg + ); + } }