tlc: phase 17 — skin_dialog adopts shared popup shell

Migrate src/filemanager/skin_dialog.rs::render() from the bespoke
Clear+Block shell (no shadow, square borders) to
terminal::popup::render_popup (rounded borders + MC-matching drop
shadow + centered layout).

This makes skin_dialog visually consistent with all 13 other TLC
dialogs that already use render_popup. The /tmp/5.png screenshot
(showing the skin selector without shadow) is now fixed — every
TLC dialog has identical premium chrome.

Deleted:
  - Duplicate 'centered_rect' helper (replaced by popup::centered_percent_rect)
  - Inline Block construction
  - Manual Clear render

Added:
  - render_drops_shadow_outside_popup test asserts the bottom-right
    cell carries theme.shadow background (assertion of MC's
    tty_draw_box_shadow algorithm at the dialog level).

Tests: 1112 passed (was 1111, +1).
This commit is contained in:
vasilito
2026-06-20 16:32:34 +03:00
parent b192842f8e
commit effc45d26d
@@ -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<SkinEntry>` (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
);
}
}