1use crate::_private::NonExhaustive;
24use crate::button::event::ButtonOutcome;
25use crate::util::{block_size, revert_style};
26use rat_event::util::{MouseFlags, have_keyboard_enhancement};
27use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Regular, ct_event};
28use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
29use rat_reloc::{RelocatableState, relocate_area};
30use ratatui::buffer::Buffer;
31use ratatui::layout::Rect;
32use ratatui::prelude::BlockExt;
33use ratatui::style::Style;
34use ratatui::text::Text;
35use ratatui::widgets::{Block, StatefulWidget, Widget};
36use std::thread;
37use std::time::Duration;
38
39#[derive(Debug, Default, Clone)]
41pub struct Button<'a> {
42 text: Text<'a>,
43 style: Style,
44 focus_style: Option<Style>,
45 hover_style: Option<Style>,
46 armed_style: Option<Style>,
47 armed_delay: Option<Duration>,
48 block: Option<Block<'a>>,
49}
50
51#[derive(Debug, Clone)]
53pub struct ButtonStyle {
54 pub style: Style,
56 pub focus: Option<Style>,
58 pub armed: Option<Style>,
60 pub hover: Option<Style>,
62 pub block: Option<Block<'static>>,
64 pub armed_delay: Option<Duration>,
68
69 pub non_exhaustive: NonExhaustive,
70}
71
72#[derive(Debug)]
74pub struct ButtonState {
75 pub area: Rect,
78 pub inner: Rect,
81 pub armed: bool,
84 pub armed_delay: Option<Duration>,
91
92 pub focus: FocusFlag,
95
96 pub mouse: MouseFlags,
99
100 pub non_exhaustive: NonExhaustive,
101}
102
103impl Default for ButtonStyle {
104 fn default() -> Self {
105 Self {
106 style: Default::default(),
107 focus: None,
108 armed: None,
109 hover: None,
110 block: None,
111 armed_delay: None,
112 non_exhaustive: NonExhaustive,
113 }
114 }
115}
116
117impl<'a> Button<'a> {
118 pub fn new(text: impl Into<Text<'a>>) -> Self {
119 Self::default().text(text)
120 }
121
122 #[inline]
124 pub fn styles_opt(self, styles: Option<ButtonStyle>) -> Self {
125 if let Some(styles) = styles {
126 self.styles(styles)
127 } else {
128 self
129 }
130 }
131
132 #[inline]
134 pub fn styles(mut self, styles: ButtonStyle) -> Self {
135 self.style = styles.style;
136 if styles.focus.is_some() {
137 self.focus_style = styles.focus;
138 }
139 if styles.armed.is_some() {
140 self.armed_style = styles.armed;
141 }
142 if styles.armed_delay.is_some() {
143 self.armed_delay = styles.armed_delay;
144 }
145 if styles.hover.is_some() {
146 self.hover_style = styles.hover;
147 }
148 if let Some(block) = styles.block {
149 self.block = Some(block);
150 }
151 self.block = self.block.map(|v| v.style(self.style));
152 self
153 }
154
155 #[inline]
157 pub fn style(mut self, style: impl Into<Style>) -> Self {
158 self.style = style.into();
159 self
160 }
161
162 #[inline]
164 pub fn focus_style(mut self, style: impl Into<Style>) -> Self {
165 self.focus_style = Some(style.into());
166 self
167 }
168
169 #[inline]
171 pub fn armed_style(mut self, style: impl Into<Style>) -> Self {
172 self.armed_style = Some(style.into());
173 self
174 }
175
176 pub fn armed_delay(mut self, delay: Duration) -> Self {
180 self.armed_delay = Some(delay);
181 self
182 }
183
184 pub fn hover_style(mut self, style: impl Into<Style>) -> Self {
186 self.hover_style = Some(style.into());
187 self
188 }
189
190 #[inline]
192 pub fn text(mut self, text: impl Into<Text<'a>>) -> Self {
193 self.text = text.into().centered();
194 self
195 }
196
197 pub fn left_aligned(mut self) -> Self {
199 self.text = self.text.left_aligned();
200 self
201 }
202
203 pub fn right_aligned(mut self) -> Self {
205 self.text = self.text.right_aligned();
206 self
207 }
208
209 #[inline]
211 pub fn block(mut self, block: Block<'a>) -> Self {
212 self.block = Some(block);
213 self.block = self.block.map(|v| v.style(self.style));
214 self
215 }
216
217 pub fn width(&self) -> u16 {
219 self.text.width() as u16 + block_size(&self.block).width
220 }
221
222 pub fn height(&self) -> u16 {
224 self.text.height() as u16 + block_size(&self.block).height
225 }
226}
227
228impl<'a> StatefulWidget for &Button<'a> {
229 type State = ButtonState;
230
231 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
232 render_ref(self, area, buf, state);
233 }
234}
235
236impl StatefulWidget for Button<'_> {
237 type State = ButtonState;
238
239 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
240 render_ref(&self, area, buf, state);
241 }
242}
243
244fn render_ref(widget: &Button<'_>, area: Rect, buf: &mut Buffer, state: &mut ButtonState) {
245 state.area = area;
246 state.inner = widget.block.inner_if_some(area);
247 state.armed_delay = widget.armed_delay;
248
249 let style = widget.style;
250 let focus_style = if let Some(focus_style) = widget.focus_style {
251 focus_style
252 } else {
253 revert_style(style)
254 };
255 let armed_style = if let Some(armed_style) = widget.armed_style {
256 armed_style
257 } else {
258 if state.is_focused() {
259 revert_style(focus_style)
260 } else {
261 revert_style(style)
262 }
263 };
264
265 if let Some(block) = &widget.block {
266 block.render(area, buf);
267 } else {
268 buf.set_style(area, style);
269 }
270
271 if state.mouse.hover.get() && widget.hover_style.is_some() {
272 buf.set_style(state.inner, widget.hover_style.expect("style"))
273 } else if state.is_focused() {
274 buf.set_style(state.inner, focus_style);
275 }
276
277 if state.armed {
278 let armed_area = Rect::new(
279 state.inner.x + 1,
280 state.inner.y,
281 state.inner.width.saturating_sub(2),
282 state.inner.height,
283 );
284 buf.set_style(armed_area, style.patch(armed_style));
285 }
286
287 let h = widget.text.height() as u16;
288 let r = state.inner.height.saturating_sub(h) / 2;
289 let area = Rect::new(state.inner.x, state.inner.y + r, state.inner.width, h);
290 (&widget.text).render(area, buf);
291}
292
293impl Clone for ButtonState {
294 fn clone(&self) -> Self {
295 Self {
296 area: self.area,
297 inner: self.inner,
298 armed: self.armed,
299 armed_delay: self.armed_delay,
300 focus: FocusFlag::named(self.focus.name()),
301 mouse: Default::default(),
302 non_exhaustive: NonExhaustive,
303 }
304 }
305}
306
307impl Default for ButtonState {
308 fn default() -> Self {
309 Self {
310 area: Default::default(),
311 inner: Default::default(),
312 armed: false,
313 armed_delay: None,
314 focus: Default::default(),
315 mouse: Default::default(),
316 non_exhaustive: NonExhaustive,
317 }
318 }
319}
320
321impl ButtonState {
322 pub fn new() -> Self {
323 Self::default()
324 }
325
326 pub fn named(name: &str) -> Self {
327 Self {
328 focus: FocusFlag::named(name),
329 ..Default::default()
330 }
331 }
332
333 pub fn clear_areas(&mut self) {
334 self.area = Rect::default();
335 self.inner = Rect::default();
336 }
337}
338
339impl HasFocus for ButtonState {
340 fn build(&self, builder: &mut FocusBuilder) {
341 builder.leaf_widget(self);
342 }
343
344 #[inline]
345 fn focus(&self) -> FocusFlag {
346 self.focus.clone()
347 }
348
349 #[inline]
350 fn area(&self) -> Rect {
351 self.area
352 }
353}
354
355impl RelocatableState for ButtonState {
356 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
357 self.area = relocate_area(self.area, shift, clip);
358 self.inner = relocate_area(self.inner, shift, clip);
359 }
360}
361
362pub(crate) mod event {
363 use rat_event::{ConsumedEvent, Outcome};
364
365 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
369 pub enum ButtonOutcome {
370 Continue,
372 Unchanged,
374 Changed,
376 Pressed,
378 }
379
380 impl ConsumedEvent for ButtonOutcome {
381 fn is_consumed(&self) -> bool {
382 *self != ButtonOutcome::Continue
383 }
384 }
385
386 impl From<ButtonOutcome> for Outcome {
387 fn from(value: ButtonOutcome) -> Self {
388 match value {
389 ButtonOutcome::Continue => Outcome::Continue,
390 ButtonOutcome::Unchanged => Outcome::Unchanged,
391 ButtonOutcome::Changed => Outcome::Changed,
392 ButtonOutcome::Pressed => Outcome::Changed,
393 }
394 }
395 }
396}
397
398impl HandleEvent<crossterm::event::Event, Regular, ButtonOutcome> for ButtonState {
399 fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> ButtonOutcome {
400 let r = if self.is_focused() {
401 if have_keyboard_enhancement() {
403 match event {
404 ct_event!(keycode press Enter) | ct_event!(key press ' ') => {
405 self.armed = true;
406 ButtonOutcome::Changed
407 }
408 ct_event!(keycode release Enter) | ct_event!(key release ' ') => {
409 if self.armed {
410 if let Some(delay) = self.armed_delay {
411 thread::sleep(delay);
412 }
413 self.armed = false;
414 ButtonOutcome::Pressed
415 } else {
416 ButtonOutcome::Unchanged
418 }
419 }
420 _ => ButtonOutcome::Continue,
421 }
422 } else {
423 match event {
424 ct_event!(keycode press Enter) | ct_event!(key press ' ') => {
425 ButtonOutcome::Pressed
426 }
427 _ => ButtonOutcome::Continue,
428 }
429 }
430 } else {
431 ButtonOutcome::Continue
432 };
433
434 if r == ButtonOutcome::Continue {
435 HandleEvent::handle(self, event, MouseOnly)
436 } else {
437 r
438 }
439 }
440}
441
442impl HandleEvent<crossterm::event::Event, MouseOnly, ButtonOutcome> for ButtonState {
443 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> ButtonOutcome {
444 match event {
445 ct_event!(mouse down Left for column, row) => {
446 if self.area.contains((*column, *row).into()) {
447 self.armed = true;
448 ButtonOutcome::Changed
449 } else {
450 ButtonOutcome::Continue
451 }
452 }
453 ct_event!(mouse up Left for column, row) => {
454 if self.area.contains((*column, *row).into()) {
455 if self.armed {
456 self.armed = false;
457 ButtonOutcome::Pressed
458 } else {
459 ButtonOutcome::Continue
460 }
461 } else {
462 if self.armed {
463 self.armed = false;
464 ButtonOutcome::Changed
465 } else {
466 ButtonOutcome::Continue
467 }
468 }
469 }
470 ct_event!(mouse any for m) if self.mouse.hover(self.area, m) => ButtonOutcome::Changed,
471 _ => ButtonOutcome::Continue,
472 }
473 }
474}
475
476impl HandleEvent<crossterm::event::Event, crossterm::event::KeyEvent, ButtonOutcome>
478 for ButtonState
479{
480 fn handle(
481 &mut self,
482 event: &crossterm::event::Event,
483 hotkey: crossterm::event::KeyEvent,
484 ) -> ButtonOutcome {
485 use crossterm::event::Event;
486
487 let r = match event {
488 Event::Key(key) => {
489 if have_keyboard_enhancement() {
491 if hotkey.code == key.code && hotkey.modifiers == key.modifiers {
492 if key.kind == crossterm::event::KeyEventKind::Press {
493 self.armed = true;
494 ButtonOutcome::Changed
495 } else if key.kind == crossterm::event::KeyEventKind::Release {
496 if self.armed {
497 if let Some(delay) = self.armed_delay {
498 thread::sleep(delay);
499 }
500 self.armed = false;
501 ButtonOutcome::Pressed
502 } else {
503 ButtonOutcome::Unchanged
505 }
506 } else {
507 ButtonOutcome::Continue
508 }
509 } else {
510 ButtonOutcome::Continue
511 }
512 } else {
513 if hotkey.code == key.code && hotkey.modifiers == key.modifiers {
514 if key.kind == crossterm::event::KeyEventKind::Press {
515 ButtonOutcome::Pressed
516 } else {
517 ButtonOutcome::Continue
518 }
519 } else {
520 ButtonOutcome::Continue
521 }
522 }
523 }
524 _ => ButtonOutcome::Continue,
525 };
526
527 r.or_else(|| self.handle(event, Regular))
528 }
529}
530
531pub fn handle_events(
535 state: &mut ButtonState,
536 focus: bool,
537 event: &crossterm::event::Event,
538) -> ButtonOutcome {
539 state.focus.set(focus);
540 HandleEvent::handle(state, event, Regular)
541}
542
543pub fn handle_mouse_events(
545 state: &mut ButtonState,
546 event: &crossterm::event::Event,
547) -> ButtonOutcome {
548 HandleEvent::handle(state, event, MouseOnly)
549}