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