rat_widget/
button.rs

1//!
2//! Button widget.
3//!
4//! Render:
5//! ```rust ignore
6//! Button::new("Button")
7//!      .styles(THEME.button_style()) //
8//!      .render(b_area_1, frame.buffer_mut(), &mut state.button1);
9//! ```
10//!
11//! Event handling:
12//! ```rust ignore
13//! match state.button1.handle(event, Regular) {
14//!     ButtonOutcome::Pressed => {
15//!         data.p1 += 1;
16//!         Outcome::Changed
17//!     }
18//!     r => r.into(),
19//! }
20//! ```
21//!
22
23use crate::_private::NonExhaustive;
24use crate::button::event::ButtonOutcome;
25use crate::text::HasScreenCursor;
26use crate::util::{block_size, revert_style};
27use rat_event::util::{MouseFlags, have_keyboard_enhancement};
28use rat_event::{ConsumedEvent, HandleEvent, MouseOnly, Regular, ct_event};
29use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
30use rat_reloc::{RelocatableState, relocate_area};
31use ratatui_core::buffer::Buffer;
32use ratatui_core::layout::Rect;
33use ratatui_core::style::Style;
34use ratatui_core::text::Text;
35use ratatui_core::widgets::{StatefulWidget, Widget};
36use ratatui_crossterm::crossterm::event::{Event, KeyEvent, KeyEventKind};
37use ratatui_widgets::block::{Block, BlockExt};
38use std::thread;
39use std::time::Duration;
40
41/// Button widget.
42#[derive(Debug, Default, Clone)]
43pub struct Button<'a> {
44    text: Text<'a>,
45    style: Style,
46    block: Option<Block<'a>>,
47    focus_style: Option<Style>,
48    hover_style: Option<Style>,
49    armed_style: Option<Style>,
50    armed_delay: Option<Duration>,
51}
52
53/// Composite style.
54#[derive(Debug, Clone)]
55pub struct ButtonStyle {
56    /// Base style
57    pub style: Style,
58    /// Button border
59    pub block: Option<Block<'static>>,
60    pub border_style: Option<Style>,
61    pub title_style: Option<Style>,
62    /// Focused style
63    pub focus: Option<Style>,
64    /// Armed style
65    pub armed: Option<Style>,
66    /// Hover style
67    pub hover: Option<Style>,
68    /// Some terminals repaint too fast to see the click.
69    /// This adds some delay when the button state goes from
70    /// armed to clicked.
71    pub armed_delay: Option<Duration>,
72
73    pub non_exhaustive: NonExhaustive,
74}
75
76/// State & event-handling.
77#[derive(Debug)]
78pub struct ButtonState {
79    /// Complete area
80    /// __read only__. renewed for each render.
81    pub area: Rect,
82    /// Area inside the block.
83    /// __read only__. renewed for each render.
84    pub inner: Rect,
85    /// Button has been clicked but not released yet.
86    /// __read only__
87    pub armed: bool,
88    /// Some terminals repaint too fast to see the click.
89    /// This adds some delay when the button state goes from
90    /// armed to clicked.
91    ///
92    /// Default is 50ms.
93    /// __read+write__
94    pub armed_delay: Option<Duration>,
95
96    /// Current focus state.
97    /// __read+write__
98    pub focus: FocusFlag,
99
100    /// Mouse interaction.
101    /// __read only__
102    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            block: Default::default(),
112            border_style: Default::default(),
113            title_style: Default::default(),
114            focus: Default::default(),
115            armed: Default::default(),
116            hover: Default::default(),
117            armed_delay: Default::default(),
118            non_exhaustive: NonExhaustive,
119        }
120    }
121}
122
123impl<'a> Button<'a> {
124    #[inline]
125    pub fn new(text: impl Into<Text<'a>>) -> Self {
126        Self::default().text(text)
127    }
128
129    /// Set all styles.
130    #[inline]
131    pub fn styles_opt(self, styles: Option<ButtonStyle>) -> Self {
132        if let Some(styles) = styles {
133            self.styles(styles)
134        } else {
135            self
136        }
137    }
138
139    /// Set all styles.
140    #[inline]
141    pub fn styles(mut self, styles: ButtonStyle) -> Self {
142        self.style = styles.style;
143        if styles.block.is_some() {
144            self.block = styles.block;
145        }
146        if let Some(border_style) = styles.border_style {
147            self.block = self.block.map(|v| v.border_style(border_style));
148        }
149        if let Some(title_style) = styles.title_style {
150            self.block = self.block.map(|v| v.title_style(title_style));
151        }
152        self.block = self.block.map(|v| v.style(self.style));
153        if styles.focus.is_some() {
154            self.focus_style = styles.focus;
155        }
156        if styles.armed.is_some() {
157            self.armed_style = styles.armed;
158        }
159        if styles.armed_delay.is_some() {
160            self.armed_delay = styles.armed_delay;
161        }
162        if styles.hover.is_some() {
163            self.hover_style = styles.hover;
164        }
165        self
166    }
167
168    /// Set the base-style.
169    #[inline]
170    pub fn style(mut self, style: impl Into<Style>) -> Self {
171        let style = style.into();
172        self.style = style.clone();
173        self.block = self.block.map(|v| v.style(style));
174        self
175    }
176
177    /// Style when focused.
178    #[inline]
179    pub fn focus_style(mut self, style: impl Into<Style>) -> Self {
180        self.focus_style = Some(style.into());
181        self
182    }
183
184    /// Style when clicked but not released.
185    #[inline]
186    pub fn armed_style(mut self, style: impl Into<Style>) -> Self {
187        self.armed_style = Some(style.into());
188        self
189    }
190
191    /// Some terminals repaint too fast to see the click.
192    /// This adds some delay when the button state goes from
193    /// armed to clicked.
194    #[inline]
195    pub fn armed_delay(mut self, delay: Duration) -> Self {
196        self.armed_delay = Some(delay);
197        self
198    }
199
200    /// Style for hover over the button.
201    #[inline]
202    pub fn hover_style(mut self, style: impl Into<Style>) -> Self {
203        self.hover_style = Some(style.into());
204        self
205    }
206
207    /// Button text.
208    #[inline]
209    pub fn text(mut self, text: impl Into<Text<'a>>) -> Self {
210        self.text = text.into().centered();
211        self
212    }
213
214    /// Left align button text.
215    #[inline]
216    pub fn left_aligned(mut self) -> Self {
217        self.text = self.text.left_aligned();
218        self
219    }
220
221    /// Right align button text.
222    #[inline]
223    pub fn right_aligned(mut self) -> Self {
224        self.text = self.text.right_aligned();
225        self
226    }
227
228    /// Block.
229    #[inline]
230    pub fn block(mut self, block: Block<'a>) -> Self {
231        self.block = Some(block.style(self.style));
232        self
233    }
234
235    /// Inherent width.
236    #[inline]
237    pub fn width(&self) -> u16 {
238        self.text.width() as u16 + block_size(&self.block).width
239    }
240
241    /// Inherent height.
242    #[inline]
243    pub fn height(&self) -> u16 {
244        self.text.height() as u16 + block_size(&self.block).height
245    }
246}
247
248impl<'a> StatefulWidget for &Button<'a> {
249    type State = ButtonState;
250
251    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
252        render_ref(self, area, buf, state);
253    }
254}
255
256impl StatefulWidget for Button<'_> {
257    type State = ButtonState;
258
259    fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
260        render_ref(&self, area, buf, state);
261    }
262}
263
264fn render_ref(widget: &Button<'_>, area: Rect, buf: &mut Buffer, state: &mut ButtonState) {
265    state.area = area;
266    state.inner = widget.block.inner_if_some(area);
267    state.armed_delay = widget.armed_delay;
268
269    let style = widget.style;
270    let focus_style = if let Some(focus_style) = widget.focus_style {
271        focus_style
272    } else {
273        revert_style(style)
274    };
275    let armed_style = if let Some(armed_style) = widget.armed_style {
276        armed_style
277    } else {
278        if state.is_focused() {
279            revert_style(focus_style)
280        } else {
281            revert_style(style)
282        }
283    };
284
285    if let Some(block) = &widget.block {
286        block.render(area, buf);
287    } else {
288        buf.set_style(area, style);
289    }
290
291    if state.mouse.hover.get() && widget.hover_style.is_some() {
292        buf.set_style(state.inner, widget.hover_style.expect("style"))
293    } else if state.is_focused() {
294        buf.set_style(state.inner, focus_style);
295    }
296
297    if state.armed {
298        let armed_area = Rect::new(
299            state.inner.x + 1,
300            state.inner.y,
301            state.inner.width.saturating_sub(2),
302            state.inner.height,
303        );
304        buf.set_style(armed_area, style.patch(armed_style));
305    }
306
307    let h = widget.text.height() as u16;
308    let r = state.inner.height.saturating_sub(h) / 2;
309    let area = Rect::new(state.inner.x, state.inner.y + r, state.inner.width, h);
310    (&widget.text).render(area, buf);
311}
312
313impl Clone for ButtonState {
314    fn clone(&self) -> Self {
315        Self {
316            area: self.area,
317            inner: self.inner,
318            armed: self.armed,
319            armed_delay: self.armed_delay,
320            focus: self.focus.new_instance(),
321            mouse: Default::default(),
322            non_exhaustive: NonExhaustive,
323        }
324    }
325}
326
327impl Default for ButtonState {
328    fn default() -> Self {
329        Self {
330            area: Default::default(),
331            inner: Default::default(),
332            armed: Default::default(),
333            armed_delay: Default::default(),
334            focus: Default::default(),
335            mouse: Default::default(),
336            non_exhaustive: NonExhaustive,
337        }
338    }
339}
340
341impl ButtonState {
342    pub fn new() -> Self {
343        Self::default()
344    }
345
346    pub fn named(name: &str) -> Self {
347        let mut z = Self::default();
348        z.focus = z.focus.with_name(name);
349        z
350    }
351
352    #[deprecated(since = "2.1.0", note = "use relocate_hidden() to clear the areas.")]
353    pub fn clear_areas(&mut self) {
354        self.area = Rect::default();
355        self.inner = Rect::default();
356    }
357}
358
359impl ButtonState {
360    /// Simulates the button-press event.
361    pub fn pressed(&mut self, is_pressed: bool) -> ButtonOutcome {
362        // Release keys may not be available.
363        if have_keyboard_enhancement() {
364            if is_pressed {
365                self.armed = true;
366                ButtonOutcome::Changed
367            } else if !is_pressed {
368                if self.armed {
369                    if let Some(delay) = self.armed_delay {
370                        thread::sleep(delay);
371                    }
372                    self.armed = false;
373                    ButtonOutcome::Pressed
374                } else {
375                    // single key release happen more often than not.
376                    ButtonOutcome::Unchanged
377                }
378            } else {
379                ButtonOutcome::Continue
380            }
381        } else {
382            if is_pressed {
383                ButtonOutcome::Pressed
384            } else {
385                ButtonOutcome::Continue
386            }
387        }
388    }
389}
390
391impl HasFocus for ButtonState {
392    fn build(&self, builder: &mut FocusBuilder) {
393        builder.leaf_widget(self);
394    }
395
396    #[inline]
397    fn focus(&self) -> FocusFlag {
398        self.focus.clone()
399    }
400
401    #[inline]
402    fn area(&self) -> Rect {
403        self.area
404    }
405}
406
407impl HasScreenCursor for ButtonState {
408    fn screen_cursor(&self) -> Option<(u16, u16)> {
409        None
410    }
411}
412
413impl RelocatableState for ButtonState {
414    fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
415        self.area = relocate_area(self.area, shift, clip);
416        self.inner = relocate_area(self.inner, shift, clip);
417    }
418}
419
420pub(crate) mod event {
421    use rat_event::{ConsumedEvent, Outcome};
422
423    /// Result value for event-handling.
424    ///
425    /// Adds `Pressed` to the general Outcome.
426    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
427    pub enum ButtonOutcome {
428        /// The given event was not handled at all.
429        Continue,
430        /// The event was handled, no repaint necessary.
431        Unchanged,
432        /// The event was handled, repaint necessary.
433        Changed,
434        /// Button has been pressed.
435        Pressed,
436    }
437
438    impl ConsumedEvent for ButtonOutcome {
439        fn is_consumed(&self) -> bool {
440            *self != ButtonOutcome::Continue
441        }
442    }
443
444    impl From<ButtonOutcome> for Outcome {
445        fn from(value: ButtonOutcome) -> Self {
446            match value {
447                ButtonOutcome::Continue => Outcome::Continue,
448                ButtonOutcome::Unchanged => Outcome::Unchanged,
449                ButtonOutcome::Changed => Outcome::Changed,
450                ButtonOutcome::Pressed => Outcome::Changed,
451            }
452        }
453    }
454}
455
456impl HandleEvent<Event, Regular, ButtonOutcome> for ButtonState {
457    fn handle(&mut self, event: &Event, _keymap: Regular) -> ButtonOutcome {
458        let r = if self.is_focused() {
459            // Release keys may not be available.
460            if have_keyboard_enhancement() {
461                match event {
462                    ct_event!(keycode press Enter) | ct_event!(key press ' ') => {
463                        self.armed = true;
464                        ButtonOutcome::Changed
465                    }
466                    ct_event!(keycode release Enter) | ct_event!(key release ' ') => {
467                        if self.armed {
468                            if let Some(delay) = self.armed_delay {
469                                thread::sleep(delay);
470                            }
471                            self.armed = false;
472                            ButtonOutcome::Pressed
473                        } else {
474                            // single key release happen more often than not.
475                            ButtonOutcome::Unchanged
476                        }
477                    }
478                    _ => ButtonOutcome::Continue,
479                }
480            } else {
481                match event {
482                    ct_event!(keycode press Enter) | ct_event!(key press ' ') => {
483                        ButtonOutcome::Pressed
484                    }
485                    _ => ButtonOutcome::Continue,
486                }
487            }
488        } else {
489            ButtonOutcome::Continue
490        };
491
492        if r == ButtonOutcome::Continue {
493            HandleEvent::handle(self, event, MouseOnly)
494        } else {
495            r
496        }
497    }
498}
499
500impl HandleEvent<Event, MouseOnly, ButtonOutcome> for ButtonState {
501    fn handle(&mut self, event: &Event, _keymap: MouseOnly) -> ButtonOutcome {
502        match event {
503            ct_event!(mouse down Left for column, row) => {
504                if self.area.contains((*column, *row).into()) {
505                    self.armed = true;
506                    ButtonOutcome::Changed
507                } else {
508                    ButtonOutcome::Continue
509                }
510            }
511            ct_event!(mouse up Left for column, row) => {
512                if self.area.contains((*column, *row).into()) {
513                    if self.armed {
514                        self.armed = false;
515                        ButtonOutcome::Pressed
516                    } else {
517                        ButtonOutcome::Continue
518                    }
519                } else {
520                    if self.armed {
521                        self.armed = false;
522                        ButtonOutcome::Changed
523                    } else {
524                        ButtonOutcome::Continue
525                    }
526                }
527            }
528            ct_event!(mouse any for m) if self.mouse.hover(self.area, m) => ButtonOutcome::Changed,
529            _ => ButtonOutcome::Continue,
530        }
531    }
532}
533
534/// Check event-handling for this hot-key and do Regular key-events otherwise.
535impl HandleEvent<Event, KeyEvent, ButtonOutcome> for ButtonState {
536    fn handle(&mut self, event: &Event, hotkey: KeyEvent) -> ButtonOutcome {
537        use Event;
538
539        let r = match event {
540            Event::Key(key) => {
541                if hotkey.code == key.code && hotkey.modifiers == key.modifiers {
542                    self.pressed(key.kind == KeyEventKind::Press)
543                } else {
544                    ButtonOutcome::Continue
545                }
546            }
547            _ => ButtonOutcome::Continue,
548        };
549
550        r.or_else(|| self.handle(event, Regular))
551    }
552}
553
554/// Handle all events.
555/// Text events are only processed if focus is true.
556/// Mouse events are processed if they are in range.
557pub fn handle_events(state: &mut ButtonState, focus: bool, event: &Event) -> ButtonOutcome {
558    state.focus.set(focus);
559    HandleEvent::handle(state, event, Regular)
560}
561
562/// Handle only mouse-events.
563pub fn handle_mouse_events(state: &mut ButtonState, event: &Event) -> ButtonOutcome {
564    HandleEvent::handle(state, event, MouseOnly)
565}