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_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#[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#[derive(Debug, Clone)]
55pub struct ButtonStyle {
56 pub style: Style,
58 pub block: Option<Block<'static>>,
60 pub border_style: Option<Style>,
61 pub title_style: Option<Style>,
62 pub focus: Option<Style>,
64 pub armed: Option<Style>,
66 pub hover: Option<Style>,
68 pub armed_delay: Option<Duration>,
72
73 pub non_exhaustive: NonExhaustive,
74}
75
76#[derive(Debug)]
78pub struct ButtonState {
79 pub area: Rect,
82 pub inner: Rect,
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 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 #[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 #[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 #[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 #[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 #[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 #[inline]
195 pub fn armed_delay(mut self, delay: Duration) -> Self {
196 self.armed_delay = Some(delay);
197 self
198 }
199
200 #[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 #[inline]
209 pub fn text(mut self, text: impl Into<Text<'a>>) -> Self {
210 self.text = text.into().centered();
211 self
212 }
213
214 #[inline]
216 pub fn left_aligned(mut self) -> Self {
217 self.text = self.text.left_aligned();
218 self
219 }
220
221 #[inline]
223 pub fn right_aligned(mut self) -> Self {
224 self.text = self.text.right_aligned();
225 self
226 }
227
228 #[inline]
230 pub fn block(mut self, block: Block<'a>) -> Self {
231 self.block = Some(block.style(self.style));
232 self
233 }
234
235 #[inline]
237 pub fn width(&self) -> u16 {
238 self.text.width() as u16 + block_size(&self.block).width
239 }
240
241 #[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 pub fn pressed(&mut self, is_pressed: bool) -> ButtonOutcome {
362 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 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 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
427 pub enum ButtonOutcome {
428 Continue,
430 Unchanged,
432 Changed,
434 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 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 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
534impl 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
554pub fn handle_events(state: &mut ButtonState, focus: bool, event: &Event) -> ButtonOutcome {
558 state.focus.set(focus);
559 HandleEvent::handle(state, event, Regular)
560}
561
562pub fn handle_mouse_events(state: &mut ButtonState, event: &Event) -> ButtonOutcome {
564 HandleEvent::handle(state, event, MouseOnly)
565}