Skip to main content

sequoia_view/
modifiers.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3use std::time::{Duration, Instant};
4
5use sequoia_animation::{Easing, animate};
6use sequoia_core::{Color, Edges, Length, Vec2};
7use sequoia_layout::{Indents, LayoutStyle, sanitize_indent};
8use sequoia_reactive::Signal;
9use sequoia_style::ShadowSpec;
10
11use crate::any::AnyView;
12use crate::filter_view::{EffectNode, FilterExt, Filtered};
13use crate::live::{AnimSpec, Live, Prop, SpringSpec};
14use crate::live_view::{LiveBackground, LiveOffset, LiveOpacity};
15use crate::presence::Presence;
16use crate::runtime::{ElementId, Runtime};
17use crate::view::{PaintCtx, View};
18use crate::views::Margined;
19
20#[derive(Clone, Debug)]
21pub struct Motion {
22    opacity: Option<(f32, f32)>,
23    offset: Option<(Vec2, Vec2)>,
24    spec: AnimSpec,
25}
26
27pub fn fade() -> Motion {
28    Motion {
29        opacity: Some((0.0, 1.0)),
30        offset: None,
31        spec: AnimSpec::SMOOTH,
32    }
33}
34
35pub fn slide_y(px: f32) -> Motion {
36    Motion {
37        opacity: None,
38        offset: Some((Vec2::new(0.0, px), Vec2::ZERO)),
39        spec: AnimSpec::SMOOTH,
40    }
41}
42
43impl Motion {
44    pub fn slide_y(mut self, px: f32) -> Self {
45        self.offset = Some((Vec2::new(0.0, px), Vec2::ZERO));
46        self
47    }
48
49    pub fn slide(mut self, dx: f32, dy: f32) -> Self {
50        self.offset = Some((Vec2::new(dx, dy), Vec2::ZERO));
51        self
52    }
53
54    pub fn fade_from(mut self, from: f32) -> Self {
55        self.opacity = Some((from.clamp(0.0, 1.0), 1.0));
56        self
57    }
58
59    pub fn duration(mut self, d: Duration) -> Self {
60        self.spec.duration_secs = d.as_secs_f32().max(0.0);
61        self
62    }
63
64    pub fn easing(mut self, easing: Easing) -> Self {
65        self.spec.easing = easing;
66        self
67    }
68
69    pub fn delay(mut self, d: Duration) -> Self {
70        self.spec.delay_secs = d.as_secs_f32().max(0.0);
71        self
72    }
73
74    pub fn preset(mut self, spec: impl Into<AnimSpec>) -> Self {
75        self.spec = spec.into();
76        self
77    }
78
79    pub fn sample_at(&self, elapsed_secs: f32) -> (Option<f32>, Option<Vec2>) {
80        let op = self
81            .opacity
82            .map(|(a, b)| self.spec.sample(a, b, elapsed_secs));
83        let off = self.offset.map(|(a, b)| {
84            Vec2::new(
85                self.spec.sample(a.x, b.x, elapsed_secs),
86                self.spec.sample(a.y, b.y, elapsed_secs),
87            )
88        });
89        (op, off)
90    }
91
92    pub fn is_running(&self, elapsed_secs: f32) -> bool {
93        (self.opacity.is_some() || self.offset.is_some()) && self.spec.is_running(elapsed_secs)
94    }
95
96    pub(crate) fn into_animate(self) -> sequoia_animation::Animate {
97        let mut b = animate()
98            .duration(Duration::from_secs_f32(self.spec.duration_secs))
99            .delay(Duration::from_secs_f32(self.spec.delay_secs))
100            .ease(self.spec.easing);
101        if let Some((a, c)) = self.opacity {
102            b = b.opacity(a..c);
103        }
104        if let Some((a, c)) = self.offset {
105            b = b.translate(a, c);
106        }
107        b.build()
108    }
109}
110
111impl From<AnimSpec> for Motion {
112    fn from(spec: AnimSpec) -> Self {
113        Motion {
114            opacity: Some((0.0, 1.0)),
115            offset: None,
116            spec,
117        }
118    }
119}
120
121impl From<Duration> for Motion {
122    fn from(d: Duration) -> Self {
123        Motion::from(AnimSpec::from(d))
124    }
125}
126
127#[derive(Clone)]
128pub struct When {
129    presence: Presence,
130}
131
132pub fn when<V: View + 'static>(present: Signal<bool>, child: V) -> When {
133    When {
134        presence: Presence::new(present).child(child),
135    }
136}
137
138impl When {
139    pub fn enter(mut self, motion: impl Into<Motion>) -> Self {
140        self.presence = self.presence.enter(motion.into().into_animate());
141        self
142    }
143
144    pub fn exit(mut self, motion: impl Into<Motion>) -> Self {
145        self.presence = self.presence.exit(motion.into().into_animate());
146        self
147    }
148
149    pub fn on_exit_complete(mut self, f: impl Fn() + 'static) -> Self {
150        self.presence = self.presence.on_exit_complete(f);
151        self
152    }
153
154    pub fn is_mounted(&self) -> bool {
155        self.presence.is_mounted()
156    }
157}
158
159impl View for When {
160    fn body(&self) -> impl View + 'static {
161        crate::views::Unit
162    }
163    fn reuse_state(&self, old: &Self) {
164        self.presence.reuse_state(&old.presence);
165    }
166    fn keyed_children(&self) -> Option<Vec<(u64, AnyView)>> {
167        self.presence.keyed_children()
168    }
169    fn layout_style(&self) -> LayoutStyle {
170        self.presence.layout_style()
171    }
172    fn paint_opacity(&self) -> Option<f32> {
173        self.presence.paint_opacity()
174    }
175    fn paint_offset(&self) -> Vec2 {
176        self.presence.paint_offset()
177    }
178    fn paint_self(&self, ctx: &mut PaintCtx) {
179        self.presence.paint_self(ctx);
180    }
181}
182
183#[derive(Clone)]
184pub struct Clip {
185    child: AnyView,
186    radius: f32,
187}
188
189impl Clip {
190    pub fn new<V: View + 'static>(child: V, radius: f32) -> Self {
191        Self {
192            child: AnyView::new(child),
193            radius: radius.max(0.0),
194        }
195    }
196}
197
198impl View for Clip {
199    fn body(&self) -> impl View + 'static {
200        crate::views::Unit
201    }
202    fn children_override(&self) -> Option<Vec<AnyView>> {
203        Some(vec![self.child.clone()])
204    }
205    fn layout_style(&self) -> LayoutStyle {
206        LayoutStyle {
207            width: Length::Fit,
208            height: Length::Fit,
209            ..LayoutStyle::default()
210        }
211    }
212    fn clip_self(&self) -> bool {
213        true
214    }
215    fn clip_radius(&self) -> f32 {
216        self.radius
217    }
218}
219
220#[derive(Clone)]
221pub struct Scaled {
222    child: AnyView,
223    live: Live<f32>,
224}
225
226impl Scaled {
227    pub fn new<V: View + 'static>(child: V, live: impl Into<Live<f32>>) -> Self {
228        Self {
229            child: AnyView::new(child),
230            live: live.into(),
231        }
232    }
233
234    pub fn animate(mut self, spec: impl Into<AnimSpec>) -> Self {
235        self.live.animate_mut(spec);
236        self
237    }
238
239    pub fn spring(mut self, spec: SpringSpec) -> Self {
240        self.live.spring_mut(spec);
241        self
242    }
243
244    pub fn target_untracked(&self) -> f32 {
245        self.live.target_untracked()
246    }
247}
248
249impl View for Scaled {
250    fn body(&self) -> impl View + 'static {
251        crate::views::Unit
252    }
253    fn children_override(&self) -> Option<Vec<AnyView>> {
254        let _ = self.live.target();
255        Some(vec![self.child.clone()])
256    }
257    fn layout_style(&self) -> LayoutStyle {
258        LayoutStyle {
259            width: Length::Fit,
260            height: Length::Fit,
261            ..LayoutStyle::default()
262        }
263    }
264    fn paint_scale_ctx(&self, rt: &Runtime, el: ElementId) -> f32 {
265        rt.resolve_live_f32(el, Prop::SCALE, &self.live)
266    }
267    fn paint_self(&self, ctx: &mut PaintCtx) {
268        let target = self.live.target_untracked();
269        let (_, running) = ctx.runtime.live_f32(
270            ctx.element,
271            Prop::SCALE,
272            target,
273            self.live.tween_spec().as_ref(),
274            self.live.spring_spec().as_ref(),
275        );
276        if running {
277            ctx.request_frame();
278        }
279    }
280}
281
282#[derive(Clone)]
283pub struct Rotated {
284    child: AnyView,
285    live: Live<f32>,
286}
287
288impl Rotated {
289    pub fn new<V: View + 'static>(child: V, live: impl Into<Live<f32>>) -> Self {
290        Self {
291            child: AnyView::new(child),
292            live: live.into(),
293        }
294    }
295
296    pub fn animate(mut self, spec: impl Into<AnimSpec>) -> Self {
297        self.live.animate_mut(spec);
298        self
299    }
300
301    pub fn spring(mut self, spec: SpringSpec) -> Self {
302        self.live.spring_mut(spec);
303        self
304    }
305
306    pub fn target_untracked(&self) -> f32 {
307        self.live.target_untracked()
308    }
309}
310
311impl View for Rotated {
312    fn body(&self) -> impl View + 'static {
313        crate::views::Unit
314    }
315    fn children_override(&self) -> Option<Vec<AnyView>> {
316        let _ = self.live.target();
317        Some(vec![self.child.clone()])
318    }
319    fn layout_style(&self) -> LayoutStyle {
320        LayoutStyle {
321            width: Length::Fit,
322            height: Length::Fit,
323            ..LayoutStyle::default()
324        }
325    }
326    fn paint_rotation_ctx(&self, rt: &Runtime, el: ElementId) -> f32 {
327        rt.resolve_live_f32(el, Prop::ROTATION, &self.live)
328    }
329    fn paint_self(&self, ctx: &mut PaintCtx) {
330        let target = self.live.target_untracked();
331        let (_, running) = ctx.runtime.live_f32(
332            ctx.element,
333            Prop::ROTATION,
334            target,
335            self.live.tween_spec().as_ref(),
336            self.live.spring_spec().as_ref(),
337        );
338        if running {
339            ctx.request_frame();
340        }
341    }
342}
343
344#[derive(Clone)]
345pub struct Enter {
346    child: AnyView,
347    motion: Motion,
348    start: Rc<RefCell<Option<Instant>>>,
349}
350
351impl Enter {
352    pub fn new<V: View + 'static>(child: V, motion: impl Into<Motion>) -> Self {
353        Self {
354            child: AnyView::new(child),
355            motion: motion.into(),
356            start: Rc::new(RefCell::new(None)),
357        }
358    }
359
360    fn elapsed(&self) -> f32 {
361        let now = Instant::now();
362        let mut slot = self.start.borrow_mut();
363        let t0 = *slot.get_or_insert(now);
364        now.duration_since(t0).as_secs_f32().min(600.0)
365    }
366}
367
368impl View for Enter {
369    fn body(&self) -> impl View + 'static {
370        crate::views::Unit
371    }
372    fn children_override(&self) -> Option<Vec<AnyView>> {
373        Some(vec![self.child.clone()])
374    }
375    fn layout_style(&self) -> LayoutStyle {
376        LayoutStyle {
377            width: Length::Fit,
378            height: Length::Fit,
379            ..LayoutStyle::default()
380        }
381    }
382    fn paint_opacity(&self) -> Option<f32> {
383        self.motion.sample_at(self.elapsed()).0
384    }
385    fn paint_offset(&self) -> Vec2 {
386        self.motion
387            .sample_at(self.elapsed())
388            .1
389            .unwrap_or(Vec2::ZERO)
390    }
391    fn paint_self(&self, ctx: &mut PaintCtx) {
392        if self.motion.is_running(self.elapsed()) {
393            ctx.request_frame();
394        }
395    }
396}
397
398#[derive(Clone, Default)]
399pub struct StatePatch {
400    background: Option<Live<Color>>,
401    opacity: Option<Live<f32>>,
402    scale: Option<Live<f32>>,
403    spec: Option<AnimSpec>,
404    spring: Option<SpringSpec>,
405}
406
407impl StatePatch {
408    pub fn new() -> Self {
409        Self::default()
410    }
411
412    pub fn background(mut self, color: impl Into<Live<Color>>) -> Self {
413        self.background = Some(color.into());
414        self
415    }
416
417    pub fn opacity(mut self, opacity: impl Into<Live<f32>>) -> Self {
418        self.opacity = Some(opacity.into());
419        self
420    }
421
422    pub fn scale(mut self, scale: impl Into<Live<f32>>) -> Self {
423        self.scale = Some(scale.into());
424        self
425    }
426
427    pub fn animate(mut self, spec: impl Into<AnimSpec>) -> Self {
428        self.spec = Some(spec.into());
429        self.spring = None;
430        self
431    }
432
433    pub fn spring(mut self, spec: SpringSpec) -> Self {
434        self.spring = Some(spec);
435        self.spec = None;
436        self
437    }
438}
439
440#[derive(Clone)]
441pub struct States {
442    child: AnyView,
443    hover: Option<StatePatch>,
444    pressed: Option<StatePatch>,
445    focused: Option<StatePatch>,
446    selected: Option<StatePatch>,
447    disabled: Option<StatePatch>,
448    disabled_when: Option<Live<bool>>,
449    selected_when: Option<Live<bool>>,
450    radius: f32,
451}
452
453impl States {
454    pub fn new<V: View + 'static>(child: V) -> Self {
455        Self {
456            child: AnyView::new(child),
457            hover: None,
458            pressed: None,
459            focused: None,
460            selected: None,
461            disabled: None,
462            disabled_when: None,
463            selected_when: None,
464            radius: 0.0,
465        }
466    }
467
468    pub fn hover(mut self, build: impl FnOnce(StatePatch) -> StatePatch) -> Self {
469        self.hover = Some(build(StatePatch::new()));
470        self
471    }
472
473    pub fn hover_patch(mut self, patch: StatePatch) -> Self {
474        self.hover = Some(patch);
475        self
476    }
477
478    pub fn pressed(mut self, build: impl FnOnce(StatePatch) -> StatePatch) -> Self {
479        self.pressed = Some(build(StatePatch::new()));
480        self
481    }
482
483    pub fn pressed_patch(mut self, patch: StatePatch) -> Self {
484        self.pressed = Some(patch);
485        self
486    }
487
488    pub fn focused(mut self, build: impl FnOnce(StatePatch) -> StatePatch) -> Self {
489        self.focused = Some(build(StatePatch::new()));
490        self
491    }
492
493    pub fn focused_patch(mut self, patch: StatePatch) -> Self {
494        self.focused = Some(patch);
495        self
496    }
497
498    pub fn selected(mut self, build: impl FnOnce(StatePatch) -> StatePatch) -> Self {
499        self.selected = Some(build(StatePatch::new()));
500        self
501    }
502
503    pub fn selected_patch(mut self, patch: StatePatch) -> Self {
504        self.selected = Some(patch);
505        self
506    }
507
508    pub fn disabled(mut self, build: impl FnOnce(StatePatch) -> StatePatch) -> Self {
509        self.disabled = Some(build(StatePatch::new()));
510        self
511    }
512
513    pub fn disabled_patch(mut self, patch: StatePatch) -> Self {
514        self.disabled = Some(patch);
515        self
516    }
517
518    pub fn disabled_when(mut self, cond: impl Into<Live<bool>>) -> Self {
519        self.disabled_when = Some(cond.into());
520        self
521    }
522
523    pub fn selected_when(mut self, cond: impl Into<Live<bool>>) -> Self {
524        self.selected_when = Some(cond.into());
525        self
526    }
527
528    pub fn radius(mut self, radius: f32) -> Self {
529        self.radius = radius.max(0.0);
530        self
531    }
532
533    fn touch(live: &Live<Color>) {
534        let _ = live.target();
535    }
536    fn touch_f32(live: &Live<f32>) {
537        let _ = live.target();
538    }
539    fn touch_bool(live: &Live<bool>) {
540        let _ = live.target();
541    }
542
543    fn touch_patch(p: &StatePatch) {
544        if let Some(v) = &p.background {
545            Self::touch(v);
546        }
547        if let Some(v) = &p.opacity {
548            Self::touch_f32(v);
549        }
550        if let Some(v) = &p.scale {
551            Self::touch_f32(v);
552        }
553    }
554
555    fn active_patch(&self, hovered: bool, pressed: bool, focused: bool) -> Option<&StatePatch> {
556        let disabled = self
557            .disabled_when
558            .as_ref()
559            .map(|c| c.target_untracked())
560            .unwrap_or(false);
561        if disabled && self.disabled.is_some() {
562            return self.disabled.as_ref();
563        }
564        if pressed && self.pressed.is_some() {
565            return self.pressed.as_ref();
566        }
567        if hovered && self.hover.is_some() {
568            return self.hover.as_ref();
569        }
570        if focused && self.focused.is_some() {
571            return self.focused.as_ref();
572        }
573        let selected = self
574            .selected_when
575            .as_ref()
576            .map(|c| c.target_untracked())
577            .unwrap_or(false);
578        if selected && self.selected.is_some() {
579            return self.selected.as_ref();
580        }
581        None
582    }
583
584    fn eff_f32(patch: &StatePatch, live: &Live<f32>) -> Live<f32> {
585        if live.tween_spec().is_some() || live.spring_spec().is_some() {
586            return live.clone();
587        }
588        let mut out = live.clone();
589        if let Some(s) = patch.spec {
590            out.animate_mut(s);
591        } else if let Some(s) = patch.spring {
592            out.spring_mut(s);
593        }
594        out
595    }
596
597    fn eff_color(patch: &StatePatch, live: &Live<Color>) -> Live<Color> {
598        if live.tween_spec().is_some() {
599            return live.clone();
600        }
601        let mut out = live.clone();
602        if let Some(s) = patch.spec {
603            out.animate_mut(s);
604        }
605        out
606    }
607}
608
609impl View for States {
610    fn body(&self) -> impl View + 'static {
611        crate::views::Unit
612    }
613    fn children_override(&self) -> Option<Vec<AnyView>> {
614        for p in [
615            &self.hover,
616            &self.pressed,
617            &self.focused,
618            &self.selected,
619            &self.disabled,
620        ]
621        .into_iter()
622        .flatten()
623        {
624            Self::touch_patch(p);
625        }
626        if let Some(c) = &self.disabled_when {
627            Self::touch_bool(c);
628        }
629        if let Some(c) = &self.selected_when {
630            Self::touch_bool(c);
631        }
632        Some(vec![self.child.clone()])
633    }
634    fn layout_style(&self) -> LayoutStyle {
635        LayoutStyle {
636            width: Length::Fit,
637            height: Length::Fit,
638            ..LayoutStyle::default()
639        }
640    }
641    fn has_visual_state(&self) -> bool {
642        true
643    }
644    fn paint_opacity_ctx(&self, rt: &Runtime, el: ElementId) -> Option<f32> {
645        let st = rt.visual_state(el);
646        let patch = self.active_patch(st.hovered, st.pressed, st.focused)?;
647        let live = patch.opacity.as_ref()?;
648        let live = Self::eff_f32(patch, live);
649        Some(
650            rt.resolve_live_f32(el, Prop::OPACITY, &live)
651                .clamp(0.0, 1.0),
652        )
653    }
654    fn paint_scale_ctx(&self, rt: &Runtime, el: ElementId) -> f32 {
655        let st = rt.visual_state(el);
656        let patch = match self.active_patch(st.hovered, st.pressed, st.focused) {
657            Some(p) => p,
658            None => return 1.0,
659        };
660        let live = match patch.scale.as_ref() {
661            Some(v) => v,
662            None => return 1.0,
663        };
664        let live = Self::eff_f32(patch, live);
665        rt.resolve_live_f32(el, Prop::SCALE, &live)
666    }
667    fn paint_self(&self, ctx: &mut PaintCtx) {
668        let st = ctx.state;
669
670        let patch = match self.active_patch(st.hovered, st.pressed, st.focused) {
671            Some(p) => p,
672            None => return,
673        };
674        let mut running = false;
675        if let Some(live) = &patch.background {
676            let live = Self::eff_color(patch, live);
677            let target = live.target_untracked();
678            let (color, r) = ctx.runtime.live_color(
679                ctx.element,
680                Prop::BACKGROUND,
681                target,
682                live.tween_spec().as_ref(),
683            );
684            running |= r;
685            if color.a > 0.0 {
686                ctx.quad(ctx.rect, color, self.radius);
687            }
688        }
689        if let Some(live) = &patch.opacity {
690            let live = Self::eff_f32(patch, live);
691            let target = live.target_untracked();
692            let (_, r) = ctx.runtime.live_f32(
693                ctx.element,
694                Prop::OPACITY,
695                target,
696                live.tween_spec().as_ref(),
697                live.spring_spec().as_ref(),
698            );
699            running |= r;
700        }
701        if let Some(live) = &patch.scale {
702            let live = Self::eff_f32(patch, live);
703            let target = live.target_untracked();
704            let (_, r) = ctx.runtime.live_f32(
705                ctx.element,
706                Prop::SCALE,
707                target,
708                live.tween_spec().as_ref(),
709                live.spring_spec().as_ref(),
710            );
711            running |= r;
712        }
713        if running {
714            ctx.request_frame();
715        }
716    }
717}
718
719pub trait ViewModifiers: View + Sized + 'static {
720    fn opacity(self, opacity: impl Into<Live<f32>>) -> LiveOpacity {
721        LiveOpacity::new(self, opacity)
722    }
723
724    fn offset(self, dx: f32, dy: f32) -> LiveOffset {
725        LiveOffset::new(self, Live::fixed(Vec2::new(dx, dy)))
726    }
727
728    fn offset_by(self, offset: impl Into<Live<Vec2>>) -> LiveOffset {
729        LiveOffset::new(self, offset)
730    }
731
732    fn background(self, color: impl Into<Live<Color>>) -> LiveBackground {
733        LiveBackground::new(self, color)
734    }
735
736    fn bg(self, color: impl Into<Live<Color>>) -> LiveBackground {
737        LiveBackground::new(self, color)
738    }
739
740    fn blur(self, px: f32) -> Filtered {
741        self.blur_own(px)
742    }
743
744    fn shadow(self, spec: ShadowSpec) -> EffectNode {
745        self.effect(sequoia_style::Effect::Shadow(spec))
746    }
747
748    fn clip(self) -> Clip {
749        Clip::new(self, 0.0)
750    }
751
752    fn clip_rounded(self, radius: f32) -> Clip {
753        Clip::new(self, radius)
754    }
755
756    fn scale(self, scale: impl Into<Live<f32>>) -> Scaled {
757        Scaled::new(self, scale)
758    }
759
760    fn rotate(self, degrees: impl Into<Live<f32>>) -> Rotated {
761        Rotated::new(self, degrees)
762    }
763
764    fn width_live(self, width: impl Into<Live<f32>>) -> crate::layout_live::AnimatedSize {
765        crate::layout_live::AnimatedSize::width(self, width)
766    }
767
768    fn height_live(self, height: impl Into<Live<f32>>) -> crate::layout_live::AnimatedSize {
769        crate::layout_live::AnimatedSize::height(self, height)
770    }
771
772    fn size_live(
773        self,
774        width: impl Into<Live<f32>>,
775        height: impl Into<Live<f32>>,
776    ) -> crate::layout_live::AnimatedSize {
777        crate::layout_live::AnimatedSize::size(self, width, height)
778    }
779
780    fn animate(self, spec: impl Into<AnimSpec>) -> Enter {
781        Enter::new(self, Motion::from(spec.into()))
782    }
783
784    fn enter(self, motion: impl Into<Motion>) -> Enter {
785        Enter::new(self, motion)
786    }
787
788    fn hover(self, build: impl FnOnce(StatePatch) -> StatePatch) -> States {
789        States::new(self).hover_patch(build(StatePatch::new()))
790    }
791
792    fn pressed(self, build: impl FnOnce(StatePatch) -> StatePatch) -> States {
793        States::new(self).pressed_patch(build(StatePatch::new()))
794    }
795
796    fn focused(self, build: impl FnOnce(StatePatch) -> StatePatch) -> States {
797        States::new(self).focused_patch(build(StatePatch::new()))
798    }
799
800    fn selected(self, build: impl FnOnce(StatePatch) -> StatePatch) -> States {
801        States::new(self).selected_patch(build(StatePatch::new()))
802    }
803
804    fn disabled(self, build: impl FnOnce(StatePatch) -> StatePatch) -> States {
805        States::new(self).disabled_patch(build(StatePatch::new()))
806    }
807
808    fn state(self, build: impl FnOnce(States) -> States) -> States {
809        build(States::new(self))
810    }
811
812    fn margin(self, margin: impl Into<Edges>) -> Margined {
813        Margined::new(self, margin)
814    }
815
816    fn margin_xy(self, x: f32, y: f32) -> Margined {
817        Margined::new(self, Edges::xy(x, y))
818    }
819
820    fn margin_x(self, x: f32) -> Margined {
821        Margined::new(self, Edges::horizontal(x))
822    }
823
824    fn margin_y(self, y: f32) -> Margined {
825        Margined::new(self, Edges::vertical(y))
826    }
827
828    fn margin_left(self, v: f32) -> Margined {
829        Margined::new(self, Edges::left(v))
830    }
831
832    fn margin_right(self, v: f32) -> Margined {
833        Margined::new(self, Edges::right(v))
834    }
835
836    fn margin_top(self, v: f32) -> Margined {
837        Margined::new(self, Edges::top(v))
838    }
839
840    fn margin_bottom(self, v: f32) -> Margined {
841        Margined::new(self, Edges::bottom(v))
842    }
843
844    fn indent_top(self, v: f32) -> Margined {
845        Margined::with_indents(
846            self,
847            Indents::only(None, Some(sanitize_indent(v)), None, None),
848        )
849    }
850
851    fn indent_bottom(self, v: f32) -> Margined {
852        Margined::with_indents(
853            self,
854            Indents::only(None, None, None, Some(sanitize_indent(v))),
855        )
856    }
857
858    fn indent_left(self, v: f32) -> Margined {
859        Margined::with_indents(
860            self,
861            Indents::only(Some(sanitize_indent(v)), None, None, None),
862        )
863    }
864
865    fn indent_right(self, v: f32) -> Margined {
866        Margined::with_indents(
867            self,
868            Indents::only(None, None, Some(sanitize_indent(v)), None),
869        )
870    }
871
872    fn indent(self, indents: Indents) -> Margined {
873        Margined::with_indents(self, indents)
874    }
875
876    fn indent_all(self, v: f32) -> Margined {
877        Margined::with_indents(self, Indents::all(v))
878    }
879
880    fn indent_xy(self, x: f32, y: f32) -> Margined {
881        Margined::with_indents(self, Indents::xy(x, y))
882    }
883
884    fn indent_axes(self, horizontal: f32, vertical: f32) -> Margined {
885        Margined::with_indents(self, Indents::axes(horizontal, vertical))
886    }
887}
888
889impl<T: View + 'static> ViewModifiers for T {}
890
891#[cfg(test)]
892mod tests {
893    use super::*;
894    use crate::value::ValueExt;
895
896    #[test]
897    fn motion_fade_and_slide_sample() {
898        let m = fade().slide_y(12.0);
899        let (op0, off0) = m.sample_at(0.0);
900        assert_eq!(op0, Some(0.0));
901        assert_eq!(off0, Some(Vec2::new(0.0, 12.0)));
902        let (op1, off1) = m.sample_at(10.0);
903        assert_eq!(op1, Some(1.0));
904        assert_eq!(off1, Some(Vec2::ZERO));
905        assert!(m.is_running(0.05));
906        assert!(!m.is_running(10.0));
907    }
908
909    #[test]
910    fn motion_from_spec_and_duration() {
911        let a = Motion::from(AnimSpec::SNAPPY);
912        assert_eq!(a.sample_at(0.0).0, Some(0.0));
913        let b = Motion::from(Duration::from_millis(200));
914        assert_eq!(b.sample_at(10.0).0, Some(1.0));
915    }
916
917    #[test]
918    fn modifiers_chain_static_values() {
919        let v = crate::views::Unit
920            .opacity(0.5)
921            .offset(3.0, -4.0)
922            .background(Color::WHITE)
923            .blur(2.0)
924            .clip_rounded(6.0)
925            .scale(1.03);
926        assert_eq!(v.paint_opacity(), None);
927        assert_eq!(v.target_untracked(), 1.03);
928        let r = crate::views::Unit.rotate(15.0);
929
930        assert_eq!(r.paint_rotation(), 0.0);
931        assert_eq!(r.target_untracked(), 15.0);
932    }
933
934    #[test]
935    fn post_hoc_animate_attaches_spec() {
936        let v = crate::views::Unit.opacity(0.0).animate(AnimSpec::SNAPPY);
937        let _ = v.children_override();
938        let v = crate::views::Unit
939            .scale(1.0)
940            .spring(SpringSpec::BOUNCY)
941            .offset(0.0, 8.0)
942            .animate(AnimSpec::SMOOTH);
943        let _ = v.children_override();
944    }
945
946    #[test]
947    fn reactive_value_flows_into_modifier() {
948        let s = Signal::new(0.8f32);
949        let v = crate::views::Unit.opacity(s.clone().animated(AnimSpec::FAST));
950        let kids = v.children_override().expect("dziecko");
951        assert_eq!(kids.len(), 1);
952        s.set(0.2);
953        let _ = v.children_override().expect("dziecko");
954    }
955
956    #[test]
957    fn states_accept_patch_and_builder() {
958        let a = crate::views::Unit.state(|s| s.hover_patch(StatePatch::new().opacity(0.5)));
959        let b = crate::views::Unit.hover(|h| {
960            h.background(Color::WHITE)
961                .opacity(0.9)
962                .animate(AnimSpec::FAST)
963        });
964        let c = crate::views::Unit.state(|s| {
965            s.hover(|h| h.opacity(1.0))
966                .pressed(|p| p.scale(0.97).spring(SpringSpec::SNAPPY))
967                .focused(|f| f.opacity(1.0))
968                .disabled(|d| d.opacity(0.5))
969        });
970        assert!(a.hover.is_some());
971        assert!(b.hover.as_ref().unwrap().background.is_some());
972        assert!(c.pressed.as_ref().unwrap().scale.is_some());
973
974        assert!(c.active_patch(true, true, false).unwrap().scale.is_some());
975        assert!(
976            c.active_patch(true, false, false)
977                .unwrap()
978                .opacity
979                .is_some()
980        );
981        assert!(c.active_patch(false, false, false).is_none());
982    }
983
984    #[test]
985    fn when_builds_with_enter_exit() {
986        let open = Signal::new(true);
987        let w = when(open, crate::views::Unit)
988            .enter(fade().slide_y(-8.0))
989            .exit(fade().slide_y(-8.0));
990        let _ = w.keyed_children();
991    }
992}