retroglyph_widgets/widget/
modal.rs1use retroglyph_core::{Backend, 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(mut self, theme: Theme) -> Self {
86 self.border_style = Style::new().fg(theme.border).bg(theme.title_bg);
87 self.fill_style = Style::new().bg(theme.panel_bg);
88 self
89 }
90
91 pub fn render<B: Backend>(self, screen: Rect, term: &mut Terminal<B>) -> Rect {
94 let rect = centered_rect(screen, self.width, self.height);
95 let mut panel = Panel::new()
96 .border_style(self.border_style)
97 .fill_style(self.fill_style)
98 .title_align(self.title_align);
99 if let Some(title) = self.title {
100 panel = panel.title(title);
101 }
102 panel.render(rect, term);
103 Rect::new(
104 rect.left() + 1,
105 rect.top() + 1,
106 rect.width().saturating_sub(2),
107 rect.height().saturating_sub(2),
108 )
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use retroglyph_core::Headless;
115
116 use super::*;
117
118 #[test]
119 fn centers_the_box_and_returns_the_inner_content_rect() {
120 let screen = Rect::new(0, 0, 20, 10);
121 let mut term = Terminal::new(Headless::new(20, 10));
122 let inner = Modal::new(10, 4).render(screen, &mut term);
123
124 assert_eq!(inner, Rect::new(6, 4, 8, 2));
127 assert_eq!(term.grid().get(5, 3).glyph(), '┌');
129 assert_eq!(term.grid().get(14, 3).glyph(), '┐');
130 }
131
132 #[test]
133 fn draws_only_the_box_leaving_the_rest_of_the_screen_untouched() {
134 let screen = Rect::new(0, 0, 20, 10);
135 let mut term = Terminal::new(Headless::new(20, 10));
136 Modal::new(10, 4).render(screen, &mut term);
137
138 assert_eq!(term.grid().get(0, 0).glyph(), ' ');
140 }
141
142 #[test]
143 fn theme_maps_named_roles_onto_border_and_fill() {
144 let screen = Rect::new(0, 0, 20, 10);
145 let mut term = Terminal::new(Headless::new(20, 10));
146 Modal::new(10, 4)
147 .theme(Theme::DARK)
148 .render(screen, &mut term);
149
150 assert_eq!(
152 term.grid().get(5, 3).style().foreground(),
153 Theme::DARK.border
154 );
155 assert_eq!(
156 term.grid().get(5, 3).style().background(),
157 Theme::DARK.title_bg
158 );
159 assert_eq!(
160 term.grid().get(6, 4).style().background(),
161 Theme::DARK.panel_bg
162 );
163 }
164}