rat_widget/
slider.rs

1//!
2//! Slider widget.
3//!
4//! ```rust no_run
5//! use rat_widget::slider::{Slider, SliderState};
6//! # use ratatui::layout::Rect;
7//! # use ratatui::prelude::*;
8//! #
9//! # let slider_area = Rect::ZERO;
10//! # let mut buf = Buffer::default();
11//!
12//! let mut state = SliderState::<u8>::new_range((0,255), 1);
13//! state.set_value(42);
14//!
15//! Slider::new().render(slider_area, &mut buf, &mut state);
16//!
17//! ```
18//!
19
20use 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/// Slider widget for a type T.
42///
43/// T has to implement [RangeOp] and [MapRange] to and from u16.
44///
45#[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    /// Base style.
79    pub style: Style,
80    /// Style for the upper/lower bounds text.
81    pub bounds: Option<Style>,
82    /// Style for the knob.
83    pub knob: Option<Style>,
84    /// Style when focused.
85    pub focus: Option<Style>,
86
87    /// Alignment for all text.
88    pub text_align: Option<Alignment>,
89    /// Text string for the lower bound. Can contain newlines.
90    pub lower_bound: Option<&'static str>,
91    /// Text string for the upper bound. Can contain newlines.
92    pub upper_bound: Option<&'static str>,
93
94    /// Fill char for the track.
95    pub track_char: Option<&'static str>,
96
97    /// Text for the knob in vertical mode.
98    pub vertical_knob: Option<&'static str>,
99    /// Text for the knob in horizontal mode.
100    pub horizontal_knob: Option<&'static str>,
101
102    /// Border
103    pub block: Option<Block<'static>>,
104
105    pub non_exhaustive: NonExhaustive,
106}
107
108/// State.
109pub struct SliderState<T>
110where
111    T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
112    u16: MapRange<T>,
113{
114    /// Complete area
115    /// __read only__. renewed for each render.
116    pub area: Rect,
117    /// Area inside the block without padding due to alignment.
118    /// __read only__. renewed for each render.
119    pub inner: Rect,
120    /// Lower bounds area.
121    /// __read only__. renewed for each render.
122    pub lower_bound: Rect,
123    /// Upper bounds area.
124    /// __read only__. renewed for each render.
125    pub upper_bound: Rect,
126    /// Track char.
127    /// __read only__. renewed for each render.
128    pub track: Rect,
129    /// Knob text
130    /// __read only__. renewed for each render.
131    pub knob: Rect,
132    /// Length of the track without the knob
133    pub scale_len: u16,
134    /// Direction
135    /// __read only__. renewed for each render.
136    pub direction: Direction,
137
138    /// Value range
139    pub range: (T, T),
140    /// Minor step.
141    pub step: <T as RangeOp>::Step,
142    /// Major step.
143    pub long_step: Option<<T as RangeOp>::Step>,
144
145    /// Value
146    pub value: T,
147
148    /// Current focus state.
149    /// __read+write__
150    pub focus: FocusFlag,
151
152    /// Mouse helper
153    /// __read+write__
154    pub mouse: MouseFlags,
155
156    pub non_exhaustive: NonExhaustive,
157}
158
159pub(crate) mod event {
160    use rat_event::{ConsumedEvent, Outcome};
161
162    /// Result value for event-handling.
163    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
164    pub enum SliderOutcome {
165        /// The given event was not handled at all.
166        Continue,
167        /// The event was handled, no repaint necessary.
168        Unchanged,
169        /// The event was handled, repaint necessary.
170        Changed,
171        /// The value has changed.
172        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    /// New
245    pub fn new() -> Self {
246        Default::default()
247    }
248
249    /// Direction for the slider.
250    pub fn direction(mut self, direction: Direction) -> Self {
251        self.direction = direction;
252        self
253    }
254
255    /// Overrides the range of the slider.
256    pub fn range(mut self, range: (T, T)) -> Self {
257        self.range = Some(range);
258        self
259    }
260
261    /// First step size.
262    pub fn step(mut self, step: <T as RangeOp>::Step) -> Self {
263        self.step = Some(step);
264        self
265    }
266
267    /// Second step size.
268    pub fn long_step(mut self, step: <T as RangeOp>::Step) -> Self {
269        self.long_step = Some(step);
270        self
271    }
272
273    /// Set all styles.
274    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    /// Base style.
311    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    /// Style for focus.
318    pub fn focus_style(mut self, style: Style) -> Self {
319        self.focus_style = Some(style);
320        self
321    }
322
323    /// Style for the bounds text.
324    pub fn bounds_style(mut self, style: Style) -> Self {
325        self.bounds_style = Some(style);
326        self
327    }
328
329    /// Style for the knob.
330    pub fn knob_style(mut self, style: Style) -> Self {
331        self.knob_style = Some(style);
332        self
333    }
334
335    /// Text alignment. Used for the bounds and the knob.
336    pub fn text_align(mut self, align: Alignment) -> Self {
337        self.text_align = align;
338        self
339    }
340
341    /// Text for the lower bound. Can contain newlines.
342    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    /// Text for the upper bound. Can contain newlines.
348    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    /// Fill char for the track.
354    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    /// Text for the horizontal knob. Can contain newlines for
360    /// multiline sliders.
361    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    /// Text for the vertical knob. Can contain newlines for a
367    /// multiline knob.
368    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    /// Block for borders.
374    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    // Creates the default knob text.
387    // knob_repeat is either rows/columns, the off direction.
388    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    // layout
473    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                // need to vertically align manually.
637                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                // need to vertically align manually.
668                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    /// New state with a given range and step.
889    ///
890    /// The range will still be overridden when set with the Widget.
891    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    /// Set the value.
912    ///
913    /// Any value you set is good, there will be no bounds check.
914    /// Without user interaction the same value will be returned
915    /// by value().
916    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    /// Current value.
923    pub fn value(&self) -> T {
924        self.value
925    }
926
927    /// Set the range.
928    pub fn set_range(&mut self, range: (T, T)) {
929        self.range = range;
930    }
931
932    /// Range.
933    pub fn range(&self) -> (T, T) {
934        self.range
935    }
936
937    /// Minor step size.
938    pub fn set_step(&mut self, step: T::Step) {
939        self.step = step;
940    }
941
942    /// Minor step size.
943    pub fn step(&self) -> T::Step {
944        self.step
945    }
946
947    /// Major step size.
948    pub fn set_long_step(&mut self, step: T::Step) {
949        self.long_step = Some(step);
950    }
951
952    /// Major step size.
953    pub fn long_step(&self) -> Option<T::Step> {
954        self.long_step
955    }
956
957    /// Next value by one step.
958    #[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    /// Previous value by one step.
966    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    /// Next value by one major step.
973    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    /// Previous value by one major step.
982    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    /// Clicked in the range or at the boundary.
991    /// Transforms the relative screen position to a value.
992    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
1170/// Handle all events.
1171/// Text events are only processed if focus is true.
1172/// Mouse events are processed if they are in range.
1173pub 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
1186/// Handle only mouse-events.
1187pub 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}