1use 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#[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 pub style: Style,
82 pub block: Option<Block<'static>>,
84 pub border_style: Option<Style>,
85 pub title_style: Option<Style>,
86 pub bounds: Option<Style>,
88 pub knob: Option<Style>,
90 pub focus: Option<Style>,
92
93 pub text_align: Option<Alignment>,
95 pub lower_bound: Option<&'static str>,
97 pub upper_bound: Option<&'static str>,
99
100 pub track_char: Option<&'static str>,
102
103 pub vertical_knob: Option<&'static str>,
105 pub horizontal_knob: Option<&'static str>,
107
108 pub non_exhaustive: NonExhaustive,
109}
110
111pub struct SliderState<T = usize>
113where
114 T: RangeOp<Step: Copy + Debug> + MapRange<u16> + Debug + Default + Copy + PartialEq,
115 u16: MapRange<T>,
116{
117 pub area: Rect,
120 pub inner: Rect,
123 pub lower_bound: Rect,
126 pub upper_bound: Rect,
129 pub track: Rect,
132 pub knob: Rect,
135 pub scale_len: u16,
137 pub direction: Direction,
140
141 pub range: (T, T),
144 pub step: <T as RangeOp>::Step,
147 pub long_step: Option<<T as RangeOp>::Step>,
150
151 pub value: T,
153
154 pub focus: FocusFlag,
157
158 pub mouse: MouseFlags,
161
162 pub non_exhaustive: NonExhaustive,
163}
164
165pub(crate) mod event {
166 use rat_event::{ConsumedEvent, Outcome};
167
168 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
170 pub enum SliderOutcome {
171 Continue,
173 Unchanged,
175 Changed,
177 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 pub fn new() -> Self {
254 Default::default()
255 }
256
257 pub fn direction(mut self, direction: Direction) -> Self {
259 self.direction = direction;
260 self
261 }
262
263 pub fn range(mut self, range: (T, T)) -> Self {
265 self.range = Some(range);
266 self
267 }
268
269 pub fn step(mut self, step: <T as RangeOp>::Step) -> Self {
271 self.step = Some(step);
272 self
273 }
274
275 pub fn long_step(mut self, step: <T as RangeOp>::Step) -> Self {
277 self.long_step = Some(step);
278 self
279 }
280
281 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 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 pub fn focus_style(mut self, style: Style) -> Self {
333 self.focus_style = Some(style);
334 self
335 }
336
337 pub fn bounds_style(mut self, style: Style) -> Self {
339 self.bounds_style = Some(style);
340 self
341 }
342
343 pub fn knob_style(mut self, style: Style) -> Self {
345 self.knob_style = Some(style);
346 self
347 }
348
349 pub fn text_align(mut self, align: Alignment) -> Self {
351 self.text_align = align;
352 self
353 }
354
355 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 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 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 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 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 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 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 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 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 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 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 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 pub fn value(&self) -> T {
1003 self.value
1004 }
1005
1006 pub fn clear(&mut self) {
1008 self.value = self.range.0;
1009 }
1010
1011 pub fn set_range(&mut self, range: (T, T)) {
1013 self.range = range;
1014 }
1015
1016 pub fn range(&self) -> (T, T) {
1018 self.range
1019 }
1020
1021 pub fn set_step(&mut self, step: T::Step) {
1023 self.step = step;
1024 }
1025
1026 pub fn step(&self) -> T::Step {
1028 self.step
1029 }
1030
1031 pub fn set_long_step(&mut self, step: T::Step) {
1033 self.long_step = Some(step);
1034 }
1035
1036 pub fn long_step(&self) -> Option<T::Step> {
1038 self.long_step
1039 }
1040
1041 #[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 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 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 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 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
1269pub 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
1281pub 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}