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}