use ratatui::{
layout::{Constraint, Flex, Layout, Margin, Offset},
style::{Modifier, Style},
widgets::{Block, Clear, Widget},
};
use ratatui_kit_macros::{Props, with_layout_style};
use crate::{
AnyElement, Component, ComponentTheme, Context, Palette, SystemContext,
components::theme::resolve_style,
input::{CurrentLayer, InputLayer},
layout_style::LayoutStyle,
};
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ModalTheme {
pub style: Style,
}
impl ComponentTheme for ModalTheme {
fn from_palette(_palette: &Palette) -> Self {
Self {
style: Style::new().add_modifier(Modifier::DIM),
}
}
}
impl Default for ModalTheme {
fn default() -> Self {
Self::from_palette(&Palette::default())
}
}
#[derive(Default, Clone, Copy)]
pub enum Placement {
Top,
TopLeft,
TopRight,
Bottom,
BottomLeft,
BottomRight,
#[default]
Center,
Left,
Right,
}
impl Placement {
pub fn to_flex(&self) -> [Flex; 2] {
match self {
Placement::Top => [Flex::Start, Flex::Center],
Placement::TopLeft => [Flex::Start, Flex::Start],
Placement::TopRight => [Flex::Start, Flex::End],
Placement::Bottom => [Flex::End, Flex::Center],
Placement::BottomLeft => [Flex::End, Flex::Start],
Placement::BottomRight => [Flex::End, Flex::End],
Placement::Center => [Flex::Center, Flex::Center],
Placement::Left => [Flex::Center, Flex::Start],
Placement::Right => [Flex::Center, Flex::End],
}
}
}
#[with_layout_style(margin, offset, width, height)]
#[derive(Default, Props)]
pub struct ModalProps<'a> {
pub children: Vec<AnyElement<'a>>,
pub style: Option<Style>,
pub placement: Placement,
pub open: bool,
pub layer: Option<InputLayer>,
pub blocks_lower: Option<bool>,
}
pub struct Modal {
pub open: bool,
pub margin: Margin,
pub offset: Offset,
pub width: Constraint,
pub height: Constraint,
pub placement: Placement,
pub style: Style,
}
impl Component for Modal {
type Props<'a> = ModalProps<'a>;
fn new(props: &Self::Props<'_>) -> Self {
Modal {
open: props.open,
margin: props.margin,
offset: props.offset,
width: props.width,
height: props.height,
style: Style::default(),
placement: props.placement,
}
}
fn update(
&mut self,
props: &mut Self::Props<'_>,
_hooks: crate::Hooks,
updater: &mut crate::ComponentUpdater,
) {
self.open = props.open;
self.margin = props.margin;
self.offset = props.offset;
self.width = props.width;
self.height = props.height;
self.placement = props.placement;
let theme = updater.use_component_theme::<ModalTheme>();
self.style = resolve_style(theme.style, props.style);
if self.open {
let blocks = props.blocks_lower.unwrap_or(true);
let layer_id = match props.layer {
Some(h) => h.id,
None => {
let mut sys = updater
.get_context_mut::<SystemContext>()
.expect("`SystemContext` missing (the root context always provides it)");
sys.input.push_layer(true, blocks).id
}
};
updater.update_children(
props.children.iter_mut(),
Some(Context::owned(CurrentLayer(layer_id))),
);
}
updater.set_layout_style(LayoutStyle {
width: Constraint::Length(0),
height: Constraint::Length(0),
..Default::default()
});
}
fn draw(&mut self, drawer: &mut crate::ComponentDrawer<'_, '_>) {
if self.open {
let area = drawer.buffer_mut().area();
let area = area.inner(self.margin).offset(self.offset);
let block = Block::default().style(self.style);
block.render(area, drawer.buffer_mut());
let [v, h] = self.placement.to_flex();
let vertical = Layout::vertical([self.height]).flex(v).split(area)[0];
let horizontal = Layout::horizontal([self.width]).flex(h).split(vertical)[0];
Clear.render(horizontal, drawer.buffer_mut());
drawer.area = horizontal;
}
}
}