1use crate::_private::NonExhaustive;
21use crate::range_op::RangeOp;
22use crate::slider::event::SliderOutcome;
23use crate::util::revert_style;
24use map_range_int::MapRange;
25use rat_event::util::MouseFlags;
26use rat_event::{ct_event, HandleEvent, MouseOnly, Regular};
27use rat_focus::{FocusBuilder, FocusFlag, HasFocus};
28use rat_reloc::{relocate_area, RelocatableState};
29use ratatui::buffer::Buffer;
30use ratatui::layout::{Alignment, Direction, Position, Rect};
31use ratatui::prelude::{BlockExt, StatefulWidget};
32use ratatui::style::{Style, Stylize};
33use ratatui::text::{Line, Text};
34#[cfg(feature = "unstable-widget-ref")]
35use ratatui::widgets::StatefulWidgetRef;
36use ratatui::widgets::{Block, Widget};
37use std::borrow::Cow;
38use std::fmt::{Debug, Formatter};
39use std::marker::PhantomData;
40use unicode_display_width::width as unicode_width;
41
42#[derive(Debug, Clone)]
47pub struct Slider<'a, T>
48where
49 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
50 u16: MapRange<T>,
51{
52 style: Style,
53 bounds_style: Option<Style>,
54 knob_style: Option<Style>,
55 focus_style: Option<Style>,
56
57 direction: Direction,
58
59 range: Option<(T, T)>,
60 step: Option<<T as RangeOp>::Step>,
61 long_step: Option<<T as RangeOp>::Step>,
62
63 text_align: Alignment,
64 lower_bound: Option<Cow<'a, str>>,
65 upper_bound: Option<Cow<'a, str>>,
66
67 track_char: Option<Cow<'a, str>>,
68
69 horizontal_knob: Option<Cow<'a, str>>,
70 vertical_knob: Option<Cow<'a, str>>,
71
72 block: Option<Block<'a>>,
73
74 _phantom: PhantomData<T>,
75}
76
77#[derive(Debug, Clone)]
78pub struct SliderStyle {
79 pub style: Style,
81 pub bounds: Option<Style>,
83 pub knob: Option<Style>,
85 pub focus: Option<Style>,
87
88 pub text_align: Option<Alignment>,
90 pub lower_bound: Option<&'static str>,
92 pub upper_bound: Option<&'static str>,
94
95 pub track_char: Option<&'static str>,
97
98 pub vertical_knob: Option<&'static str>,
100 pub horizontal_knob: Option<&'static str>,
102
103 pub block: Option<Block<'static>>,
105
106 pub non_exhaustive: NonExhaustive,
107}
108
109pub struct SliderState<T>
111where
112 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
113 u16: MapRange<T>,
114{
115 pub area: Rect,
118 pub inner: Rect,
121 pub lower_bound: Rect,
124 pub upper_bound: Rect,
127 pub track: Rect,
130 pub knob: Rect,
133 pub scale_len: u16,
135 pub direction: Direction,
138
139 pub range: (T, T),
141 pub step: <T as RangeOp>::Step,
143 pub long_step: Option<<T as RangeOp>::Step>,
145
146 pub value: T,
148
149 pub focus: FocusFlag,
152
153 pub mouse: MouseFlags,
156
157 pub non_exhaustive: NonExhaustive,
158}
159
160pub(crate) mod event {
161 use rat_event::{ConsumedEvent, Outcome};
162
163 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
165 pub enum SliderOutcome {
166 Continue,
168 Unchanged,
170 Changed,
172 Value,
174 }
175
176 impl ConsumedEvent for SliderOutcome {
177 fn is_consumed(&self) -> bool {
178 *self != SliderOutcome::Continue
179 }
180 }
181
182 impl From<SliderOutcome> for Outcome {
183 fn from(value: SliderOutcome) -> Self {
184 match value {
185 SliderOutcome::Continue => Outcome::Continue,
186 SliderOutcome::Unchanged => Outcome::Unchanged,
187 SliderOutcome::Changed => Outcome::Changed,
188 SliderOutcome::Value => Outcome::Changed,
189 }
190 }
191 }
192}
193
194impl Default for SliderStyle {
195 fn default() -> Self {
196 Self {
197 style: Default::default(),
198 bounds: None,
199 knob: None,
200 focus: None,
201 text_align: None,
202 lower_bound: None,
203 upper_bound: None,
204 track_char: None,
205 vertical_knob: None,
206 horizontal_knob: None,
207 block: None,
208 non_exhaustive: NonExhaustive,
209 }
210 }
211}
212
213impl<T> Default for Slider<'_, T>
214where
215 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
216 u16: MapRange<T>,
217{
218 fn default() -> Self {
219 Self {
220 style: Default::default(),
221 bounds_style: None,
222 knob_style: None,
223 focus_style: None,
224 direction: Direction::Horizontal,
225 range: None,
226 step: None,
227 long_step: None,
228 text_align: Alignment::Left,
229 lower_bound: None,
230 upper_bound: None,
231 track_char: None,
232 horizontal_knob: None,
233 vertical_knob: None,
234 block: None,
235 _phantom: Default::default(),
236 }
237 }
238}
239
240impl<'a, T> Slider<'a, T>
241where
242 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
243 u16: MapRange<T>,
244{
245 pub fn new() -> Self {
247 Default::default()
248 }
249
250 pub fn direction(mut self, direction: Direction) -> Self {
252 self.direction = direction;
253 self
254 }
255
256 pub fn range(mut self, range: (T, T)) -> Self {
258 self.range = Some(range);
259 self
260 }
261
262 pub fn step(mut self, step: <T as RangeOp>::Step) -> Self {
264 self.step = Some(step);
265 self
266 }
267
268 pub fn long_step(mut self, step: <T as RangeOp>::Step) -> Self {
270 self.long_step = Some(step);
271 self
272 }
273
274 pub fn styles(mut self, styles: SliderStyle) -> Self {
276 self.style = styles.style;
277 if styles.bounds.is_some() {
278 self.bounds_style = styles.bounds;
279 }
280 if styles.knob.is_some() {
281 self.knob_style = styles.knob;
282 }
283 if styles.focus.is_some() {
284 self.focus_style = styles.focus;
285 }
286 if let Some(align) = styles.text_align {
287 self.text_align = align;
288 }
289 if styles.lower_bound.is_some() {
290 self.lower_bound = styles.lower_bound.map(Cow::Borrowed);
291 }
292 if styles.upper_bound.is_some() {
293 self.upper_bound = styles.upper_bound.map(Cow::Borrowed);
294 }
295 if styles.track_char.is_some() {
296 self.track_char = styles.track_char.map(Cow::Borrowed);
297 }
298 if styles.vertical_knob.is_some() {
299 self.vertical_knob = styles.vertical_knob.map(Cow::Borrowed);
300 }
301 if styles.horizontal_knob.is_some() {
302 self.horizontal_knob = styles.horizontal_knob.map(Cow::Borrowed);
303 }
304 if styles.block.is_some() {
305 self.block = styles.block;
306 }
307 self.block = self.block.map(|v| v.style(self.style));
308 self
309 }
310
311 pub fn style(mut self, style: Style) -> Self {
313 self.style = style;
314 self.block = self.block.map(|v| v.style(style));
315 self
316 }
317
318 pub fn focus_style(mut self, style: Style) -> Self {
320 self.focus_style = Some(style);
321 self
322 }
323
324 pub fn bounds_style(mut self, style: Style) -> Self {
326 self.bounds_style = Some(style);
327 self
328 }
329
330 pub fn knob_style(mut self, style: Style) -> Self {
332 self.knob_style = Some(style);
333 self
334 }
335
336 pub fn text_align(mut self, align: Alignment) -> Self {
338 self.text_align = align;
339 self
340 }
341
342 pub fn lower_bound(mut self, bound: impl Into<Cow<'a, str>>) -> Self {
344 self.lower_bound = Some(bound.into());
345 self
346 }
347
348 pub fn upper_bound(mut self, bound: impl Into<Cow<'a, str>>) -> Self {
350 self.upper_bound = Some(bound.into());
351 self
352 }
353
354 pub fn track_char(mut self, bound: impl Into<Cow<'a, str>>) -> Self {
356 self.track_char = Some(bound.into());
357 self
358 }
359
360 pub fn horizontal_knob(mut self, knob: impl Into<Cow<'a, str>>) -> Self {
363 self.horizontal_knob = Some(knob.into());
364 self
365 }
366
367 pub fn vertical_knob(mut self, knob: impl Into<Cow<'a, str>>) -> Self {
370 self.vertical_knob = Some(knob.into());
371 self
372 }
373
374 pub fn block(mut self, block: Block<'a>) -> Self {
376 self.block = Some(block);
377 self.block = self.block.map(|v| v.style(self.style));
378 self
379 }
380}
381
382impl<'a, T> Slider<'a, T>
383where
384 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
385 u16: MapRange<T>,
386{
387 fn render_knob_str(&'a self, knob_repeat: u16, is_focused: bool) -> Cow<'a, str> {
390 fn map_ref<'b>(s0: &'b Option<Cow<'b, str>>, d: Cow<'b, str>) -> Cow<'b, str> {
391 s0.as_ref().map(|v| Cow::Borrowed(v.as_ref())).unwrap_or(d)
392 }
393
394 if is_focused {
395 match (self.direction, knob_repeat) {
396 (Direction::Horizontal, 1) => map_ref(&self.horizontal_knob, Cow::Borrowed(" | ")),
397 (Direction::Horizontal, 2) => {
398 map_ref(&self.horizontal_knob, Cow::Borrowed(" ╷ \n ╵ "))
399 }
400 (Direction::Horizontal, 3) => {
401 map_ref(&self.horizontal_knob, Cow::Borrowed(" ╷ \n │ \n ╵ "))
402 }
403 (Direction::Horizontal, 4) => {
404 map_ref(&self.horizontal_knob, Cow::Borrowed(" ╷ \n │ \n │ \n ╵ "))
405 }
406 (Direction::Horizontal, 5) => map_ref(
407 &self.horizontal_knob,
408 Cow::Borrowed(" ╷ \n │ \n │ \n │ \n ╵ "),
409 ),
410 (Direction::Horizontal, n) => {
411 let mut tmp = String::new();
412 tmp.push_str(" ╷ \n");
413 for _ in 0..n - 2 {
414 tmp.push_str(" │ \n");
415 }
416 tmp.push_str(" ╵ ");
417 map_ref(&self.horizontal_knob, Cow::Owned(tmp))
418 }
419
420 (Direction::Vertical, 1) => map_ref(&self.vertical_knob, Cow::Borrowed("─")),
421 (Direction::Vertical, 2) => map_ref(&self.vertical_knob, Cow::Borrowed("╶╴")),
422 (Direction::Vertical, 3) => map_ref(&self.vertical_knob, Cow::Borrowed("╶─╴")),
423 (Direction::Vertical, 4) => map_ref(&self.vertical_knob, Cow::Borrowed("╶──╴")),
424 (Direction::Vertical, 5) => map_ref(&self.vertical_knob, Cow::Borrowed("╶───╴")),
425 (Direction::Vertical, n) => {
426 let mut tmp = String::new();
427 tmp.push('╶');
428 for _ in 0..n - 2 {
429 tmp.push('─');
430 }
431 tmp.push('╴');
432 map_ref(&self.vertical_knob, Cow::Owned(tmp))
433 }
434 }
435 } else {
436 match (self.direction, knob_repeat) {
437 (Direction::Horizontal, 1) => map_ref(&self.horizontal_knob, Cow::Borrowed(" ")),
438 (Direction::Horizontal, 2) => {
439 map_ref(&self.horizontal_knob, Cow::Borrowed(" \n "))
440 }
441 (Direction::Horizontal, 3) => {
442 map_ref(&self.horizontal_knob, Cow::Borrowed(" \n \n "))
443 }
444 (Direction::Horizontal, 4) => {
445 map_ref(&self.horizontal_knob, Cow::Borrowed(" \n \n \n "))
446 }
447 (Direction::Horizontal, 5) => map_ref(
448 &self.horizontal_knob,
449 Cow::Borrowed(" \n \n \n \n "),
450 ),
451 (Direction::Horizontal, n) => {
452 let mut tmp = String::new();
453 tmp.push_str(" \n");
454 for _ in 0..n - 2 {
455 tmp.push_str(" \n");
456 }
457 tmp.push_str(" ");
458 map_ref(&self.horizontal_knob, Cow::Owned(tmp))
459 }
460
461 (Direction::Vertical, 1) => map_ref(&self.vertical_knob, Cow::Borrowed(" ")),
462 (Direction::Vertical, 2) => map_ref(&self.vertical_knob, Cow::Borrowed(" ")),
463 (Direction::Vertical, 3) => map_ref(&self.vertical_knob, Cow::Borrowed(" ")),
464 (Direction::Vertical, 4) => map_ref(&self.vertical_knob, Cow::Borrowed(" ")),
465 (Direction::Vertical, 5) => map_ref(&self.vertical_knob, Cow::Borrowed(" ")),
466 (Direction::Vertical, n) => {
467 map_ref(&self.vertical_knob, Cow::Owned(" ".repeat(n as usize)))
468 }
469 }
470 }
471 }
472
473 fn layout(&self, area: Rect, state: &mut SliderState<T>) {
475 state.area = area;
476 state.inner = self.block.inner_if_some(area);
477 state.direction = self.direction;
478
479 if let Some(range) = self.range {
480 state.range = range;
481 }
482 if let Some(step) = self.step {
483 state.step = step;
484 }
485 if let Some(long_step) = self.long_step {
486 state.long_step = Some(long_step);
487 }
488
489 let inner = state.inner;
490
491 match self.direction {
492 Direction::Horizontal => {
493 let lower_width = self
494 .lower_bound
495 .as_ref()
496 .map(|v| unicode_width(v) as u16)
497 .unwrap_or_default();
498 let upper_width = self
499 .upper_bound
500 .as_ref()
501 .map(|v| unicode_width(v) as u16)
502 .unwrap_or_default();
503
504 state.lower_bound = Rect::new(inner.x, inner.y, lower_width, inner.height);
505 state.upper_bound = Rect::new(
506 (inner.x + inner.width).saturating_sub(upper_width),
507 inner.y,
508 upper_width,
509 inner.height,
510 );
511
512 let track_len = state
513 .upper_bound
514 .x
515 .saturating_sub(state.lower_bound.right());
516 state.track =
517 Rect::new(state.lower_bound.right(), inner.y, track_len, inner.height);
518
519 let knob_width = unicode_width(
520 self.render_knob_str(inner.height, false)
521 .split('\n')
522 .next()
523 .expect("one knob"),
524 ) as u16;
525 state.scale_len = track_len.saturating_sub(knob_width);
526
527 if let Some(knob_pos) = state.value.map_range(state.range, (0, state.scale_len)) {
528 state.knob =
529 Rect::new(state.track.x + knob_pos, inner.y, knob_width, inner.height)
530 } else {
531 state.knob = Rect::new(state.track.x, inner.y, 0, inner.height);
532 }
533 }
534 Direction::Vertical => {
535 let lower_height = self
536 .lower_bound
537 .as_ref()
538 .map(|v| v.split('\n').count() as u16)
539 .unwrap_or_default();
540 let upper_height = self
541 .upper_bound
542 .as_ref()
543 .map(|v| v.split('\n').count() as u16)
544 .unwrap_or_default();
545
546 state.lower_bound = Rect::new(inner.x, inner.y, inner.width, lower_height);
547 state.upper_bound = Rect::new(
548 inner.x,
549 inner.bottom().saturating_sub(upper_height),
550 inner.width,
551 upper_height,
552 );
553
554 let track_len = inner.height.saturating_sub(lower_height + upper_height);
555 state.track = Rect::new(inner.x, inner.y + lower_height, inner.width, track_len);
556
557 state.scale_len = track_len.saturating_sub(1);
558
559 if let Some(knob_pos) = state.value.map_range(state.range, (0, state.scale_len)) {
560 state.knob = Rect::new(inner.x, state.track.y + knob_pos, inner.width, 1)
561 } else {
562 state.knob = Rect::new(inner.x, state.track.y, inner.width, 0)
563 }
564 }
565 }
566 }
567}
568
569#[cfg(feature = "unstable-widget-ref")]
570impl<'a, T> StatefulWidgetRef for Slider<'a, T>
571where
572 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
573 u16: MapRange<T>,
574{
575 type State = SliderState<T>;
576
577 fn render_ref(&self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
578 render_slider(self, area, buf, state);
579 }
580}
581
582impl<T> StatefulWidget for Slider<'_, T>
583where
584 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
585 u16: MapRange<T>,
586{
587 type State = SliderState<T>;
588
589 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
590 render_slider(&self, area, buf, state);
591 }
592}
593
594fn render_slider<T>(
595 widget: &Slider<'_, T>,
596 area: Rect,
597 buf: &mut Buffer,
598 state: &mut SliderState<T>,
599) where
600 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
601 u16: MapRange<T>,
602{
603 widget.layout(area, state);
604
605 if let Some(block) = widget.block.as_ref() {
606 block.render(area, buf);
607 } else {
608 buf.set_style(area, widget.style);
609 }
610
611 let style = if widget.style == Default::default() {
612 Style::default().black().on_gray()
613 } else {
614 widget.style
615 };
616 let bounds_style = if let Some(bounds_style) = widget.bounds_style {
617 style.patch(bounds_style)
618 } else {
619 style
620 };
621 let knob_style = if state.is_focused() {
622 if let Some(focus_style) = widget.focus_style {
623 style.patch(focus_style)
624 } else {
625 revert_style(style)
626 }
627 } else if let Some(knob_style) = widget.knob_style {
628 style.patch(knob_style)
629 } else {
630 revert_style(style)
631 };
632
633 if let Some(lower_bound_str) = widget.lower_bound.as_ref() {
634 match widget.direction {
635 Direction::Horizontal => {
636 buf.set_style(state.lower_bound, bounds_style);
637
638 let lower_height = lower_bound_str.split('\n').count() as u16;
640 let y_offset = match widget.text_align {
641 Alignment::Left => 0,
642 Alignment::Center => state.lower_bound.height.saturating_sub(lower_height) / 2,
643 Alignment::Right => state.lower_bound.height.saturating_sub(lower_height),
644 };
645 let txt_area = Rect::new(
646 state.lower_bound.x,
647 state.lower_bound.y + y_offset,
648 state.lower_bound.width,
649 state.lower_bound.height,
650 );
651
652 Text::from(lower_bound_str.as_ref())
653 .alignment(widget.text_align)
654 .render(txt_area, buf);
655 }
656 Direction::Vertical => {
657 Text::from(lower_bound_str.as_ref())
658 .style(bounds_style)
659 .alignment(widget.text_align)
660 .render(state.lower_bound, buf);
661 }
662 }
663 }
664 if let Some(upper_bound_str) = widget.upper_bound.as_ref() {
665 match widget.direction {
666 Direction::Horizontal => {
667 buf.set_style(state.upper_bound, bounds_style);
668
669 let upper_height = upper_bound_str.split('\n').count() as u16;
671 let y_offset = match widget.text_align {
672 Alignment::Left => 0,
673 Alignment::Center => state.upper_bound.height.saturating_sub(upper_height) / 2,
674 Alignment::Right => state.upper_bound.height.saturating_sub(upper_height),
675 };
676
677 let txt_area = Rect::new(
678 state.upper_bound.x,
679 state.upper_bound.y + y_offset,
680 state.upper_bound.width,
681 state.upper_bound.height,
682 );
683
684 Text::from(upper_bound_str.as_ref())
685 .alignment(widget.text_align)
686 .render(txt_area, buf);
687 }
688 Direction::Vertical => {
689 Text::from(upper_bound_str.as_ref())
690 .style(bounds_style)
691 .alignment(widget.text_align)
692 .render(state.upper_bound, buf);
693 }
694 }
695 }
696
697 let track_str = widget.track_char.as_ref().unwrap_or(&Cow::Borrowed(" "));
698 if " " != track_str.as_ref() {
699 for y in state.track.top()..state.track.bottom() {
700 for x in state.track.left()..state.track.right() {
701 if let Some(cell) = buf.cell_mut((x, y)) {
702 cell.set_symbol(track_str.as_ref());
703 }
704 }
705 }
706 }
707
708 match widget.direction {
709 Direction::Horizontal => {
710 let knob_str = widget.render_knob_str(state.knob.height, state.is_focused());
711 Text::from(knob_str.as_ref())
712 .style(knob_style)
713 .render(state.knob, buf);
714 }
715 Direction::Vertical => {
716 let knob_str = widget.render_knob_str(state.knob.width, state.is_focused());
717 Line::from(knob_str)
718 .alignment(widget.text_align)
719 .style(knob_style)
720 .render(state.knob, buf);
721 }
722 }
723}
724
725impl<T> Debug for SliderState<T>
726where
727 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
728 u16: MapRange<T>,
729{
730 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
731 f.debug_struct("SliderState")
732 .field("area", &self.area)
733 .field("inner", &self.inner)
734 .field("lower_bound", &self.lower_bound)
735 .field("upper_bound", &self.upper_bound)
736 .field("track", &self.track)
737 .field("knob", &self.knob)
738 .field("scale_len", &self.scale_len)
739 .field("direction", &self.direction)
740 .field("range", &self.range)
741 .field("step", &self.step)
742 .field("long_step", &self.long_step)
743 .field("value", &self.value)
744 .field("focus", &self.focus)
745 .field("mouse", &self.mouse)
746 .finish()
747 }
748}
749
750impl<T> HasFocus for SliderState<T>
751where
752 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
753 u16: MapRange<T>,
754{
755 fn build(&self, builder: &mut FocusBuilder) {
756 builder.leaf_widget(self);
757 }
758
759 fn focus(&self) -> FocusFlag {
760 self.focus.clone()
761 }
762
763 fn area(&self) -> Rect {
764 self.area
765 }
766}
767
768impl<T> RelocatableState for SliderState<T>
769where
770 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
771 u16: MapRange<T>,
772{
773 fn relocate(&mut self, shift: (i16, i16), clip: Rect) {
774 self.area = relocate_area(self.area, shift, clip);
775 self.inner = relocate_area(self.inner, shift, clip);
776 self.lower_bound = relocate_area(self.lower_bound, shift, clip);
777 self.upper_bound = relocate_area(self.upper_bound, shift, clip);
778 self.track = relocate_area(self.track, shift, clip);
779 self.knob = relocate_area(self.knob, shift, clip);
780 }
781}
782
783macro_rules! slider_new {
784 ($tt:ty) => {
785 impl Default for SliderState<$tt> {
786 fn default() -> Self {
787 Self {
788 area: Default::default(),
789 inner: Default::default(),
790 lower_bound: Default::default(),
791 upper_bound: Default::default(),
792 track: Default::default(),
793 knob: Default::default(),
794 scale_len: 0,
795 direction: Default::default(),
796 range: (<$tt>::MIN, <$tt>::MAX),
797 step: 1,
798 long_step: None,
799 value: Default::default(),
800 focus: Default::default(),
801 mouse: Default::default(),
802 non_exhaustive: NonExhaustive,
803 }
804 }
805 }
806
807 impl SliderState<$tt> {
808 pub fn new() -> Self {
809 Self::new_range((<$tt>::MIN, <$tt>::MAX), 1)
810 }
811 }
812 };
813}
814macro_rules! slider_new_f {
815 ($tt:ty) => {
816 impl Default for SliderState<$tt> {
817 fn default() -> Self {
818 Self {
819 area: Default::default(),
820 inner: Default::default(),
821 lower_bound: Default::default(),
822 upper_bound: Default::default(),
823 track: Default::default(),
824 knob: Default::default(),
825 scale_len: 0,
826 direction: Default::default(),
827 range: (<$tt>::MIN, <$tt>::MAX),
828 step: 1.,
829 long_step: None,
830 value: Default::default(),
831 focus: Default::default(),
832 mouse: Default::default(),
833 non_exhaustive: NonExhaustive,
834 }
835 }
836 }
837
838 impl SliderState<$tt> {
839 pub fn new() -> Self {
840 Self::new_range((<$tt>::MIN, <$tt>::MAX), 1.)
841 }
842 }
843 };
844}
845
846impl<T> Clone for SliderState<T>
847where
848 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
849 u16: MapRange<T>,
850{
851 fn clone(&self) -> Self {
852 Self {
853 area: self.area,
854 inner: self.inner,
855 lower_bound: self.lower_bound,
856 upper_bound: self.upper_bound,
857 track: self.track,
858 knob: self.knob,
859 scale_len: self.scale_len,
860 direction: self.direction,
861 range: self.range,
862 step: self.step,
863 long_step: self.long_step,
864 value: self.value,
865 focus: FocusFlag::named(self.focus.name()),
866 mouse: Default::default(),
867 non_exhaustive: NonExhaustive,
868 }
869 }
870}
871
872slider_new!(u8);
873slider_new!(u16);
874slider_new!(u32);
875slider_new!(u64);
876slider_new!(usize);
877slider_new!(i8);
878slider_new!(i16);
879slider_new!(i32);
880slider_new!(i64);
881slider_new!(isize);
882slider_new_f!(f32);
883slider_new_f!(f64);
884
885impl<T> SliderState<T>
886where
887 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
888 u16: MapRange<T>,
889{
890 pub fn new_range(range: (T, T), step: T::Step) -> Self {
894 Self {
895 area: Default::default(),
896 inner: Default::default(),
897 lower_bound: Default::default(),
898 upper_bound: Default::default(),
899 track: Default::default(),
900 knob: Default::default(),
901 scale_len: 0,
902 direction: Default::default(),
903 range,
904 step,
905 long_step: None,
906 value: Default::default(),
907 focus: Default::default(),
908 mouse: Default::default(),
909 non_exhaustive: NonExhaustive,
910 }
911 }
912
913 pub fn set_value(&mut self, value: T) -> bool {
919 let old_value = self.value;
920 self.value = value;
921 old_value != value
922 }
923
924 pub fn value(&self) -> T {
926 self.value
927 }
928
929 pub fn set_range(&mut self, range: (T, T)) {
931 self.range = range;
932 }
933
934 pub fn range(&self) -> (T, T) {
936 self.range
937 }
938
939 pub fn set_step(&mut self, step: T::Step) {
941 self.step = step;
942 }
943
944 pub fn step(&self) -> T::Step {
946 self.step
947 }
948
949 pub fn set_long_step(&mut self, step: T::Step) {
951 self.long_step = Some(step);
952 }
953
954 pub fn long_step(&self) -> Option<T::Step> {
956 self.long_step
957 }
958
959 #[allow(clippy::should_implement_trait)]
961 pub fn next(&mut self) -> bool {
962 let old_value = self.value;
963 self.value = self.value.add_clamp(self.step, self.range);
964 old_value != self.value
965 }
966
967 pub fn prev(&mut self) -> bool {
969 let old_value = self.value;
970 self.value = self.value.sub_clamp(self.step, self.range);
971 old_value != self.value
972 }
973
974 pub fn next_major(&mut self) -> bool {
976 let old_value = self.value;
977 if let Some(long_step) = self.long_step {
978 self.value = self.value.add_clamp(long_step, self.range);
979 }
980 old_value != self.value
981 }
982
983 pub fn prev_major(&mut self) -> bool {
985 let old_value = self.value;
986 if let Some(long_step) = self.long_step {
987 self.value = self.value.sub_clamp(long_step, self.range);
988 }
989 old_value != self.value
990 }
991
992 pub fn clicked_at(&mut self, x: u16, y: u16) -> bool {
995 match self.direction {
996 Direction::Horizontal => {
997 let x_pos = x.saturating_sub(self.track.x);
998 if x_pos >= self.track.width {
999 self.value = self.range.1;
1000 true
1001 } else if let Some(value) = x_pos.map_range((0, self.scale_len), self.range) {
1002 self.value = value;
1003 true
1004 } else {
1005 false
1006 }
1007 }
1008 Direction::Vertical => {
1009 let y_pos = y.saturating_sub(self.track.y);
1010 if y_pos >= self.track.height {
1011 self.value = self.range.1;
1012 true
1013 } else if let Some(value) = y_pos.map_range((0, self.scale_len), self.range) {
1014 self.value = value;
1015 true
1016 } else {
1017 false
1018 }
1019 }
1020 }
1021 }
1022}
1023
1024impl<T> HandleEvent<crossterm::event::Event, Regular, SliderOutcome> for SliderState<T>
1025where
1026 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
1027 u16: MapRange<T>,
1028{
1029 fn handle(&mut self, event: &crossterm::event::Event, _qualifier: Regular) -> SliderOutcome {
1030 let r = if self.is_focused() {
1031 match event {
1032 ct_event!(keycode press CONTROL-Left)
1033 | ct_event!(keycode press CONTROL-Up)
1034 | ct_event!(keycode press Home) => {
1035 if self.set_value(self.range.0) {
1036 SliderOutcome::Value
1037 } else {
1038 SliderOutcome::Unchanged
1039 }
1040 }
1041
1042 ct_event!(keycode press CONTROL-Right)
1043 | ct_event!(keycode press CONTROL-Down)
1044 | ct_event!(keycode press End) => {
1045 if self.set_value(self.range.1) {
1046 SliderOutcome::Value
1047 } else {
1048 SliderOutcome::Unchanged
1049 }
1050 }
1051
1052 ct_event!(keycode press Up)
1053 | ct_event!(keycode press Left)
1054 | ct_event!(key press '-') => {
1055 if self.prev() {
1056 SliderOutcome::Value
1057 } else {
1058 SliderOutcome::Unchanged
1059 }
1060 }
1061 ct_event!(keycode press Down)
1062 | ct_event!(keycode press Right)
1063 | ct_event!(key press '+') => {
1064 if self.next() {
1065 SliderOutcome::Value
1066 } else {
1067 SliderOutcome::Unchanged
1068 }
1069 }
1070
1071 ct_event!(keycode press PageUp)
1072 | ct_event!(keycode press ALT-Up)
1073 | ct_event!(keycode press ALT-Left)
1074 | ct_event!(key press ALT-'-') => {
1075 if self.prev_major() {
1076 SliderOutcome::Value
1077 } else {
1078 SliderOutcome::Unchanged
1079 }
1080 }
1081 ct_event!(keycode press PageDown)
1082 | ct_event!(keycode press ALT-Down)
1083 | ct_event!(keycode press ALT-Right)
1084 | ct_event!(key press ALT-'+') => {
1085 if self.next_major() {
1086 SliderOutcome::Value
1087 } else {
1088 SliderOutcome::Unchanged
1089 }
1090 }
1091 _ => SliderOutcome::Continue,
1092 }
1093 } else {
1094 SliderOutcome::Continue
1095 };
1096
1097 if r == SliderOutcome::Continue {
1098 HandleEvent::handle(self, event, MouseOnly)
1099 } else {
1100 r
1101 }
1102 }
1103}
1104
1105impl<T> HandleEvent<crossterm::event::Event, MouseOnly, SliderOutcome> for SliderState<T>
1106where
1107 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
1108 u16: MapRange<T>,
1109{
1110 fn handle(&mut self, event: &crossterm::event::Event, _keymap: MouseOnly) -> SliderOutcome {
1111 match event {
1112 ct_event!(mouse drag Left for x,y) | ct_event!(mouse down Left for x,y) => {
1113 if self.inner.contains(Position::new(*x, *y)) {
1114 if self.clicked_at(*x, *y) {
1115 SliderOutcome::Value
1116 } else {
1117 SliderOutcome::Unchanged
1118 }
1119 } else {
1120 SliderOutcome::Continue
1121 }
1122 }
1123 ct_event!(scroll down for x,y) => {
1124 if self.track.contains(Position::new(*x, *y)) {
1125 if self.next() {
1126 SliderOutcome::Value
1127 } else {
1128 SliderOutcome::Unchanged
1129 }
1130 } else {
1131 SliderOutcome::Continue
1132 }
1133 }
1134 ct_event!(scroll up for x,y) => {
1135 if self.track.contains(Position::new(*x, *y)) {
1136 if self.prev() {
1137 SliderOutcome::Value
1138 } else {
1139 SliderOutcome::Unchanged
1140 }
1141 } else {
1142 SliderOutcome::Continue
1143 }
1144 }
1145 ct_event!(scroll ALT down for x,y) => {
1146 if self.track.contains(Position::new(*x, *y)) {
1147 if self.next_major() {
1148 SliderOutcome::Value
1149 } else {
1150 SliderOutcome::Unchanged
1151 }
1152 } else {
1153 SliderOutcome::Continue
1154 }
1155 }
1156 ct_event!(scroll ALT up for x,y) => {
1157 if self.track.contains(Position::new(*x, *y)) {
1158 if self.prev_major() {
1159 SliderOutcome::Value
1160 } else {
1161 SliderOutcome::Unchanged
1162 }
1163 } else {
1164 SliderOutcome::Continue
1165 }
1166 }
1167 _ => SliderOutcome::Continue,
1168 }
1169 }
1170}
1171
1172pub fn handle_events<T>(
1176 state: &mut SliderState<T>,
1177 focus: bool,
1178 event: &crossterm::event::Event,
1179) -> SliderOutcome
1180where
1181 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
1182 u16: MapRange<T>,
1183{
1184 state.focus.set(focus);
1185 HandleEvent::handle(state, event, Regular)
1186}
1187
1188pub fn handle_mouse_events<T>(
1190 state: &mut SliderState<T>,
1191 event: &crossterm::event::Event,
1192) -> SliderOutcome
1193where
1194 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
1195 u16: MapRange<T>,
1196{
1197 HandleEvent::handle(state, event, MouseOnly)
1198}