rat_widget/
slider.rs

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