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