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