1use ratatui::{
19 layout::{Constraint, Flex, Layout, Margin, Offset},
20 style::{Modifier, Style},
21 widgets::{Block, Clear, Widget},
22};
23use ratatui_kit_macros::{Props, with_layout_style};
24
25use crate::{
26 AnyElement, Component, ComponentTheme, Context, Palette, SystemContext,
27 components::theme::resolve_style,
28 input::{CurrentLayer, InputLayer},
29 layout_style::LayoutStyle,
30};
31
32#[non_exhaustive]
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct ModalTheme {
36 pub style: Style,
38}
39
40impl ComponentTheme for ModalTheme {
41 fn from_palette(_palette: &Palette) -> Self {
42 Self {
43 style: Style::new().add_modifier(Modifier::DIM),
44 }
45 }
46}
47
48impl Default for ModalTheme {
49 fn default() -> Self {
50 Self::from_palette(&Palette::default())
51 }
52}
53
54#[derive(Default, Clone, Copy)]
55pub enum Placement {
57 Top,
58 TopLeft,
59 TopRight,
60 Bottom,
61 BottomLeft,
62 BottomRight,
63 #[default]
64 Center,
65 Left,
66 Right,
67}
68
69impl Placement {
70 pub fn to_flex(&self) -> [Flex; 2] {
71 match self {
72 Placement::Top => [Flex::Start, Flex::Center],
73 Placement::TopLeft => [Flex::Start, Flex::Start],
74 Placement::TopRight => [Flex::Start, Flex::End],
75 Placement::Bottom => [Flex::End, Flex::Center],
76 Placement::BottomLeft => [Flex::End, Flex::Start],
77 Placement::BottomRight => [Flex::End, Flex::End],
78 Placement::Center => [Flex::Center, Flex::Center],
79 Placement::Left => [Flex::Center, Flex::Start],
80 Placement::Right => [Flex::Center, Flex::End],
81 }
82 }
83}
84
85#[with_layout_style(margin, offset, width, height)]
86#[derive(Default, Props)]
87pub struct ModalProps<'a> {
89 pub children: Vec<AnyElement<'a>>,
91 pub style: Option<Style>,
93 pub placement: Placement,
95 pub open: bool,
97 pub layer: Option<InputLayer>,
105 pub blocks_lower: Option<bool>,
107}
108
109pub struct Modal {
111 pub open: bool,
112 pub margin: Margin,
113 pub offset: Offset,
114 pub width: Constraint,
115 pub height: Constraint,
116 pub placement: Placement,
117 pub style: Style,
118}
119
120impl Component for Modal {
121 type Props<'a> = ModalProps<'a>;
122 fn new(props: &Self::Props<'_>) -> Self {
123 Modal {
124 open: props.open,
125 margin: props.margin,
126 offset: props.offset,
127 width: props.width,
128 height: props.height,
129 style: Style::default(),
131 placement: props.placement,
132 }
133 }
134
135 fn update(
136 &mut self,
137 props: &mut Self::Props<'_>,
138 _hooks: crate::Hooks,
139 updater: &mut crate::ComponentUpdater,
140 ) {
141 self.open = props.open;
142 self.margin = props.margin;
143 self.offset = props.offset;
144 self.width = props.width;
145 self.height = props.height;
146 self.placement = props.placement;
147 let theme = updater.use_component_theme::<ModalTheme>();
150 self.style = resolve_style(theme.style, props.style);
151
152 if self.open {
153 let blocks = props.blocks_lower.unwrap_or(true);
154 let layer_id = match props.layer {
157 Some(h) => h.id,
159 None => {
161 let mut sys = updater
162 .get_context_mut::<SystemContext>()
163 .expect("`SystemContext` missing (the root context always provides it)");
164 sys.input.push_layer(true, blocks).id
165 }
166 };
167
168 updater.update_children(
170 props.children.iter_mut(),
171 Some(Context::owned(CurrentLayer(layer_id))),
172 );
173 }
174
175 updater.set_layout_style(LayoutStyle {
176 width: Constraint::Length(0),
177 height: Constraint::Length(0),
178 ..Default::default()
179 });
180 }
181
182 fn draw(&mut self, drawer: &mut crate::ComponentDrawer<'_, '_>) {
183 if self.open {
184 let area = drawer.buffer_mut().area();
186 let area = area.inner(self.margin).offset(self.offset);
187
188 let block = Block::default().style(self.style);
189 block.render(area, drawer.buffer_mut());
190
191 let [v, h] = self.placement.to_flex();
192
193 let vertical = Layout::vertical([self.height]).flex(v).split(area)[0];
194 let horizontal = Layout::horizontal([self.width]).flex(h).split(vertical)[0];
195
196 Clear.render(horizontal, drawer.buffer_mut());
198 drawer.area = horizontal;
199 }
200 }
201}