multiverse/widgets/
popup_input.rs

1use crossterm::event::KeyEvent;
2use ratatui::{
3    buffer::Buffer,
4    layout::{Constraint, Flex, Layout, Rect},
5    style::{Color, Stylize, palette::tailwind},
6    widgets::{Block, Borders, Clear, Widget},
7};
8use tui_textarea::TextArea;
9
10#[derive(Default, Clone)]
11pub(crate) struct PopupInputBuilder {
12    /// Title of popup
13    title: String,
14
15    /// Placeholder text of popup
16    placeholder_text: String,
17
18    /// Width constraint text of popup
19    width_constraint: Constraint,
20
21    /// Height constraint of popup
22    height_constraint: Constraint,
23
24    /// Optional border characters used for collapsed borders
25    border_set: Option<ratatui::symbols::border::Set>,
26
27    /// Borders
28    borders: Option<Borders>,
29
30    /// Background color
31    bg: Option<Color>,
32
33    /// Foreground color
34    fg: Option<Color>,
35}
36
37impl PopupInputBuilder {
38    /// Create a new empty [`PopupInput`] widget.
39    pub fn new<T: Into<String>>(title: T, placeholder_text: T) -> Self {
40        Self {
41            title: title.into(),
42            placeholder_text: placeholder_text.into(),
43            width_constraint: Constraint::Percentage(40),
44            height_constraint: Constraint::Length(3),
45            ..Default::default()
46        }
47    }
48
49    /// Width constraint text of popup
50    pub fn width_constraint(&mut self, value: Constraint) -> &mut Self {
51        self.width_constraint = value;
52        self
53    }
54
55    /// Height constraint of popup
56    pub fn height_constraint(&mut self, value: Constraint) -> &mut Self {
57        self.height_constraint = value;
58        self
59    }
60
61    /// Set the custom border characters
62    pub fn border_set(&mut self, set: ratatui::symbols::border::Set) -> &mut Self {
63        self.border_set = Some(set);
64        self
65    }
66
67    /// Set the borders
68    pub fn borders(&mut self, borders: Borders) -> &mut Self {
69        self.borders = Some(borders);
70        self
71    }
72
73    /// Set the background color
74    pub fn bg(&mut self, color: Color) -> &mut Self {
75        self.bg = color.into();
76        self
77    }
78
79    /// Build the [`PopupInput`] from this builder
80    pub fn build(&self) -> PopupInput {
81        let mut ret = PopupInput {
82            textarea: TextArea::default(),
83            height_constraint: self.height_constraint,
84            width_constraint: self.width_constraint,
85        };
86
87        ret.textarea.set_placeholder_text(self.placeholder_text.clone());
88
89        let border_set = self.border_set.unwrap_or_default();
90        let borders = self.borders.unwrap_or_default();
91        let bg = self.bg.unwrap_or(tailwind::BLUE.c400);
92        let fg = self.fg.unwrap_or(tailwind::GRAY.c50);
93
94        let input_block = Block::new()
95            .border_set(border_set)
96            .borders(borders)
97            .bg(bg)
98            .fg(fg)
99            .title(self.title.clone());
100
101        ret.textarea.set_block(input_block);
102
103        ret
104    }
105}
106
107#[derive(Debug, Default, Clone)]
108pub(crate) struct PopupInput {
109    /// Input field for text
110    textarea: TextArea<'static>,
111
112    /// Height constraint of input
113    height_constraint: Constraint,
114
115    /// Width constraint of input
116    width_constraint: Constraint,
117}
118
119impl PopupInput {
120    /// Receive a key press event and handle it.
121    pub fn handle_key_press(&mut self, key: KeyEvent) {
122        self.textarea.input(key);
123    }
124
125    /// Get the currently input text.
126    pub fn get_input(&self) -> String {
127        self.textarea.lines().join("\n")
128    }
129}
130
131impl Widget for &mut PopupInput {
132    fn render(self, area: Rect, buf: &mut Buffer) {
133        let vertical = Layout::vertical([self.height_constraint]).flex(Flex::Center);
134        let horizontal = Layout::horizontal([self.width_constraint]).flex(Flex::Center);
135        let [area] = vertical.areas(area);
136        let [area] = horizontal.areas(area);
137        Clear.render(area, buf);
138
139        self.textarea.render(area, buf);
140    }
141}