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