diff --git a/src/ui/widget/container.rs b/src/ui/widget/container.rs index 68a87e6b..797cc690 100644 --- a/src/ui/widget/container.rs +++ b/src/ui/widget/container.rs @@ -11,7 +11,7 @@ use ratatui::widgets::{BorderType, Borders, Row}; use crate::ui::ext::{FooterBlock, FooterBlockType, HeaderBlock}; use crate::ui::theme::style; -use super::{Column, EventCallback, Properties, UpdateCallback, View, Widget}; +use super::{BoxedWidget, Column, EventCallback, Properties, UpdateCallback, View, Widget}; #[derive(Clone, Debug)] pub struct HeaderProps<'a> { @@ -343,3 +343,138 @@ where } } } + +#[derive(Clone, Default)] +pub struct ContainerProps { + _focus: bool, +} + +impl Properties for ContainerProps {} + +pub struct Container +where + B: Backend, +{ + /// Internal properties + props: ContainerProps, + /// Message sender + _action_tx: UnboundedSender, + /// Custom update handler + on_update: Option>, + /// Additional custom event handler + on_change: Option>, + /// Container header + header: Option>, + /// Content widget + content: Option>, + /// Container footer + footer: Option>, +} + +impl Container +where + B: Backend, +{ + pub fn header(mut self, header: BoxedWidget) -> Self { + self.header = Some(header); + self + } + + pub fn content(mut self, content: BoxedWidget) -> Self { + self.content = Some(content); + self + } + + pub fn footer(mut self, footer: BoxedWidget) -> Self { + self.footer = Some(footer); + self + } +} + +impl View for Container +where + B: Backend, +{ + fn new(_state: &S, action_tx: UnboundedSender) -> Self + where + Self: Sized, + { + Self { + _action_tx: action_tx.clone(), + props: ContainerProps::default(), + header: None, + content: None, + footer: None, + on_update: None, + on_change: None, + } + } + + fn on_update(mut self, callback: UpdateCallback) -> Self { + self.on_update = Some(callback); + self + } + + fn on_change(mut self, callback: EventCallback) -> Self { + self.on_change = Some(callback); + self + } + + fn update(&mut self, state: &S) { + self.props = self + .on_update + .and_then(|on_update| (on_update)(state).downcast_ref::().cloned()) + .unwrap_or(self.props.clone()); + + if let Some(header) = &mut self.header { + header.update(state); + } + + if let Some(content) = &mut self.content { + content.update(state); + } + + if let Some(footer) = &mut self.footer { + footer.update(state); + } + } + + fn handle_key_event(&mut self, key: termion::event::Key) { + if let Some(content) = &mut self.content { + content.handle_key_event(key); + } + } +} + +impl<'a: 'static, B, S, A> Widget for Container +where + B: Backend, +{ + fn render(&self, frame: &mut ratatui::Frame, area: Rect, props: Option<&dyn Any>) { + let _props = props + .and_then(|props| props.downcast_ref::()) + .unwrap_or(&self.props); + + let header_h = if self.header.is_some() { 3 } else { 0 }; + let footer_h = if self.footer.is_some() { 3 } else { 0 }; + + let [header_area, content_area, footer_area] = Layout::vertical([ + Constraint::Length(header_h), + Constraint::Min(1), + Constraint::Length(footer_h), + ]) + .areas(area); + + if let Some(header) = &self.header { + header.render(frame, header_area, None); + } + + if let Some(content) = &self.content { + content.render(frame, content_area, None); + } + + if let Some(footer) = &self.footer { + footer.render(frame, footer_area, None); + } + } +} diff --git a/src/ui/widget/text.rs b/src/ui/widget/text.rs index 19bab5e0..4418098c 100644 --- a/src/ui/widget/text.rs +++ b/src/ui/widget/text.rs @@ -209,7 +209,6 @@ impl<'a: 'static, S, A> View for Paragraph<'a, S, A> { } if let Some(on_change) = self.on_change { - // (on_change)(&self.props, self.action_tx.clone()); (on_change)(&self.state, self.action_tx.clone()); } }