1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12zng_wgt::enable_widget_macros!();
13
14pub mod thumb;
15
16use std::{any::Any, fmt, sync::Arc};
17
18use colors::ACCENT_COLOR_VAR;
19use parking_lot::Mutex;
20use zng_ext_input::{
21 mouse::{ButtonState, MOUSE_INPUT_EVENT, MOUSE_MOVE_EVENT},
22 pointer_capture::CaptureMode,
23 touch::{TOUCH_INPUT_EVENT, TouchPhase},
24};
25use zng_var::{AnyVar, AnyVarValue};
26use zng_wgt::prelude::*;
27use zng_wgt_input::{focus::FocusableMix, pointer_capture::capture_pointer};
28use zng_wgt_style::{Style, StyleMix, impl_style_fn};
29
30#[widget($crate::Slider)]
32pub struct Slider(FocusableMix<StyleMix<WidgetBase>>);
33impl Slider {
34 fn widget_intrinsic(&mut self) {
35 self.style_intrinsic(STYLE_FN_VAR, property_id!(self::style_fn));
36 }
37}
38impl_style_fn!(Slider, DefaultStyle);
39
40#[widget($crate::DefaultStyle)]
42pub struct DefaultStyle(Style);
43impl DefaultStyle {
44 fn widget_intrinsic(&mut self) {
45 widget_set! {
46 self;
47 zng_wgt_container::child = SliderTrack! {
48 zng_wgt::corner_radius = 5;
49 zng_wgt_fill::background_color = ACCENT_COLOR_VAR.rgba();
50 zng_wgt::margin = 8; when #{SLIDER_DIRECTION_VAR}.is_horizontal() {
53 zng_wgt_size_offset::height = 5;
54 }
55 when #{SLIDER_DIRECTION_VAR}.is_vertical() {
56 zng_wgt_size_offset::width = 5;
57 }
58 };
59 zng_wgt_container::child_align = Align::FILL_X;
60 }
61 }
62}
63
64trait SelectorImpl: Send {
65 fn selection(&self) -> AnyVar;
66 fn thumbs(&self) -> Var<Vec<ThumbValue>>;
67 fn set(&self, nearest: Factor, to: Factor);
68
69 fn to_offset(&self, t: &dyn AnyVarValue) -> Option<Factor>;
70 #[allow(clippy::wrong_self_convention)]
71 fn from_offset(&self, offset: Factor) -> Box<dyn Any>;
72}
73
74trait OffsetConvert<T>: Send + Sync {
75 fn to(&self, t: &T) -> Factor;
76 fn from(&self, f: Factor) -> T;
77}
78impl<T, Tf: Fn(&T) -> Factor + Send + Sync, Ff: Fn(Factor) -> T + Send + Sync> OffsetConvert<T> for (Tf, Ff) {
79 fn to(&self, t: &T) -> Factor {
80 (self.0)(t)
81 }
82
83 fn from(&self, f: Factor) -> T {
84 (self.1)(f)
85 }
86}
87
88pub trait SelectorValue: VarValue {
95 fn to_selector(value: Var<Self>, min: Self, max: Self) -> Selector;
97}
98
99#[derive(Clone)]
103pub struct Selector(Arc<Mutex<dyn SelectorImpl>>);
104impl fmt::Debug for Selector {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 write!(f, "Selector(_)")
107 }
108}
109impl PartialEq for Selector {
110 fn eq(&self, other: &Self) -> bool {
111 Arc::ptr_eq(&self.0, &other.0)
112 }
113}
114impl Selector {
115 pub fn value<T: SelectorValue>(selection: impl IntoVar<T>, min: T, max: T) -> Self {
117 T::to_selector(selection.into_var(), min, max)
118 }
119
120 pub fn value_with<T>(
125 selection: impl IntoVar<T>,
126 to_offset: impl Fn(&T) -> Factor + Send + Sync + 'static,
127 from_offset: impl Fn(Factor) -> T + Send + Sync + 'static,
128 ) -> Self
129 where
130 T: VarValue,
131 {
132 struct SingleImpl<T: VarValue> {
133 selection: Var<T>,
134 thumbs: Var<Vec<ThumbValue>>,
135 to_from: Arc<dyn OffsetConvert<T>>,
136 }
137 impl<T: VarValue> SelectorImpl for SingleImpl<T> {
138 fn selection(&self) -> AnyVar {
139 self.selection.as_any().clone()
140 }
141
142 fn set(&self, _: Factor, to: Factor) {
143 self.selection.set(self.to_from.from(to));
144 }
145
146 fn thumbs(&self) -> Var<Vec<ThumbValue>> {
147 self.thumbs.clone()
148 }
149
150 fn to_offset(&self, t: &dyn AnyVarValue) -> Option<Factor> {
151 let f = self.to_from.to(t.downcast_ref::<T>()?);
152 Some(f)
153 }
154
155 fn from_offset(&self, offset: Factor) -> Box<dyn Any> {
156 Box::new(self.to_from.from(offset))
157 }
158 }
159 let to_from = Arc::new((to_offset, from_offset));
160 let selection = selection.into_var();
161 let thumbs = selection.map(clmv!(to_from, |s| vec![ThumbValue {
162 offset: to_from.to(s),
163 n_of: (0, 1)
164 }]));
165 Self(Arc::new(Mutex::new(SingleImpl {
166 thumbs,
167 selection,
168 to_from,
169 })))
170 }
171
172 pub fn many<T: SelectorValue>(many: impl IntoVar<Vec<T>>, min: T, max: T) -> Self {
174 let convert = T::to_selector(zng_var::const_var(min.clone()), min, max);
176 Self::many_with(many.into_var(), clmv!(convert, |t| convert.to_offset(t).unwrap()), move |f| {
177 convert.from_offset(f).unwrap()
178 })
179 }
180
181 pub fn many_with<T>(
187 many: impl IntoVar<Vec<T>>,
188 to_offset: impl Fn(&T) -> Factor + Send + Sync + 'static,
189 from_offset: impl Fn(Factor) -> T + Send + Sync + 'static,
190 ) -> Self
191 where
192 T: VarValue,
193 {
194 struct ManyImpl<T: VarValue> {
195 selection: Var<Vec<T>>,
196 thumbs: Var<Vec<ThumbValue>>,
197 to_from: Arc<dyn OffsetConvert<T>>,
198 }
199 impl<T: VarValue> SelectorImpl for ManyImpl<T> {
200 fn selection(&self) -> AnyVar {
201 self.selection.as_any().clone()
202 }
203
204 fn set(&self, from: Factor, to: Factor) {
205 let mut selection = self.selection.get();
209 if selection.is_empty() {
210 return;
211 }
212
213 let to_value = self.to_from.from(to);
214
215 let (remove_i, mut insert_i) = self.thumbs.with(|t| {
216 let (remove_i, _) = t
217 .iter()
218 .enumerate()
219 .map(|(i, f)| (i, (f.offset - from).abs()))
220 .reduce(|a, b| if a.1 < b.1 { a } else { b })
221 .unwrap_or((t.len(), 0.fct()));
222 let insert_i = t.iter().position(|t| t.offset >= to).unwrap_or(t.len());
223 (remove_i, insert_i)
224 });
225
226 if remove_i == insert_i {
227 selection[remove_i] = to_value;
228 } else {
229 if insert_i > remove_i {
230 insert_i -= 1;
231 }
232 selection.remove(remove_i);
233 selection.insert(insert_i, to_value)
234 }
235
236 self.selection.set(selection);
237 }
238
239 fn thumbs(&self) -> Var<Vec<ThumbValue>> {
240 self.thumbs.clone()
241 }
242
243 fn to_offset(&self, t: &dyn AnyVarValue) -> Option<Factor> {
244 let f = self.to_from.to(t.downcast_ref::<T>()?);
245 Some(f)
246 }
247
248 fn from_offset(&self, offset: Factor) -> Box<dyn Any> {
249 Box::new(self.to_from.from(offset))
250 }
251 }
252
253 let to_from = Arc::new((to_offset, from_offset));
254 let selection = many.into_var();
255 let thumbs = selection.map(clmv!(to_from, |s| {
256 let len = s.len().min(u16::MAX as _) as u16;
257 s.iter()
258 .enumerate()
259 .take(u16::MAX as _)
260 .map(|(i, s)| ThumbValue {
261 offset: to_from.to(s),
262 n_of: (i as u16, len),
263 })
264 .collect()
265 }));
266
267 Self(Arc::new(Mutex::new(ManyImpl {
268 selection,
269 thumbs,
270 to_from,
271 })))
272 }
273
274 pub fn nil() -> Self {
276 Self::many_with(vec![], |_: &bool| 0.fct(), |_| false)
277 }
278
279 pub fn to_offset<T: VarValue>(&self, t: &T) -> Option<Factor> {
283 self.0.lock().to_offset(t)
284 }
285
286 pub fn from_offset<T: VarValue>(&self, offset: impl IntoValue<Factor>) -> Option<T> {
290 let b = self.0.lock().from_offset(offset.into()).downcast().ok()?;
291 Some(*b)
292 }
293
294 pub fn set(&self, from: impl IntoValue<Factor>, to: impl IntoValue<Factor>) {
298 self.0.lock().set(from.into(), to.into())
299 }
300
301 pub fn selection(&self) -> AnyVar {
305 self.0.lock().selection()
306 }
307
308 pub fn thumbs(&self) -> Var<Vec<ThumbValue>> {
312 self.0.lock().thumbs()
313 }
314}
315
316#[derive(Clone, Debug, PartialEq, Copy)]
318pub struct ThumbValue {
319 offset: Factor,
320 n_of: (u16, u16),
321}
322impl ThumbValue {
323 pub fn offset(&self) -> Factor {
325 self.offset
326 }
327
328 pub fn n_of(&self) -> (u16, u16) {
332 self.n_of
333 }
334
335 pub fn is_first(&self) -> bool {
337 self.n_of.0 == 0
338 }
339
340 pub fn is_last(&self) -> bool {
342 self.n_of.0 == self.n_of.1
343 }
344}
345
346context_local! {
347 pub static SELECTOR: Selector = Selector::nil();
349}
350context_var! {
351 pub static THUMB_FN_VAR: WidgetFn<ThumbArgs> = wgt_fn!(|a: ThumbArgs| thumb::Thumb!(a.thumb()));
353}
354
355#[property(CONTEXT, default(Selector::nil()), widget_impl(Slider))]
357pub fn selector(child: impl IntoUiNode, selector: impl IntoValue<Selector>) -> UiNode {
358 with_context_local(child, &SELECTOR, selector)
359}
360
361#[property(CONTEXT, default(THUMB_FN_VAR))]
365pub fn thumb_fn(child: impl IntoUiNode, thumb: impl IntoVar<WidgetFn<ThumbArgs>>) -> UiNode {
366 with_context_var(child, THUMB_FN_VAR, thumb)
367}
368
369pub struct ThumbArgs {
371 thumb: Var<ThumbValue>,
372}
373impl ThumbArgs {
374 pub fn thumb(&self) -> Var<ThumbValue> {
376 self.thumb.clone()
377 }
378}
379
380pub trait WidgetInfoExt {
382 fn is_slider_track(&self) -> bool;
384
385 fn slider_track(&self) -> Option<WidgetInfo>;
387}
388impl WidgetInfoExt for WidgetInfo {
389 fn is_slider_track(&self) -> bool {
390 self.meta().flagged(*IS_SLIDER_ID)
391 }
392
393 fn slider_track(&self) -> Option<WidgetInfo> {
394 self.self_and_ancestors().find(|w| w.is_slider_track())
395 }
396}
397
398static_id! {
399 static ref IS_SLIDER_ID: StateId<()>;
400}
401
402#[derive(Clone, Copy, PartialEq, Eq)]
404pub enum SliderDirection {
405 StartToEnd,
409 EndToStart,
413 LeftToRight,
415 RightToLeft,
417 BottomToTop,
419 TopToBottom,
421}
422impl SliderDirection {
423 pub fn is_vertical(&self) -> bool {
425 matches!(self, Self::BottomToTop | Self::TopToBottom)
426 }
427
428 pub fn is_horizontal(&self) -> bool {
430 !self.is_vertical()
431 }
432
433 pub fn layout(&self, direction: LayoutDirection) -> Self {
435 match *self {
436 SliderDirection::StartToEnd => {
437 if direction.is_ltr() {
438 Self::LeftToRight
439 } else {
440 Self::RightToLeft
441 }
442 }
443 SliderDirection::EndToStart => {
444 if direction.is_ltr() {
445 Self::RightToLeft
446 } else {
447 Self::LeftToRight
448 }
449 }
450 s => s,
451 }
452 }
453}
454impl fmt::Debug for SliderDirection {
455 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
456 if f.alternate() {
457 write!(f, "SliderDirection::")?;
458 }
459 match self {
460 Self::StartToEnd => write!(f, "StartToEnd"),
461 Self::EndToStart => write!(f, "EndToStart"),
462 Self::LeftToRight => write!(f, "LeftToRight"),
463 Self::RightToLeft => write!(f, "RightToLeft"),
464 Self::BottomToTop => write!(f, "BottomToTop"),
465 Self::TopToBottom => write!(f, "TopToBottom"),
466 }
467 }
468}
469
470context_var! {
471 pub static SLIDER_DIRECTION_VAR: SliderDirection = SliderDirection::StartToEnd;
473}
474
475#[property(CONTEXT, default(SLIDER_DIRECTION_VAR), widget_impl(Slider, DefaultStyle))]
479fn direction(child: impl IntoUiNode, direction: impl IntoVar<SliderDirection>) -> UiNode {
480 with_context_var(child, SLIDER_DIRECTION_VAR, direction)
481}
482
483#[widget($crate::SliderTrack)]
488pub struct SliderTrack(WidgetBase);
489impl SliderTrack {
490 fn widget_intrinsic(&mut self) {
491 self.widget_builder().push_build_action(|wgt| {
492 wgt.set_child(slider_track_node());
493 });
494 widget_set! {
495 self;
496 capture_pointer = CaptureMode::Subtree;
497 }
498 }
499}
500
501fn slider_track_node() -> UiNode {
502 let mut layout_direction = LayoutDirection::LTR;
503 match_node(ui_vec![], move |thumbs, op| match op {
504 UiNodeOp::Init => {
505 WIDGET
506 .sub_var(&THUMB_FN_VAR)
507 .sub_event(&MOUSE_INPUT_EVENT)
508 .sub_event(&TOUCH_INPUT_EVENT)
509 .sub_event(&MOUSE_MOVE_EVENT);
510
511 let thumb_fn = THUMB_FN_VAR.get();
512
513 let thumbs_var = SELECTOR.get().thumbs();
514 let thumbs_len = thumbs_var.with(|t| t.len());
515 let thumbs_typed = thumbs.node_impl::<UiVec>();
516 thumbs_typed.reserve(thumbs_len);
517 for i in 0..thumbs_len {
518 let thumb_var = thumbs_var.map(move |t| {
519 t.get(i).copied().unwrap_or(ThumbValue {
520 offset: 0.fct(),
521 n_of: (0, 0),
522 })
523 });
524 thumbs_typed.push(thumb_fn(ThumbArgs { thumb: thumb_var }))
525 }
526
527 thumbs.init();
528 }
529 UiNodeOp::Deinit => {
530 thumbs.deinit();
531 *thumbs.node_impl::<UiVec>() = ui_vec![];
532 }
533 UiNodeOp::Info { info } => {
534 info.flag_meta(*IS_SLIDER_ID);
535 thumbs.info(info);
536 }
537 UiNodeOp::Measure { desired_size, .. } => {
538 thumbs.delegated();
539 *desired_size = LAYOUT.constraints().fill_size();
540 }
541 UiNodeOp::Layout { final_size, wl } => {
542 *final_size = LAYOUT.constraints().fill_size();
543 layout_direction = LAYOUT.direction();
544 let _ = thumbs.layout_list(wl, |_, n, wl| n.layout(wl), |_, _| PxSize::zero());
545 }
546 UiNodeOp::Event { update } => {
547 thumbs.event(update);
548
549 let mut pos = None;
550
551 if let Some(args) = MOUSE_MOVE_EVENT.on_unhandled(update) {
552 if let Some(cap) = &args.capture
553 && cap.target.contains(WIDGET.id())
554 {
555 pos = Some(args.position);
556 args.propagation().stop();
557 }
558 } else if let Some(args) = MOUSE_INPUT_EVENT.on_unhandled(update) {
559 if args.state == ButtonState::Pressed {
560 pos = Some(args.position);
561 args.propagation().stop();
562 }
563 } else if let Some(args) = TOUCH_INPUT_EVENT.on_unhandled(update)
564 && args.phase == TouchPhase::Start
565 {
566 pos = Some(args.position);
567 args.propagation().stop();
568 }
569
570 if let Some(pos) = pos {
571 let track_info = WIDGET.info();
572 let track_bounds = track_info.inner_bounds();
573 let track_orientation = SLIDER_DIRECTION_VAR.get();
574
575 let (track_min, track_max) = match track_orientation.layout(layout_direction) {
576 SliderDirection::LeftToRight => (track_bounds.min_x(), track_bounds.max_x()),
577 SliderDirection::RightToLeft => (track_bounds.max_x(), track_bounds.min_x()),
578 SliderDirection::BottomToTop => (track_bounds.max_y(), track_bounds.min_y()),
579 SliderDirection::TopToBottom => (track_bounds.min_y(), track_bounds.max_y()),
580 _ => unreachable!(),
581 };
582 let cursor = if track_orientation.is_horizontal() {
583 pos.x.to_px(track_info.tree().scale_factor())
584 } else {
585 pos.y.to_px(track_info.tree().scale_factor())
586 };
587 let new_offset = (cursor - track_min).0 as f32 / (track_max - track_min).abs().0 as f32;
588 let new_offset = new_offset.fct().clamp_range();
589
590 let selector = crate::SELECTOR.get();
591 selector.set(new_offset, new_offset);
592 }
593 }
594 UiNodeOp::Update { updates } => {
595 if let Some(thumb_fn) = THUMB_FN_VAR.get_new() {
596 thumbs.deinit();
597
598 let thumbs_vec = thumbs.node_impl::<UiVec>();
599 thumbs_vec.clear();
600
601 let thumbs_var = SELECTOR.get().thumbs();
602 let thumbs_len = thumbs_var.with(|t| t.len());
603 thumbs_vec.reserve(thumbs_len);
604 for i in 0..thumbs_len {
605 let thumb_var = thumbs_var.map(move |t| {
606 t.get(i).copied().unwrap_or(ThumbValue {
607 offset: 0.fct(),
608 n_of: (0, 0),
609 })
610 });
611 thumbs_vec.push(thumb_fn(ThumbArgs { thumb: thumb_var }))
612 }
613
614 thumbs.init();
615
616 WIDGET.update_info().layout().render();
617 } else {
618 thumbs.update(updates);
619
620 let thumbs_var = SELECTOR.get().thumbs();
623 let thumbs_len = thumbs_var.with(|t| t.len());
624
625 match thumbs_len.cmp(&thumbs.node().children_len()) {
626 std::cmp::Ordering::Less => {
627 for mut drop in thumbs.node_impl::<UiVec>().drain(thumbs_len..) {
629 drop.deinit();
630 }
631 }
632 std::cmp::Ordering::Greater => {
633 let thumbs_vec = thumbs.node_impl::<UiVec>();
635 let thumb_fn = THUMB_FN_VAR.get();
636 let from_len = thumbs_vec.len();
637 thumbs_vec.reserve(thumbs_len - from_len);
638 for i in from_len..thumbs_len {
639 let thumb_var = thumbs_var.map(move |t| {
640 t.get(i).copied().unwrap_or(ThumbValue {
641 offset: 0.fct(),
642 n_of: (0, 0),
643 })
644 });
645 thumbs_vec.push(thumb_fn(ThumbArgs { thumb: thumb_var }))
646 }
647 }
648 std::cmp::Ordering::Equal => {}
649 }
650 }
651 }
652 _ => {}
653 })
654}
655
656macro_rules! impl_32 {
657 (($to_f32:expr, $from_f32:expr) => $($T:ident),+ $(,)?) => {
658 $(
659 impl SelectorValue for $T {
660 #[allow(clippy::unnecessary_cast)]
661 fn to_selector(value: Var<Self>, min: Self, max: Self) -> Selector {
662 let to_f32 = $to_f32;
663 let from_f32 = $from_f32;
664
665 let min = to_f32(min);
666 let max = to_f32(max);
667 if min >= max {
668 Selector::nil()
669 } else {
670 let d = max - min;
671 Selector::value_with(value, move |i| {
672 let i = to_f32(i.clone());
673 ((i - min) / d).fct()
674 }, move |f| {
675 from_f32((f.0 * d + min).round())
676 })
677 }
678 }
679 }
680 )+
681 };
682}
683impl_32!((|i| i as f32, |f| f as Self) => u32, i32, u16, i16, u8, i8, f32);
684impl_32!((|p: Self| p.0 as f32, |f| Self(f as _)) => Px, Factor, FactorPercent);
685impl_32!((|p: Dip| p.to_f32(), |f| Dip::new_f32(f)) => Dip);
686
687macro_rules! impl_64 {
688 ($($T:ident),+ $(,)?) => {
689 $(
690 impl SelectorValue for $T {
691 fn to_selector(value: Var<Self>, min: Self, max: Self) -> Selector {
692 let min = min as f64;
693 let max = max as f64;
694 if min >= max {
695 Selector::nil()
696 } else {
697 let d = max - min;
698 Selector::value_with(value, move |&i| {
699 let i = i as f64;
700 Factor(((i - min) / d) as f32)
701 }, move |f| {
702 ((f.0 as f64) * d + min).round() as Self
703 })
704 }
705 }
706 }
707 )+
708 };
709}
710impl_64!(u64, i64, u128, i128, f64);
711
712impl SelectorValue for Length {
713 fn to_selector(value: Var<Self>, min: Self, max: Self) -> Selector {
714 let (min_f32, max_f32) = LAYOUT.with_context(LayoutMetrics::new(1.fct(), PxSize::splat(Px(1000)), Px(16)), || {
715 (min.layout_f32(LayoutAxis::X), max.layout_f32(LayoutAxis::X))
716 });
717 if min_f32 >= max_f32 {
718 Selector::nil()
719 } else {
720 let d_f32 = max_f32 - min_f32;
721 let d = max - min.clone();
722 Selector::value_with(
723 value,
724 move |l| {
725 let l = LAYOUT.with_context(LayoutMetrics::new(1.fct(), PxSize::splat(Px(1000)), Px(16)), || {
726 l.layout_f32(LayoutAxis::X)
727 });
728 Factor((l - min_f32) / d_f32)
729 },
730 move |f| d.clone() * f + min.clone(),
731 )
732 }
733 }
734}
735
736#[cfg(test)]
737mod tests {
738 use super::*;
739
740 fn selector_value_t<T: SelectorValue>(min: T, max: T) {
741 let s = Selector::value(min.clone(), min.clone(), max.clone());
742 assert_eq!(s.to_offset(&min), Some(0.fct()));
743 assert_eq!(s.to_offset(&max), Some(1.fct()));
744 assert_eq!(s.from_offset(0.fct()), Some(min));
745 assert_eq!(s.from_offset(1.fct()), Some(max));
746 }
747
748 #[test]
749 fn selector_value_u8() {
750 selector_value_t(u8::MIN, u8::MAX);
751 selector_value_t(20u8, 120u8);
752 }
753
754 #[test]
755 fn selector_value_i32() {
756 selector_value_t(i32::MIN, i32::MAX);
757 selector_value_t(20i32, 120i32);
758 }
759
760 #[test]
761 fn selector_value_i64() {
762 selector_value_t(i64::MIN, i64::MAX);
763 }
764
765 #[test]
766 fn selector_value_f64() {
767 selector_value_t(-200f64, 200f64);
768 selector_value_t(20f64, 120f64);
769 }
770
771 #[test]
772 fn selector_pct() {
773 selector_value_t(0.pct(), 100.pct());
774 }
775
776 #[test]
777 fn selector_value_px() {
778 selector_value_t(Px(20), Px(200));
779 }
780
781 #[test]
782 fn selector_value_dip() {
783 selector_value_t(Dip::new(20), Dip::new(200));
784 }
785
786 #[test]
787 fn selector_value_length() {
788 selector_value_t(20.px(), 200.px());
789 }
790}