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 let id = WIDGET.id();
506 WIDGET
507 .sub_var(&THUMB_FN_VAR)
508 .sub_event_when(&MOUSE_INPUT_EVENT, |args| args.state == ButtonState::Pressed)
509 .sub_event_when(&TOUCH_INPUT_EVENT, |args| args.phase == TouchPhase::Start)
510 .sub_event_when(&MOUSE_MOVE_EVENT, move |args| {
511 args.capture.as_ref().map(|c| c.target.contains(id)).unwrap_or(false)
513 });
514
515 let thumb_fn = THUMB_FN_VAR.get();
516
517 let thumbs_var = SELECTOR.get().thumbs();
518 let thumbs_len = thumbs_var.with(|t| t.len());
519 let thumbs_typed = thumbs.node_impl::<UiVec>();
520 thumbs_typed.reserve(thumbs_len);
521 for i in 0..thumbs_len {
522 let thumb_var = thumbs_var.map(move |t| {
523 t.get(i).copied().unwrap_or(ThumbValue {
524 offset: 0.fct(),
525 n_of: (0, 0),
526 })
527 });
528 thumbs_typed.push(thumb_fn(ThumbArgs { thumb: thumb_var }))
529 }
530
531 thumbs.init();
532 }
533 UiNodeOp::Deinit => {
534 thumbs.deinit();
535 *thumbs.node_impl::<UiVec>() = ui_vec![];
536 }
537 UiNodeOp::Info { info } => {
538 info.flag_meta(*IS_SLIDER_ID);
539 thumbs.info(info);
540 }
541 UiNodeOp::Measure { desired_size, .. } => {
542 thumbs.delegated();
543 *desired_size = LAYOUT.constraints().fill_size();
544 }
545 UiNodeOp::Layout { final_size, wl } => {
546 *final_size = LAYOUT.constraints().fill_size();
547 layout_direction = LAYOUT.direction();
548 let _ = thumbs.layout_list(wl, |_, n, wl| n.layout(wl), |_, _| PxSize::zero());
549 }
550 UiNodeOp::Update { updates } => {
551 thumbs.update(updates);
552
553 if let Some(thumb_fn) = THUMB_FN_VAR.get_new() {
557 thumbs.deinit();
558
559 let thumbs_vec = thumbs.node_impl::<UiVec>();
560 thumbs_vec.clear();
561
562 let thumbs_var = SELECTOR.get().thumbs();
563 let thumbs_len = thumbs_var.with(|t| t.len());
564 thumbs_vec.reserve(thumbs_len);
565 for i in 0..thumbs_len {
566 let thumb_var = thumbs_var.map(move |t| {
567 t.get(i).copied().unwrap_or(ThumbValue {
568 offset: 0.fct(),
569 n_of: (0, 0),
570 })
571 });
572 thumbs_vec.push(thumb_fn(ThumbArgs { thumb: thumb_var }))
573 }
574
575 thumbs.init();
576
577 WIDGET.update_info().layout().render();
578 } else {
579 let thumbs_var = SELECTOR.get().thumbs();
582 let thumbs_len = thumbs_var.with(|t| t.len());
583
584 match thumbs_len.cmp(&thumbs.node().children_len()) {
585 std::cmp::Ordering::Less => {
586 for mut drop in thumbs.node_impl::<UiVec>().drain(thumbs_len..) {
588 drop.deinit();
589 }
590 }
591 std::cmp::Ordering::Greater => {
592 let thumbs_vec = thumbs.node_impl::<UiVec>();
594 let thumb_fn = THUMB_FN_VAR.get();
595 let from_len = thumbs_vec.len();
596 thumbs_vec.reserve(thumbs_len - from_len);
597 for i in from_len..thumbs_len {
598 let thumb_var = thumbs_var.map(move |t| {
599 t.get(i).copied().unwrap_or(ThumbValue {
600 offset: 0.fct(),
601 n_of: (0, 0),
602 })
603 });
604 thumbs_vec.push(thumb_fn(ThumbArgs { thumb: thumb_var }))
605 }
606 }
607 std::cmp::Ordering::Equal => {}
608 }
609 }
610
611 let mut pos = None;
615
616 MOUSE_MOVE_EVENT.each_update(false, |args| {
617 if let Some(cap) = &args.capture
618 && cap.target.contains(WIDGET.id())
619 {
620 pos = Some(args.position);
621 args.propagation.stop();
622 }
623 });
624 MOUSE_INPUT_EVENT.each_update(false, |args| {
625 if args.state == ButtonState::Pressed {
626 pos = Some(args.position);
627 args.propagation.stop();
628 }
629 });
630 TOUCH_INPUT_EVENT.each_update(false, |args| {
631 if args.phase == TouchPhase::Start {
632 pos = Some(args.position);
633 args.propagation.stop();
634 }
635 });
636
637 if let Some(pos) = pos {
638 let track_info = WIDGET.info();
639 let track_bounds = track_info.inner_bounds();
640 let track_orientation = SLIDER_DIRECTION_VAR.get();
641
642 let (track_min, track_max) = match track_orientation.layout(layout_direction) {
643 SliderDirection::LeftToRight => (track_bounds.min_x(), track_bounds.max_x()),
644 SliderDirection::RightToLeft => (track_bounds.max_x(), track_bounds.min_x()),
645 SliderDirection::BottomToTop => (track_bounds.max_y(), track_bounds.min_y()),
646 SliderDirection::TopToBottom => (track_bounds.min_y(), track_bounds.max_y()),
647 _ => unreachable!(),
648 };
649 let cursor = if track_orientation.is_horizontal() {
650 pos.x.to_px(track_info.tree().scale_factor())
651 } else {
652 pos.y.to_px(track_info.tree().scale_factor())
653 };
654 let new_offset = (cursor - track_min).0 as f32 / (track_max - track_min).abs().0 as f32;
655 let new_offset = new_offset.fct().clamp_range();
656
657 let selector = crate::SELECTOR.get();
658 selector.set(new_offset, new_offset);
659 }
660 }
661 _ => {}
662 })
663}
664
665macro_rules! impl_32 {
666 (($to_f32:expr, $from_f32:expr) => $($T:ident),+ $(,)?) => {
667 $(
668 impl SelectorValue for $T {
669 #[allow(clippy::unnecessary_cast)]
670 fn to_selector(value: Var<Self>, min: Self, max: Self) -> Selector {
671 let to_f32 = $to_f32;
672 let from_f32 = $from_f32;
673
674 let min = to_f32(min);
675 let max = to_f32(max);
676 if min >= max {
677 Selector::nil()
678 } else {
679 let d = max - min;
680 Selector::value_with(value, move |i| {
681 let i = to_f32(i.clone());
682 ((i - min) / d).fct()
683 }, move |f| {
684 from_f32((f.0 * d + min).round())
685 })
686 }
687 }
688 }
689 )+
690 };
691}
692impl_32!((|i| i as f32, |f| f as Self) => u32, i32, u16, i16, u8, i8, f32);
693impl_32!((|p: Self| p.0 as f32, |f| Self(f as _)) => Px, Factor, FactorPercent);
694impl_32!((|p: Dip| p.to_f32(), |f| Dip::new_f32(f)) => Dip);
695
696macro_rules! impl_64 {
697 ($($T:ident),+ $(,)?) => {
698 $(
699 impl SelectorValue for $T {
700 fn to_selector(value: Var<Self>, min: Self, max: Self) -> Selector {
701 let min = min as f64;
702 let max = max as f64;
703 if min >= max {
704 Selector::nil()
705 } else {
706 let d = max - min;
707 Selector::value_with(value, move |&i| {
708 let i = i as f64;
709 Factor(((i - min) / d) as f32)
710 }, move |f| {
711 ((f.0 as f64) * d + min).round() as Self
712 })
713 }
714 }
715 }
716 )+
717 };
718}
719impl_64!(u64, i64, u128, i128, f64);
720
721impl SelectorValue for Length {
722 fn to_selector(value: Var<Self>, min: Self, max: Self) -> Selector {
723 let (min_f32, max_f32) = LAYOUT.with_context(LayoutMetrics::new(1.fct(), PxSize::splat(Px(1000)), Px(16)), || {
724 (min.layout_f32(LayoutAxis::X), max.layout_f32(LayoutAxis::X))
725 });
726 if min_f32 >= max_f32 {
727 Selector::nil()
728 } else {
729 let d_f32 = max_f32 - min_f32;
730 let d = max - min.clone();
731 Selector::value_with(
732 value,
733 move |l| {
734 let l = LAYOUT.with_context(LayoutMetrics::new(1.fct(), PxSize::splat(Px(1000)), Px(16)), || {
735 l.layout_f32(LayoutAxis::X)
736 });
737 Factor((l - min_f32) / d_f32)
738 },
739 move |f| d.clone() * f + min.clone(),
740 )
741 }
742 }
743}
744
745#[cfg(test)]
746mod tests {
747 use super::*;
748
749 fn selector_value_t<T: SelectorValue>(min: T, max: T) {
750 let s = Selector::value(min.clone(), min.clone(), max.clone());
751 assert_eq!(s.to_offset(&min), Some(0.fct()));
752 assert_eq!(s.to_offset(&max), Some(1.fct()));
753 assert_eq!(s.from_offset(0.fct()), Some(min));
754 assert_eq!(s.from_offset(1.fct()), Some(max));
755 }
756
757 #[test]
758 fn selector_value_u8() {
759 selector_value_t(u8::MIN, u8::MAX);
760 selector_value_t(20u8, 120u8);
761 }
762
763 #[test]
764 fn selector_value_i32() {
765 selector_value_t(i32::MIN, i32::MAX);
766 selector_value_t(20i32, 120i32);
767 }
768
769 #[test]
770 fn selector_value_i64() {
771 selector_value_t(i64::MIN, i64::MAX);
772 }
773
774 #[test]
775 fn selector_value_f64() {
776 selector_value_t(-200f64, 200f64);
777 selector_value_t(20f64, 120f64);
778 }
779
780 #[test]
781 fn selector_pct() {
782 selector_value_t(0.pct(), 100.pct());
783 }
784
785 #[test]
786 fn selector_value_px() {
787 selector_value_t(Px(20), Px(200));
788 }
789
790 #[test]
791 fn selector_value_dip() {
792 selector_value_t(Dip::new(20), Dip::new(200));
793 }
794
795 #[test]
796 fn selector_value_length() {
797 selector_value_t(20.px(), 200.px());
798 }
799}