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