1use crate::_private::NonExhaustive;
24use crate::button::event::ButtonOutcome;
25use crate::util::{block_size, revert_style};
26use rat_event::util::{have_keyboard_enhancement, MouseFlags};
27use rat_event::{ct_event, ConsumedEvent, HandleEvent, MouseOnly, Regular};
28use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
29use rat_reloc::{relocate_area, RelocatableState};
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
82 pub hover_enabled: bool,
84 pub armed: bool,
87 pub armed_delay: Option<Duration>,
93
94 pub focus: FocusFlag,
97
98 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 state.hover_enabled = widget.hover_style.is_some();
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 hover_enabled: false,
300 armed: self.armed,
301 armed_delay: self.armed_delay,
302 focus: FocusFlag::named(self.focus.name()),
303 mouse: Default::default(),
304 non_exhaustive: NonExhaustive,
305 }
306 }
307}
308
309impl Default for ButtonState {
310 fn default() -> Self {
311 Self {
312 area: Default::default(),
313 inner: Default::default(),
314 hover_enabled: false,
315 armed: false,
316 armed_delay: None,
317 focus: Default::default(),
318 mouse: Default::default(),
319 non_exhaustive: NonExhaustive,
320 }
321 }
322}
323
324impl ButtonState {
325 pub fn new() -> Self {
326 Self::default()
327 }
328
329 pub fn named(name: &str) -> Self {
330 Self {
331 focus: FocusFlag::named(name),
332 ..Default::default()
333 }
334 }
335}
336
337impl HasFocus for ButtonState {
338 fn build(&self, builder: &mut FocusBuilder) {
339 builder.leaf_widget(self);
340 }
341
342 #[inline]
343 fn focus(&self) -> FocusFlag {
344 self.focus.clone()
345 }
346
347 #[inline]
348 fn area(&self) -> Rect {
349 self.area
350 }
351}
352
353impl RelocatableState for ButtonState {
354 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
355 self.area = relocate_area(self.area, shift, clip);
356 self.inner = relocate_area(self.inner, shift, clip);
357 }
358}
359
360pub(crate) mod event {
361 use rat_event::{ConsumedEvent, Outcome};
362
363 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
367 pub enum ButtonOutcome {
368 Continue,
370 Unchanged,
372 Changed,
374 Pressed,
376 }
377
378 impl ConsumedEvent for ButtonOutcome {
379 fn is_consumed(&self) -> bool {
380 *self != ButtonOutcome::Continue
381 }
382 }
383
384 impl From<ButtonOutcome> for Outcome {
385 fn from(value: ButtonOutcome) -> Self {
386 match value {
387 ButtonOutcome::Continue => Outcome::Continue,
388 ButtonOutcome::Unchanged => Outcome::Unchanged,
389 ButtonOutcome::Changed => Outcome::Changed,
390 ButtonOutcome::Pressed => Outcome::Changed,
391 }
392 }
393 }
394}
395
396impl HandleEvent<crossterm::event::Event, Regular, ButtonOutcome> for ButtonState {
397 fn handle(&mut self, event: &crossterm::event::Event, _keymap: Regular) -> ButtonOutcome {
398 let r = if self.is_focused() {
399 if have_keyboard_enhancement() {
401 match event {
402 ct_event!(keycode press Enter) | ct_event!(key press ' ') => {
403 self.armed = true;
404 ButtonOutcome::Changed
405 }
406 ct_event!(keycode release Enter) | ct_event!(key release ' ') => {
407 if self.armed {
408 if let Some(delay) = self.armed_delay {
409 thread::sleep(delay);
410 }
411 self.armed = false;
412 ButtonOutcome::Pressed
413 } else {
414 ButtonOutcome::Unchanged
416 }
417 }
418 _ => ButtonOutcome::Continue,
419 }
420 } else {
421 match event {
422 ct_event!(keycode press Enter) | ct_event!(key press ' ') => {
423 ButtonOutcome::Pressed
424 }
425 _ => ButtonOutcome::Continue,
426 }
427 }
428 } else {
429 ButtonOutcome::Continue
430 };
431
432 if r == ButtonOutcome::Continue {
433 HandleEvent::handle(self, event, MouseOnly)
434 } else {
435 r
436 }
437 }
438}
439
440impl HandleEvent<crossterm::event::Event, MouseOnly, ButtonOutcome> for ButtonState {
441 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> ButtonOutcome {
442 match event {
443 ct_event!(mouse down Left for column, row) => {
444 if self.area.contains((*column, *row).into()) {
445 self.armed = true;
446 ButtonOutcome::Changed
447 } else {
448 ButtonOutcome::Continue
449 }
450 }
451 ct_event!(mouse up Left for column, row) => {
452 if self.area.contains((*column, *row).into()) {
453 if self.armed {
454 self.armed = false;
455 ButtonOutcome::Pressed
456 } else {
457 ButtonOutcome::Continue
458 }
459 } else {
460 if self.armed {
461 self.armed = false;
462 ButtonOutcome::Changed
463 } else {
464 ButtonOutcome::Continue
465 }
466 }
467 }
468 ct_event!(mouse any for m) if self.mouse.hover(self.area, m) => ButtonOutcome::Changed,
469 _ => ButtonOutcome::Continue,
470 }
471 }
472}
473
474impl HandleEvent<crossterm::event::Event, crossterm::event::KeyEvent, ButtonOutcome>
476 for ButtonState
477{
478 fn handle(
479 &mut self,
480 event: &crossterm::event::Event,
481 hotkey: crossterm::event::KeyEvent,
482 ) -> ButtonOutcome {
483 use crossterm::event::Event;
484
485 let r = match event {
486 Event::Key(key) => {
487 if have_keyboard_enhancement() {
489 if hotkey.code == key.code && hotkey.modifiers == key.modifiers {
490 if key.kind == crossterm::event::KeyEventKind::Press {
491 self.armed = true;
492 ButtonOutcome::Changed
493 } else if key.kind == crossterm::event::KeyEventKind::Release {
494 if self.armed {
495 if let Some(delay) = self.armed_delay {
496 thread::sleep(delay);
497 }
498 self.armed = false;
499 ButtonOutcome::Pressed
500 } else {
501 ButtonOutcome::Unchanged
503 }
504 } else {
505 ButtonOutcome::Continue
506 }
507 } else {
508 ButtonOutcome::Continue
509 }
510 } else {
511 if hotkey.code == key.code && hotkey.modifiers == key.modifiers {
512 if key.kind == crossterm::event::KeyEventKind::Press {
513 ButtonOutcome::Pressed
514 } else {
515 ButtonOutcome::Continue
516 }
517 } else {
518 ButtonOutcome::Continue
519 }
520 }
521 }
522 _ => ButtonOutcome::Continue,
523 };
524
525 r.or_else(|| self.handle(event, Regular))
526 }
527}
528
529pub fn handle_events(
533 state: &mut ButtonState,
534 focus: bool,
535 event: &crossterm::event::Event,
536) -> ButtonOutcome {
537 state.focus.set(focus);
538 HandleEvent::handle(state, event, Regular)
539}
540
541pub fn handle_mouse_events(
543 state: &mut ButtonState,
544 event: &crossterm::event::Event,
545) -> ButtonOutcome {
546 HandleEvent::handle(state, event, MouseOnly)
547}