ratatui_toolkit/
button.rs1use ratatui::layout::Rect;
2use ratatui::style::{Color, Modifier, Style};
3use ratatui::text::{Line, Span};
4
5#[derive(Debug, Clone)]
7pub struct Button {
8 pub text: String,
10 pub area: Option<Rect>,
12 pub hovered: bool,
14 pub normal_style: Style,
16 pub hover_style: Style,
18}
19
20impl Button {
21 pub fn new(text: impl Into<String>) -> Self {
23 Self {
24 text: text.into(),
25 area: None,
26 hovered: false,
27 normal_style: Style::default()
28 .fg(Color::Cyan)
29 .add_modifier(Modifier::BOLD),
30 hover_style: Style::default()
31 .fg(Color::Black)
32 .bg(Color::Cyan)
33 .add_modifier(Modifier::BOLD),
34 }
35 }
36
37 pub fn normal_style(mut self, style: Style) -> Self {
39 self.normal_style = style;
40 self
41 }
42
43 pub fn hover_style(mut self, style: Style) -> Self {
45 self.hover_style = style;
46 self
47 }
48
49 pub fn is_clicked(&self, column: u16, row: u16) -> bool {
51 if let Some(area) = self.area {
52 column >= area.x
53 && column < area.x + area.width
54 && row >= area.y
55 && row < area.y + area.height
56 } else {
57 false
58 }
59 }
60
61 pub fn update_hover(&mut self, column: u16, row: u16) {
63 self.hovered = self.is_clicked(column, row);
64 }
65
66 pub fn render(&self, panel_area: Rect, _title_prefix: &str) -> (Span<'static>, Rect) {
69 let button_text = format!(" [{}] ", self.text);
71 let button_width = button_text.len() as u16;
72 let button_x = panel_area.x + panel_area.width.saturating_sub(button_width + 2); let button_y = panel_area.y;
74
75 let area = Rect {
76 x: button_x,
77 y: button_y,
78 width: button_width,
79 height: 1,
80 };
81
82 let style = if self.hovered {
83 self.hover_style
84 } else {
85 self.normal_style
86 };
87
88 (Span::styled(button_text, style), area)
90 }
91
92 pub fn render_with_title(&mut self, panel_area: Rect, title: &str) -> Line<'static> {
96 let (button_span, area) = self.render(panel_area, title);
97
98 self.area = Some(area);
100
101 let title_line = Line::from(vec![Span::raw(title.to_string()), button_span]);
103
104 title_line
105 }
106
107 pub fn render_at_offset(
110 &self,
111 panel_area: Rect,
112 offset_from_right: u16,
113 ) -> (Span<'static>, Rect) {
114 let button_text = format!(" [{}] ", self.text);
115 let button_width = button_text.len() as u16;
116 let button_x = panel_area.x
117 + panel_area
118 .width
119 .saturating_sub(offset_from_right + button_width + 2);
120 let button_y = panel_area.y;
121
122 let area = Rect {
123 x: button_x,
124 y: button_y,
125 width: button_width,
126 height: 1,
127 };
128
129 let style = if self.hovered {
130 self.hover_style
131 } else {
132 self.normal_style
133 };
134
135 (Span::styled(button_text, style), area)
136 }
137}
138
139pub fn render_title_with_buttons(
141 panel_area: Rect,
142 title: &str,
143 buttons: &mut [&mut Button],
144) -> Line<'static> {
145 let mut spans = vec![Span::raw(title.to_string())];
146
147 let mut offset = 0u16;
149
150 for button in buttons.iter_mut().rev() {
152 let (button_span, area) = button.render_at_offset(panel_area, offset);
153 button.area = Some(area);
154
155 let button_width = format!(" [{}] ", button.text).len() as u16;
157 offset += button_width;
158
159 spans.insert(1, button_span);
161 }
162
163 Line::from(spans)
164}
165
166impl Default for Button {
167 fn default() -> Self {
168 Self::new("Button")
169 }
170}