retroglyph_widgets/widget/
modal.rs1use retroglyph_core::{Backend, Color, Rect, Style, Terminal};
3
4use super::{Panel, Widget};
5use crate::layout::centered_rect;
6use crate::{Align, Theme};
7
8#[derive(Clone, Copy, Debug)]
26pub struct Modal<'a> {
27 width: u16,
28 height: u16,
29 title: Option<&'a str>,
30 title_align: Align,
31 border_style: Style,
32 fill_style: Style,
33}
34
35impl<'a> Modal<'a> {
36 #[must_use]
38 pub fn new(width: u16, height: u16) -> Self {
39 Self {
40 width,
41 height,
42 title: None,
43 title_align: Align::Center,
44 border_style: Style::new(),
45 fill_style: Style::new(),
46 }
47 }
48
49 #[must_use]
51 pub const fn title(mut self, title: &'a str) -> Self {
52 self.title = Some(title);
53 self
54 }
55
56 #[must_use]
59 pub const fn title_align(mut self, align: Align) -> Self {
60 self.title_align = align;
61 self
62 }
63
64 #[must_use]
66 pub const fn border_style(mut self, style: Style) -> Self {
67 self.border_style = style;
68 self
69 }
70
71 #[must_use]
73 pub const fn fill_style(mut self, style: Style) -> Self {
74 self.fill_style = style;
75 self
76 }
77
78 #[must_use]
85 pub fn theme(self, theme: Theme) -> Self {
86 self.theme_on(theme, theme.panel_bg)
87 }
88
89 #[must_use]
94 pub fn theme_on(mut self, theme: Theme, bg: Color) -> Self {
95 self.border_style = Style::new().fg(theme.border).bg(theme.title_bg);
96 self.fill_style = Style::new().bg(bg);
97 self
98 }
99
100 pub fn render<B: Backend>(self, screen: Rect, term: &mut Terminal<B>) -> Rect {
103 let rect = centered_rect(screen, self.width, self.height);
104 let mut panel = Panel::new()
105 .border_style(self.border_style)
106 .fill_style(self.fill_style)
107 .title_align(self.title_align);
108 if let Some(title) = self.title {
109 panel = panel.title(title);
110 }
111 panel.render(rect, term);
112 Rect::new(
113 rect.left() + 1,
114 rect.top() + 1,
115 rect.width().saturating_sub(2),
116 rect.height().saturating_sub(2),
117 )
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use retroglyph_core::Headless;
124
125 use super::*;
126
127 #[test]
128 fn centers_the_box_and_returns_the_inner_content_rect() {
129 let screen = Rect::new(0, 0, 20, 10);
130 let mut term = Terminal::new(Headless::new(20, 10));
131 let inner = Modal::new(10, 4).render(screen, &mut term);
132
133 assert_eq!(inner, Rect::new(6, 4, 8, 2));
136 assert_eq!(term.grid().get(5, 3).glyph(), '┌');
138 assert_eq!(term.grid().get(14, 3).glyph(), '┐');
139 }
140
141 #[test]
142 fn draws_only_the_box_leaving_the_rest_of_the_screen_untouched() {
143 let screen = Rect::new(0, 0, 20, 10);
144 let mut term = Terminal::new(Headless::new(20, 10));
145 Modal::new(10, 4).render(screen, &mut term);
146
147 assert_eq!(term.grid().get(0, 0).glyph(), ' ');
149 }
150
151 #[test]
152 fn theme_maps_named_roles_onto_border_and_fill() {
153 let screen = Rect::new(0, 0, 20, 10);
154 let mut term = Terminal::new(Headless::new(20, 10));
155 Modal::new(10, 4)
156 .theme(Theme::DARK)
157 .render(screen, &mut term);
158
159 assert_eq!(
161 term.grid().get(5, 3).style().foreground(),
162 Theme::DARK.border
163 );
164 assert_eq!(
165 term.grid().get(5, 3).style().background(),
166 Theme::DARK.title_bg
167 );
168 assert_eq!(
169 term.grid().get(6, 4).style().background(),
170 Theme::DARK.panel_bg
171 );
172 }
173
174 #[test]
175 fn theme_on_uses_the_given_backdrop_instead_of_panel_bg() {
176 let screen = Rect::new(0, 0, 20, 10);
177 let mut term = Terminal::new(Headless::new(20, 10));
178 Modal::new(10, 4)
179 .theme_on(Theme::DARK, Color::Default)
180 .render(screen, &mut term);
181
182 assert_eq!(
183 term.grid().get(5, 3).style().foreground(),
184 Theme::DARK.border
185 );
186 assert_eq!(
187 term.grid().get(5, 3).style().background(),
188 Theme::DARK.title_bg
189 );
190 assert_eq!(term.grid().get(6, 4).style().background(), Color::Default);
191 }
192}