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 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 pub style: Style,
80 pub block: Option<Block<'static>>,
82 pub border_style: Option<Style>,
83 pub title_style: Option<Style>,
84 pub bounds: Option<Style>,
86 pub knob: Option<Style>,
88 pub focus: Option<Style>,
90
91 pub text_align: Option<Alignment>,
93 pub lower_bound: Option<&'static str>,
95 pub upper_bound: Option<&'static str>,
97
98 pub track_char: Option<&'static str>,
100
101 pub vertical_knob: Option<&'static str>,
103 pub horizontal_knob: Option<&'static str>,
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 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 pub fn new() -> Self {
252 Default::default()
253 }
254
255 pub fn direction(mut self, direction: Direction) -> Self {
257 self.direction = direction;
258 self
259 }
260
261 pub fn range(mut self, range: (T, T)) -> Self {
263 self.range = Some(range);
264 self
265 }
266
267 pub fn step(mut self, step: <T as RangeOp>::Step) -> Self {
269 self.step = Some(step);
270 self
271 }
272
273 pub fn long_step(mut self, step: <T as RangeOp>::Step) -> Self {
275 self.long_step = Some(step);
276 self
277 }
278
279 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 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 pub fn focus_style(mut self, style: Style) -> Self {
331 self.focus_style = Some(style);
332 self
333 }
334
335 pub fn bounds_style(mut self, style: Style) -> Self {
337 self.bounds_style = Some(style);
338 self
339 }
340
341 pub fn knob_style(mut self, style: Style) -> Self {
343 self.knob_style = Some(style);
344 self
345 }
346
347 pub fn text_align(mut self, align: Alignment) -> Self {
349 self.text_align = align;
350 self
351 }
352
353 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 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 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 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 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 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 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 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 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 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 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 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 pub fn value(&self) -> T {
1001 self.value
1002 }
1003
1004 pub fn clear(&mut self) {
1006 self.value = self.range.0;
1007 }
1008
1009 pub fn set_range(&mut self, range: (T, T)) {
1011 self.range = range;
1012 }
1013
1014 pub fn range(&self) -> (T, T) {
1016 self.range
1017 }
1018
1019 pub fn set_step(&mut self, step: T::Step) {
1021 self.step = step;
1022 }
1023
1024 pub fn step(&self) -> T::Step {
1026 self.step
1027 }
1028
1029 pub fn set_long_step(&mut self, step: T::Step) {
1031 self.long_step = Some(step);
1032 }
1033
1034 pub fn long_step(&self) -> Option<T::Step> {
1036 self.long_step
1037 }
1038
1039 #[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 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 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 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 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
1267pub 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
1283pub 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}