use std::fmt::Debug;
use crate::PopupState;
use derive_setters::Setters;
use ratatui::{
prelude::{Buffer, Line, Rect, Style, Text},
symbols::border::Set,
widgets::{Block, Borders, Clear, StatefulWidgetRef, Widget, WidgetRef},
};
use std::cmp::min;
#[derive(Debug, Setters)]
#[setters(into)]
#[non_exhaustive]
pub struct Popup<'content, W: SizedWidgetRef> {
#[setters(skip)]
pub body: W,
pub title: Line<'content>,
pub style: Style,
pub borders: Borders,
pub border_set: Set,
pub border_style: Style,
}
pub trait SizedWidgetRef: WidgetRef + Debug {
fn width(&self) -> usize;
fn height(&self) -> usize;
}
impl<'content, W: SizedWidgetRef> Popup<'content, W> {
pub fn new(body: W) -> Self {
Self {
body,
borders: Borders::ALL,
border_set: Set::default(),
border_style: Style::default(),
title: Line::default(),
style: Style::default(),
}
}
}
impl SizedWidgetRef for Text<'_> {
fn width(&self) -> usize {
self.width()
}
fn height(&self) -> usize {
self.height()
}
}
impl SizedWidgetRef for &str {
fn width(&self) -> usize {
Text::from(*self).width()
}
fn height(&self) -> usize {
Text::from(*self).height()
}
}
#[derive(Debug)]
pub struct SizedWrapper<W: Debug> {
pub inner: W,
pub width: usize,
pub height: usize,
}
impl<W: WidgetRef + Debug> WidgetRef for SizedWrapper<W> {
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
self.inner.render_ref(area, buf);
}
}
impl<W: WidgetRef + Debug> SizedWidgetRef for SizedWrapper<W> {
fn width(&self) -> usize {
self.width
}
fn height(&self) -> usize {
self.height
}
}
impl<W: SizedWidgetRef> 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<W: SizedWidgetRef> 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() {
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)
.border_set(self.border_set)
.border_style(self.border_style)
.title(self.title.clone())
.style(self.style);
block.render_ref(area, buf);
self.body.render_ref(block.inner(area), buf);
}
}
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),
}
}