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
39impl<'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 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 if let Some(first) = first {
113 "▔".repeat(area.width as usize).fg(top).bg(bg).render(first, buf);
114 }
115 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}