Skip to main content

multiverse/widgets/
button.rs

1#![allow(unused)]
2
3use crossterm::event::{KeyCode, KeyEvent};
4use ratatui::{prelude::*, widgets::Widget};
5
6#[derive(Debug, Clone)]
7pub struct Button<'text> {
8    text: Text<'text>,
9    theme: Theme,
10    state: State,
11}
12
13#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
14pub enum State {
15    #[default]
16    Normal,
17    Selected,
18    Pressed,
19}
20
21#[derive(Debug, Clone, Copy)]
22pub struct Theme {
23    normal_text: Color,
24    normal_background: Color,
25    selected_text: Color,
26    selected_background: Color,
27    pressed_text: Color,
28    pressed_background: Color,
29    highlight: Color,
30    shadow: Color,
31}
32
33impl Default for Theme {
34    fn default() -> Self {
35        themes::NORMAL
36    }
37}
38
39/// Config
40impl<'text> Button<'text> {
41    pub fn new<T: Into<Text<'text>>>(text: T) -> Self {
42        Self { text: text.into(), theme: Theme::default(), state: State::default() }
43    }
44
45    pub fn with_theme(mut self, theme: Theme) -> Self {
46        self.theme = theme;
47        self
48    }
49}
50
51impl Button<'_> {
52    pub fn toggle_press(&mut self) {
53        match self.state {
54            State::Normal => self.press(),
55            State::Selected => self.press(),
56            State::Pressed => self.select(),
57        }
58    }
59
60    pub fn press(&mut self) {
61        self.state = State::Pressed;
62    }
63
64    pub fn normal(&mut self) {
65        self.state = State::Normal;
66    }
67
68    pub fn select(&mut self) {
69        self.state = State::Selected;
70    }
71
72    fn handle_key(&mut self, key_event: KeyEvent) {
73        match key_event.code {
74            KeyCode::Char(' ') | KeyCode::Enter => self.toggle_press(),
75            _ => {}
76        }
77    }
78}
79
80impl Widget for &Button<'_> {
81    fn render(self, area: Rect, buf: &mut Buffer) {
82        let theme = self.theme;
83
84        // these are wrong
85        let fg = match self.state {
86            State::Normal => theme.normal_text,
87            State::Selected => theme.selected_text,
88            State::Pressed => theme.pressed_text,
89        };
90        let bg = match self.state {
91            State::Normal => theme.normal_background,
92            State::Selected => theme.selected_background,
93            State::Pressed => theme.pressed_background,
94        };
95        let (top, bottom) = if self.state == State::Pressed {
96            (theme.shadow, theme.highlight)
97        } else {
98            (theme.highlight, theme.shadow)
99        };
100
101        buf.set_style(area, (fg, bg));
102
103        let rows = area.rows().collect::<Vec<_>>();
104        let last_index = rows.len().saturating_sub(1);
105        let (first, middle, last) = match rows.len() {
106            0 | 1 => (None, &rows[..], None),
107            2 => (None, &rows[..last_index], Some(rows[last_index])),
108            _ => (Some(rows[0]), &rows[1..last_index], Some(rows[last_index])),
109        };
110
111        // render top line if there's enough space
112        if let Some(first) = first {
113            "▔".repeat(area.width as usize).fg(top).bg(bg).render(first, buf);
114        }
115        // render bottom line if there's enough space
116        if let Some(last) = last {
117            "▁".repeat(area.width as usize).fg(bottom).bg(bg).render(last, buf);
118        }
119        self.text.clone().centered().render(middle[0], buf);
120    }
121}
122
123pub mod themes {
124    use ratatui::style::palette::tailwind;
125
126    use super::Theme;
127
128    pub const NORMAL: Theme = Theme {
129        normal_text: tailwind::GRAY.c200,
130        normal_background: tailwind::GRAY.c800,
131        selected_text: tailwind::GRAY.c100,
132        selected_background: tailwind::GRAY.c700,
133        pressed_text: tailwind::GRAY.c300,
134        pressed_background: tailwind::GRAY.c900,
135        highlight: tailwind::GRAY.c600,
136        shadow: tailwind::GRAY.c950,
137    };
138
139    pub const RED: Theme = Theme {
140        normal_text: tailwind::RED.c200,
141        normal_background: tailwind::RED.c800,
142        selected_text: tailwind::RED.c100,
143        selected_background: tailwind::RED.c700,
144        pressed_text: tailwind::RED.c300,
145        pressed_background: tailwind::RED.c900,
146        highlight: tailwind::RED.c600,
147        shadow: tailwind::RED.c950,
148    };
149
150    pub const GREEN: Theme = Theme {
151        normal_text: tailwind::GREEN.c200,
152        normal_background: tailwind::GREEN.c800,
153        selected_text: tailwind::GREEN.c100,
154        selected_background: tailwind::GREEN.c700,
155        pressed_text: tailwind::GREEN.c300,
156        pressed_background: tailwind::GREEN.c900,
157        highlight: tailwind::GREEN.c600,
158        shadow: tailwind::GREEN.c950,
159    };
160
161    pub const BLUE: Theme = Theme {
162        normal_text: tailwind::BLUE.c200,
163        normal_background: tailwind::BLUE.c800,
164        selected_text: tailwind::BLUE.c100,
165        selected_background: tailwind::BLUE.c700,
166        pressed_text: tailwind::BLUE.c300,
167        pressed_background: tailwind::BLUE.c900,
168        highlight: tailwind::BLUE.c600,
169        shadow: tailwind::BLUE.c950,
170    };
171}