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