diff --git a/examples/demo.rs b/examples/demo.rs index b4a9a41..ddb1ee5 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -25,8 +25,9 @@ fn main() -> Result<()> { fn render(frame: &mut Frame) { let area = frame.size(); let background = background(area); - let popup = - Popup::new("tui-popup demo", "Press any key to exit").style(Style::new().white().on_blue()); + let popup = Popup::new("Press any key to exit") + .title("tui-popup demo") + .style(Style::new().white().on_blue()); frame.render_widget(background, area); frame.render_widget(&popup, area); } diff --git a/examples/paragraph.rs b/examples/paragraph.rs index 979948f..8d98e13 100644 --- a/examples/paragraph.rs +++ b/examples/paragraph.rs @@ -33,8 +33,9 @@ impl App { let background = background(area); let paragraph = paragraph(self.scroll); - let popup = - Popup::new("scroll: ↑/↓ quit: Esc", paragraph).style(Style::new().white().on_blue()); + let popup = Popup::new(paragraph) + .title("scroll: ↑/↓ quit: Esc") + .style(Style::new().white().on_blue()); frame.render_widget(background, area); frame.render_widget(&popup, area); diff --git a/examples/state.rs b/examples/state.rs index f5eb74c..eacab36 100644 --- a/examples/state.rs +++ b/examples/state.rs @@ -50,17 +50,15 @@ impl App { } fn popup_widget() -> Popup<'static, Text<'static>> { - Popup::new( - "Popup", - Text::from_iter([ - "q: exit", - "r: reset", - "j: move down", - "k: move up", - "h: move left", - "l: move right", - ]), - ) + Popup::new(Text::from_iter([ + "q: exit", + "r: reset", + "j: move down", + "k: move up", + "h: move left", + "l: move right", + ])) + .title("Popup") .style(Style::new().white().on_blue()) } diff --git a/src/lib.rs b/src/lib.rs index fa3119e..2d352a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,8 @@ //! use tui_popup::Popup; //! //! fn render_popup(frame: &mut Frame) { -//! let popup = Popup::new("tui-popup demo", "Press any key to exit") +//! let popup = Popup::new("Press any key to exit") +//! .title("tui-popup demo") //! .style(Style::new().white().on_blue()); //! frame.render_widget(&popup, frame.size()); //! } @@ -23,7 +24,6 @@ mod popup; mod state; -mod widget; pub use popup::*; pub use state::*; diff --git a/src/popup.rs b/src/popup.rs index 7529fbb..e099c9b 100644 --- a/src/popup.rs +++ b/src/popup.rs @@ -1,10 +1,12 @@ use std::fmt::Debug; +use crate::PopupState; use derive_setters::Setters; use ratatui::{ - prelude::*, - widgets::{Borders, WidgetRef}, + prelude::{Buffer, Line, Rect, Style, Text}, + widgets::{Block, Borders, Clear, StatefulWidgetRef, Widget, WidgetRef}, }; +use std::cmp::min; /// Configuration for a popup. /// @@ -18,7 +20,8 @@ use ratatui::{ /// use tui_popup::Popup; /// /// fn render_popup(frame: &mut Frame) { -/// let popup = Popup::new("tui-popup demo", "Press any key to exit") +/// let popup = Popup::new("Press any key to exit") +/// .title("tui-popup demo") /// .style(Style::new().white().on_blue()); /// frame.render_widget(&popup, frame.size()); /// } @@ -28,6 +31,7 @@ use ratatui::{ #[non_exhaustive] pub struct Popup<'content, W: SizedWidgetRef> { /// The body of the popup. + #[setters(skip)] pub body: W, /// The title of the popup. pub title: Line<'content>, @@ -53,8 +57,6 @@ impl<'content, W: SizedWidgetRef> Popup<'content, W> { /// /// # Parameters /// - /// - `title` - The title of the popup. This can be any type that can be converted into a - /// [`Line`]. /// - `body` - The body of the popup. This can be any type that can be converted into a /// [`Text`]. /// @@ -63,17 +65,14 @@ impl<'content, W: SizedWidgetRef> Popup<'content, W> { /// ```rust /// use tui_popup::Popup; /// - /// let popup = Popup::new("tui-popup demo", "Press any key to exit"); + /// let popup = Popup::new("Press any key to exit").title("tui-popup demo"); /// ``` - pub fn new(title: L, body: W) -> Self - where - L: Into>, - { + pub fn new(body: W) -> Self { Self { body, - title: title.into(), - style: Style::default(), borders: Borders::ALL, + title: Line::default(), + style: Style::default(), } } } @@ -120,3 +119,65 @@ impl SizedWidgetRef for SizedWrapper { self.height } } + +impl WidgetRef for Popup<'_, W> { + fn render_ref(&self, area: Rect, buf: &mut Buffer) { + let mut state = PopupState::default(); + StatefulWidgetRef::render_ref(self, area, buf, &mut state); + } +} + +impl StatefulWidgetRef for Popup<'_, W> { + type State = PopupState; + + fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { + let area = if let Some(next) = state.area.take() { + // ensure that the popup remains on screen + let width = min(next.width, area.width); + let height = min(next.height, area.height); + let x = next.x.clamp(buf.area.x, area.right() - width); + let y = next.y.clamp(buf.area.y, area.bottom() - height); + + Rect::new(x, y, width, height) + } else { + let border_height = usize::from(self.borders.intersects(Borders::TOP)) + + usize::from(self.borders.intersects(Borders::BOTTOM)); + let border_width = usize::from(self.borders.intersects(Borders::LEFT)) + + usize::from(self.borders.intersects(Borders::RIGHT)); + + let height = self + .body + .height() + .saturating_add(border_height) + .try_into() + .unwrap_or(area.height); + let width = self + .body + .width() + .saturating_add(border_width) + .try_into() + .unwrap_or(area.width); + centered_rect(width, height, area) + }; + + state.area.replace(area); + + Clear.render(area, buf); + let block = Block::default() + .borders(self.borders) + .title(self.title.clone()) + .style(self.style); + block.render_ref(area, buf); + self.body.render_ref(block.inner(area), buf); + } +} + +/// Create a rectangle centered in the given area. +fn centered_rect(width: u16, height: u16, area: Rect) -> Rect { + Rect { + x: area.width.saturating_sub(width) / 2, + y: area.height.saturating_sub(height) / 2, + width: min(width, area.width), + height: min(height, area.height), + } +} diff --git a/src/widget.rs b/src/widget.rs deleted file mode 100644 index 66ea5fc..0000000 --- a/src/widget.rs +++ /dev/null @@ -1,70 +0,0 @@ -use std::cmp::min; - -use ratatui::{ - prelude::*, - widgets::{Block, Borders, Clear, StatefulWidgetRef, Widget, WidgetRef}, -}; - -use crate::{popup::SizedWidgetRef, Popup, PopupState}; - -impl WidgetRef for Popup<'_, W> { - fn render_ref(&self, area: Rect, buf: &mut Buffer) { - let mut state = PopupState::default(); - StatefulWidgetRef::render_ref(self, area, buf, &mut state); - } -} - -impl StatefulWidgetRef for Popup<'_, W> { - type State = PopupState; - - fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) { - let area = if let Some(next) = state.area.take() { - // ensure that the popup remains on screen - let width = min(next.width, area.width); - let height = min(next.height, area.height); - let x = next.x.clamp(buf.area.x, area.right() - width); - let y = next.y.clamp(buf.area.y, area.bottom() - height); - - Rect::new(x, y, width, height) - } else { - let border_height = usize::from(self.borders.intersects(Borders::TOP)) - + usize::from(self.borders.intersects(Borders::BOTTOM)); - let border_width = usize::from(self.borders.intersects(Borders::LEFT)) - + usize::from(self.borders.intersects(Borders::RIGHT)); - - let height = self - .body - .height() - .saturating_add(border_height) - .try_into() - .unwrap_or(area.height); - let width = self - .body - .width() - .saturating_add(border_width) - .try_into() - .unwrap_or(area.width); - centered_rect(width, height, area) - }; - - state.area.replace(area); - - Clear.render(area, buf); - let block = Block::default() - .borders(self.borders) - .title(self.title.clone()) - .style(self.style); - block.render_ref(area, buf); - self.body.render_ref(block.inner(area), buf); - } -} - -/// Create a rectangle centered in the given area. -fn centered_rect(width: u16, height: u16, area: Rect) -> Rect { - Rect { - x: area.width.saturating_sub(width) / 2, - y: area.height.saturating_sub(height) / 2, - width: min(width, area.width), - height: min(height, area.height), - } -}