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