ratatui_kit/components/
modal.rs1use ratatui::{
19 layout::{Constraint, Flex, Layout, Margin, Offset},
20 style::Style,
21 widgets::{Block, Clear, Widget},
22};
23use ratatui_kit_macros::{Props, with_layout_style};
24
25use crate::{
26 AnyElement, Component, Context, SystemContext,
27 input::{CurrentLayer, InputLayer},
28 layout_style::LayoutStyle,
29};
30
31#[derive(Default, Clone, Copy)]
32pub enum Placement {
34 Top,
35 TopLeft,
36 TopRight,
37 Bottom,
38 BottomLeft,
39 BottomRight,
40 #[default]
41 Center,
42 Left,
43 Right,
44}
45
46impl Placement {
47 pub fn to_flex(&self) -> [Flex; 2] {
48 match self {
49 Placement::Top => [Flex::Start, Flex::Center],
50 Placement::TopLeft => [Flex::Start, Flex::Start],
51 Placement::TopRight => [Flex::Start, Flex::End],
52 Placement::Bottom => [Flex::End, Flex::Center],
53 Placement::BottomLeft => [Flex::End, Flex::Start],
54 Placement::BottomRight => [Flex::End, Flex::End],
55 Placement::Center => [Flex::Center, Flex::Center],
56 Placement::Left => [Flex::Center, Flex::Start],
57 Placement::Right => [Flex::Center, Flex::End],
58 }
59 }
60}
61
62#[with_layout_style(margin, offset, width, height)]
63#[derive(Default, Props)]
64pub struct ModalProps<'a> {
66 pub children: Vec<AnyElement<'a>>,
68 pub style: Style,
70 pub placement: Placement,
72 pub open: bool,
74 pub layer: Option<InputLayer>,
82 pub blocks_lower: Option<bool>,
84}
85
86pub struct Modal {
88 pub open: bool,
89 pub margin: Margin,
90 pub offset: Offset,
91 pub width: Constraint,
92 pub height: Constraint,
93 pub placement: Placement,
94 pub style: Style,
95}
96
97impl Component for Modal {
98 type Props<'a> = ModalProps<'a>;
99 fn new(props: &Self::Props<'_>) -> Self {
100 Modal {
101 open: props.open,
102 margin: props.margin,
103 offset: props.offset,
104 width: props.width,
105 height: props.height,
106 style: props.style,
107 placement: props.placement,
108 }
109 }
110
111 fn update(
112 &mut self,
113 props: &mut Self::Props<'_>,
114 _hooks: crate::Hooks,
115 updater: &mut crate::ComponentUpdater,
116 ) {
117 self.open = props.open;
118 self.margin = props.margin;
119 self.offset = props.offset;
120 self.width = props.width;
121 self.height = props.height;
122 self.style = props.style;
123 self.placement = props.placement;
124
125 if self.open {
126 let blocks = props.blocks_lower.unwrap_or(true);
127 let layer_id = match props.layer {
130 Some(h) => h.id,
132 None => {
134 let mut sys = updater
135 .get_context_mut::<SystemContext>()
136 .expect("SystemContext 缺失(根 context 必有)");
137 sys.input.push_layer(true, blocks).id
138 }
139 };
140
141 updater.update_children(
143 props.children.iter_mut(),
144 Some(Context::owned(CurrentLayer(layer_id))),
145 );
146 }
147
148 updater.set_layout_style(LayoutStyle {
149 width: Constraint::Length(0),
150 height: Constraint::Length(0),
151 ..Default::default()
152 });
153 }
154
155 fn draw(&mut self, drawer: &mut crate::ComponentDrawer<'_, '_>) {
156 if self.open {
157 let area = drawer.buffer_mut().area();
159 let area = area.inner(self.margin).offset(self.offset);
160
161 let block = Block::default().style(self.style);
162 block.render(area, drawer.buffer_mut());
163
164 let [v, h] = self.placement.to_flex();
165
166 let vertical = Layout::vertical([self.height]).flex(v).split(area)[0];
167 let horizontal = Layout::horizontal([self.width]).flex(h).split(vertical)[0];
168
169 Clear.render(horizontal, drawer.buffer_mut());
171 drawer.area = horizontal;
172 }
173 }
174}