1use kurbo::{BezPath, Vec2};
2use oxframe_core::{BlendMode, Color, Gradient, Signal};
3use oxframe_text::TextStyle;
4
5use crate::draw::{LineCap, LineJoin};
6use crate::graph::{Direction, Justify, LayoutAlign, NodeId, NodeKind, SceneGraph, with_graph};
7use crate::handles::{
8 Camera, Circle, Code, Custom, Grid, Group, Img, Layout, Line, Particles, Path, Polygon, Rect,
9 Shader, Star, Txt,
10};
11use crate::image::ImageSource;
12use crate::place::{self, Axis, Edge, Place, Side, Span};
13use crate::text::{CodeTheme, RichSpan};
14use crate::theme::{self, Corner, IntoPaint, Len, Step, TypeRole};
15
16#[doc(hidden)]
18#[derive(Debug)]
19pub struct BaseProps {
20 pub name: Option<String>,
21 pub x: f64,
22 pub y: f64,
23 pub rotation: f64,
24 pub scale: Vec2,
25 pub opacity: f64,
26 pub fill: Color,
27 pub fill_explicit: bool,
30 pub stroke: Color,
31 pub line_width: f64,
32 pub start: f64,
33 pub end: f64,
34 pub fill_gradient: Option<Gradient>,
35 pub stroke_gradient: Option<Gradient>,
36 pub dash: Vec<f64>,
37 pub dash_offset: f64,
38 pub line_cap: LineCap,
39 pub line_join: LineJoin,
40 pub blur: f64,
41 pub shadow_color: Color,
42 pub shadow_blur: f64,
43 pub shadow_offset: Vec2,
44 pub clip: bool,
45 pub blend: BlendMode,
46 pub z_index: f64,
47 pub anchor: Vec2,
48 pub grow: f64,
49 pub shrink: f64,
50 pub basis: Option<f64>,
51 pub margin: f64,
52 pub align_self: Option<LayoutAlign>,
53 pub children: Vec<ChildBuilder>,
54 pub place: Option<Place>,
56}
57
58impl Default for BaseProps {
59 fn default() -> Self {
60 Self {
61 name: None,
62 x: 0.0,
63 y: 0.0,
64 rotation: 0.0,
65 scale: Vec2::new(1.0, 1.0),
66 opacity: 1.0,
67 fill: Color::TRANSPARENT,
68 fill_explicit: false,
69 stroke: Color::TRANSPARENT,
70 line_width: 0.0,
71 start: 0.0,
72 end: 1.0,
73 fill_gradient: None,
74 stroke_gradient: None,
75 dash: Vec::new(),
76 dash_offset: 0.0,
77 line_cap: LineCap::Round,
78 line_join: LineJoin::Round,
79 blur: 0.0,
80 shadow_color: Color::TRANSPARENT,
81 shadow_blur: 0.0,
82 shadow_offset: Vec2::ZERO,
83 clip: false,
84 blend: BlendMode::Normal,
85 z_index: 0.0,
86 anchor: Vec2::ZERO,
87 grow: 0.0,
88 shrink: 0.0,
89 basis: None,
90 margin: 0.0,
91 align_self: None,
92 children: Vec::new(),
93 place: None,
94 }
95 }
96}
97
98type BuildFn = Box<dyn FnOnce(&SceneGraph, NodeId)>;
100
101pub struct ChildBuilder(BuildFn);
103
104impl std::fmt::Debug for ChildBuilder {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 f.write_str("ChildBuilder")
107 }
108}
109
110pub trait NodeBuilder: Sized + 'static {
112 type Handle;
114
115 #[doc(hidden)]
116 fn base(&mut self) -> &mut BaseProps;
117
118 #[doc(hidden)]
119 fn build(self, graph: &SceneGraph, parent: NodeId) -> Self::Handle;
120
121 fn name(mut self, v: impl Into<String>) -> Self {
124 self.base().name = Some(v.into());
125 self
126 }
127 fn x(mut self, v: f64) -> Self {
129 self.base().x = v;
130 self
131 }
132 fn y(mut self, v: f64) -> Self {
134 self.base().y = v;
135 self
136 }
137 fn position(mut self, v: impl Into<Vec2>) -> Self {
139 let v = v.into();
140 let b = self.base();
141 (b.x, b.y) = (v.x, v.y);
142 self
143 }
144 fn rotation(mut self, v: f64) -> Self {
146 self.base().rotation = v;
147 self
148 }
149 fn scale(mut self, v: f64) -> Self {
151 self.base().scale = Vec2::new(v, v);
152 self
153 }
154 fn scale_xy(mut self, v: impl Into<Vec2>) -> Self {
156 self.base().scale = v.into();
157 self
158 }
159 fn opacity(mut self, v: f64) -> Self {
161 self.base().opacity = v;
162 self
163 }
164 fn fill(mut self, v: impl IntoPaint) -> Self {
167 let b = self.base();
168 b.fill = v.into_paint();
169 b.fill_explicit = true;
170 self
171 }
172 fn stroke(mut self, v: impl IntoPaint) -> Self {
175 self.base().stroke = v.into_paint();
176 self
177 }
178 fn line_width(mut self, v: f64) -> Self {
180 self.base().line_width = v;
181 self
182 }
183 fn start(mut self, v: f64) -> Self {
185 self.base().start = v;
186 self
187 }
188 fn end(mut self, v: f64) -> Self {
190 self.base().end = v;
191 self
192 }
193 fn fill_gradient(mut self, g: Gradient) -> Self {
196 self.base().fill_gradient = Some(g);
197 self
198 }
199 fn stroke_gradient(mut self, g: Gradient) -> Self {
201 self.base().stroke_gradient = Some(g);
202 self
203 }
204 fn dash(mut self, pattern: impl Into<Vec<f64>>) -> Self {
207 self.base().dash = pattern.into();
208 self
209 }
210 fn dash_offset(mut self, v: f64) -> Self {
212 self.base().dash_offset = v;
213 self
214 }
215 fn line_cap(mut self, v: LineCap) -> Self {
217 self.base().line_cap = v;
218 self
219 }
220 fn line_join(mut self, v: LineJoin) -> Self {
222 self.base().line_join = v;
223 self
224 }
225 fn blur(mut self, v: f64) -> Self {
228 self.base().blur = v;
229 self
230 }
231 fn shadow(mut self, color: impl IntoPaint, blur: f64, offset: impl Into<Vec2>) -> Self {
233 let b = self.base();
234 (b.shadow_color, b.shadow_blur, b.shadow_offset) =
235 (color.into_paint(), blur, offset.into());
236 self
237 }
238 fn elevation(self, step: Step) -> Self {
240 let s = theme::current().shadow(step);
241 self.shadow(s.color, s.blur, s.offset)
242 }
243 fn glow(self, color: impl IntoPaint, blur: f64) -> Self {
245 self.shadow(color, blur, Vec2::ZERO)
246 }
247 fn clip(mut self, v: bool) -> Self {
249 self.base().clip = v;
250 self
251 }
252 fn blend(mut self, v: BlendMode) -> Self {
254 self.base().blend = v;
255 self
256 }
257 fn z_index(mut self, v: f64) -> Self {
259 self.base().z_index = v;
260 self
261 }
262 fn anchor(mut self, v: impl Into<Vec2>) -> Self {
264 self.base().anchor = v.into();
265 self
266 }
267 fn grow(mut self, v: f64) -> Self {
269 self.base().grow = v;
270 self
271 }
272 fn shrink(mut self, v: f64) -> Self {
274 self.base().shrink = v;
275 self
276 }
277 fn basis(mut self, v: f64) -> Self {
279 self.base().basis = Some(v);
280 self
281 }
282 fn margin(mut self, v: impl Len) -> Self {
284 self.base().margin = v.px();
285 self
286 }
287 fn align_self(mut self, v: LayoutAlign) -> Self {
289 self.base().align_self = Some(v);
290 self
291 }
292 fn next_to(mut self, other: impl Into<crate::Node>, side: Side) -> Self {
295 self.base().place = Some(Place::next_to(other.into().id(), side));
296 self
297 }
298 fn align_to(mut self, other: impl Into<crate::Node>, edge: Edge) -> Self {
300 self.base().place = Some(Place::align_to(other.into().id(), edge));
301 self
302 }
303 fn to_edge(mut self, edge: Edge) -> Self {
306 self.base().place = Some(Place::to_edge(edge));
307 self
308 }
309 fn center_in(mut self, other: impl Into<crate::Node>) -> Self {
311 self.base().place = Some(Place::center_in(other.into().id()));
312 self
313 }
314 fn child<C: NodeBuilder>(mut self, child: C) -> Self {
317 self.base()
318 .children
319 .push(ChildBuilder(Box::new(move |g, parent| {
320 child.build(g, parent);
321 })));
322 self
323 }
324}
325
326pub(crate) fn add<B: NodeBuilder>(parent: NodeId, builder: B) -> B::Handle {
327 with_graph(|g| builder.build(g, parent))
328}
329
330pub(crate) fn insert(
332 graph: &SceneGraph,
333 parent: NodeId,
334 kind: NodeKind,
335 mut base: BaseProps,
336) -> NodeId {
337 let children = std::mem::take(&mut base.children);
338 let id = graph.insert(Some(parent), kind, base);
339 for c in children {
340 (c.0)(graph, id);
341 }
342 id
343}
344
345fn sig<T: Clone + 'static>(graph: &SceneGraph, kind: &str, field: &str, v: T) -> Signal<T> {
346 Signal::new(v).with_name(format!("{kind}#{}.{field}", graph.next_id()))
347}
348
349macro_rules! builder {
350 (
351 $(#[$doc:meta])*
352 $builder:ident => $handle:ident, kind $kind:literal, defaults { $($bf:ident: $bv:expr),* $(,)? },
353 fields { $($field:ident: $ty:ty = $default:expr),* $(,)? }
354 ) => {
355 $(#[$doc])*
356 #[doc = ""]
357 #[doc = concat!("Builder for a [`", stringify!($handle), "`] node: set the initial values, then")]
358 #[doc = "pass it to `add`."]
359 #[derive(Debug)]
360 #[must_use = "a builder does nothing until it is passed to `add`"]
361 pub struct $builder {
362 base: BaseProps,
363 $(pub(crate) $field: $ty,)*
364 }
365
366 impl Default for $builder {
367 fn default() -> Self {
368 Self {
369 base: BaseProps { $($bf: $bv,)* ..Default::default() },
370 $($field: $default,)*
371 }
372 }
373 }
374
375 impl $builder {
376 #[allow(dead_code)]
377 const KIND: &'static str = $kind;
378 }
379 };
380}
381
382macro_rules! setters {
383 ($builder:ident { $($(#[$doc:meta])* $field:ident: $ty:ty),* $(,)? }) => {
384 impl $builder {
385 $(
386 $(#[$doc])*
387 #[doc = ""]
388 #[doc = concat!("Sets the initial `", stringify!($field), "`.")]
389 pub fn $field(mut self, v: impl Into<$ty>) -> Self {
390 self.$field = v.into();
391 self
392 }
393 )*
394 }
395 };
396}
397
398macro_rules! base_accessor {
399 () => {
400 fn base(&mut self) -> &mut BaseProps {
401 &mut self.base
402 }
403 };
404}
405
406builder!(GroupBuilder => Group, kind "Group", defaults {}, fields {});
409
410impl NodeBuilder for GroupBuilder {
411 type Handle = Group;
412 base_accessor!();
413 fn build(self, graph: &SceneGraph, parent: NodeId) -> Group {
414 Group::from_id(insert(graph, parent, NodeKind::Group, self.base))
415 }
416}
417
418builder!(
421 CameraBuilder => Camera, kind "Camera", defaults {}, fields { zoom: f64 = 1.0 });
426setters!(CameraBuilder {
427 zoom: f64
429});
430
431impl NodeBuilder for CameraBuilder {
432 type Handle = Camera;
433 base_accessor!();
434 fn build(self, g: &SceneGraph, parent: NodeId) -> Camera {
435 let kind = NodeKind::Camera {
436 zoom: sig(g, Self::KIND, "zoom", self.zoom),
437 };
438 Camera::from_id(insert(g, parent, kind, self.base))
439 }
440}
441
442builder!(RectBuilder => Rect, kind "Rect", defaults { fill: Color::WHITE, fill_explicit: true },
445 fields { width: Span = Span::Px(100.0), height: Span = Span::Px(100.0), radius: f64 = 0.0 });
446
447impl RectBuilder {
448 pub fn width(mut self, v: impl Into<Span>) -> Self {
450 self.width = v.into();
451 self
452 }
453 pub fn height(mut self, v: impl Into<Span>) -> Self {
455 self.height = v.into();
456 self
457 }
458 pub fn radius(mut self, v: impl Corner) -> Self {
461 self.radius = v.radius();
462 self
463 }
464 pub fn size(mut self, v: f64) -> Self {
466 (self.width, self.height) = (Span::Px(v), Span::Px(v));
467 self
468 }
469}
470
471impl NodeBuilder for RectBuilder {
472 type Handle = Rect;
473 base_accessor!();
474 fn build(self, g: &SceneGraph, parent: NodeId) -> Rect {
475 let k = Self::KIND;
476 let n = g.next_id();
477 let kind = NodeKind::Rect {
478 width: place::span_signal(g, parent, Axis::X, self.width, format!("{k}#{n}.width")),
479 height: place::span_signal(g, parent, Axis::Y, self.height, format!("{k}#{n}.height")),
480 radius: sig(g, k, "radius", self.radius),
481 };
482 Rect::from_id(insert(g, parent, kind, self.base))
483 }
484}
485
486builder!(CircleBuilder => Circle, kind "Circle", defaults { fill: Color::WHITE, fill_explicit: true },
489 fields { size: f64 = 100.0, start_angle: f64 = 0.0, end_angle: f64 = 360.0, pie: bool = false });
490setters!(CircleBuilder {
491 size: f64,
493 start_angle: f64,
495 end_angle: f64,
496 pie: bool
498});
499
500impl NodeBuilder for CircleBuilder {
501 type Handle = Circle;
502 base_accessor!();
503 fn build(self, g: &SceneGraph, parent: NodeId) -> Circle {
504 let k = Self::KIND;
505 let kind = NodeKind::Circle {
506 size: sig(g, k, "size", self.size),
507 start_angle: sig(g, k, "start_angle", self.start_angle),
508 end_angle: sig(g, k, "end_angle", self.end_angle),
509 pie: self.pie,
510 };
511 Circle::from_id(insert(g, parent, kind, self.base))
512 }
513}
514
515builder!(PolygonBuilder => Polygon, kind "Polygon", defaults { fill: Color::WHITE, fill_explicit: true },
518 fields { sides: f64 = 6.0, size: f64 = 100.0 });
519setters!(PolygonBuilder {
520 sides: f64,
522 size: f64
524});
525
526impl NodeBuilder for PolygonBuilder {
527 type Handle = Polygon;
528 base_accessor!();
529 fn build(self, g: &SceneGraph, parent: NodeId) -> Polygon {
530 let k = Self::KIND;
531 let kind = NodeKind::Polygon {
532 sides: sig(g, k, "sides", self.sides),
533 size: sig(g, k, "size", self.size),
534 };
535 Polygon::from_id(insert(g, parent, kind, self.base))
536 }
537}
538
539builder!(StarBuilder => Star, kind "Star", defaults { fill: Color::WHITE, fill_explicit: true },
542 fields { points: f64 = 5.0, size: f64 = 100.0, inner: f64 = 0.45 });
543setters!(StarBuilder {
544 points: f64,
545 size: f64,
547 inner: f64
549});
550
551impl NodeBuilder for StarBuilder {
552 type Handle = Star;
553 base_accessor!();
554 fn build(self, g: &SceneGraph, parent: NodeId) -> Star {
555 let k = Self::KIND;
556 let kind = NodeKind::Star {
557 points: sig(g, k, "points", self.points),
558 size: sig(g, k, "size", self.size),
559 inner: sig(g, k, "inner", self.inner),
560 };
561 Star::from_id(insert(g, parent, kind, self.base))
562 }
563}
564
565builder!(PathBuilder => Path, kind "Path", defaults { fill: Color::WHITE, fill_explicit: true },
568 fields { data: BezPath = BezPath::new() });
569setters!(PathBuilder { data: BezPath });
570
571impl PathBuilder {
572 pub fn svg(mut self, d: &str) -> Self {
578 self.data = BezPath::from_svg(d).unwrap_or_else(|e| panic!("invalid SVG path {d:?}: {e}"));
579 self
580 }
581}
582
583impl NodeBuilder for PathBuilder {
584 type Handle = Path;
585 base_accessor!();
586 fn build(self, g: &SceneGraph, parent: NodeId) -> Path {
587 let kind = NodeKind::Path {
588 data: sig(g, Self::KIND, "data", self.data),
589 };
590 Path::from_id(insert(g, parent, kind, self.base))
591 }
592}
593
594builder!(LineBuilder => Line, kind "Line",
597 defaults { stroke: Color::WHITE, line_width: 4.0 },
598 fields { points: Vec<Vec2> = Vec::new(), closed: bool = false, smooth: bool = false,
599 start_arrow: bool = false, end_arrow: bool = false, arrow_size: f64 = 24.0 });
600setters!(LineBuilder {
601 closed: bool,
602 smooth: bool,
604 start_arrow: bool,
605 end_arrow: bool,
606 arrow_size: f64
607});
608
609impl LineBuilder {
610 pub fn points<P: Into<Vec2>>(mut self, pts: impl IntoIterator<Item = P>) -> Self {
612 self.points = pts.into_iter().map(Into::into).collect();
613 self
614 }
615}
616
617impl NodeBuilder for LineBuilder {
618 type Handle = Line;
619 base_accessor!();
620 fn build(self, g: &SceneGraph, parent: NodeId) -> Line {
621 let kind = NodeKind::Line {
622 points: sig(g, Self::KIND, "points", self.points),
623 closed: self.closed,
624 smooth: self.smooth,
625 start_arrow: self.start_arrow,
626 end_arrow: self.end_arrow,
627 arrow_size: sig(g, Self::KIND, "arrow_size", self.arrow_size),
628 };
629 Line::from_id(insert(g, parent, kind, self.base))
630 }
631}
632
633builder!(TxtBuilder => Txt, kind "Txt", defaults { fill: Color::WHITE },
636 fields { text: String = String::new(), spans: Vec<RichSpan> = Vec::new(),
637 style: TextStyle = TextStyle::default() });
638
639impl TxtBuilder {
640 pub fn text(mut self, v: impl Into<String>) -> Self {
642 self.text = v.into();
643 self
644 }
645 pub fn rich(mut self, spans: impl IntoIterator<Item = RichSpan>) -> Self {
647 self.spans = spans.into_iter().collect();
648 self
649 }
650 pub fn font_size(mut self, v: f64) -> Self {
652 self.style.size = v;
653 self
654 }
655 pub fn weight(mut self, v: f64) -> Self {
657 self.style.weight = v;
658 self
659 }
660 pub fn family(mut self, v: impl Into<String>) -> Self {
663 self.style.family = v.into();
664 self
665 }
666 pub fn italic(mut self, v: bool) -> Self {
668 self.style.italic = v;
669 self
670 }
671 pub fn line_height(mut self, v: f64) -> Self {
673 self.style.line_height = v;
674 self
675 }
676 pub fn letter_spacing(mut self, v: f64) -> Self {
678 self.style.letter_spacing = v;
679 self
680 }
681 pub fn align(mut self, v: crate::TextAlign) -> Self {
683 self.style.align = v;
684 self
685 }
686 pub fn max_width(mut self, v: f64) -> Self {
688 self.style.max_width = Some(v);
689 self
690 }
691 pub fn display(self) -> Self {
693 self.role(TypeRole::Display)
694 }
695 pub fn h1(self) -> Self {
697 self.role(TypeRole::H1)
698 }
699 pub fn h2(self) -> Self {
701 self.role(TypeRole::H2)
702 }
703 pub fn body(self) -> Self {
705 self.role(TypeRole::Body)
706 }
707 pub fn caption(self) -> Self {
709 self.role(TypeRole::Caption)
710 }
711 pub fn code(self) -> Self {
713 self.family(crate::MONO).role(TypeRole::Code)
714 }
715
716 fn role(mut self, role: TypeRole) -> Self {
717 let r = theme::current().role(role);
718 self.style.size = r.size;
719 self.style.weight = r.weight;
720 self.style.line_height = r.line_height;
721 self
722 }
723}
724
725impl NodeBuilder for TxtBuilder {
726 type Handle = Txt;
727 base_accessor!();
728 fn build(mut self, g: &SceneGraph, parent: NodeId) -> Txt {
729 if !self.base.fill_explicit {
730 self.base.fill = theme::current().text;
731 }
732 let k = Self::KIND;
733 let kind = NodeKind::Txt {
734 text: sig(g, k, "text", self.text),
735 spans: sig(g, k, "spans", self.spans),
736 font_size: sig(g, k, "font_size", self.style.size),
737 weight: sig(g, k, "weight", self.style.weight),
738 style: self.style,
739 morph: sig(g, k, "morph", None),
740 morph_t: sig(g, k, "morph_t", 0.0),
741 };
742 Txt::from_id(insert(g, parent, kind, self.base))
743 }
744}
745
746builder!(CodeBuilder => Code, kind "Code", defaults {},
749 fields { code: String = String::new(), lang: oxframe_text::Lang = oxframe_text::Lang::Rust,
750 theme: CodeTheme = CodeTheme::dark(), theme_set: bool = false, style: TextStyle = TextStyle {
751 family: oxframe_text::MONO.to_owned(), size: 32.0, line_height: 1.45, ..TextStyle::default()
752 }, focus: Vec2 = Vec2::ZERO, dim: f64 = 0.3 });
753
754impl CodeBuilder {
755 pub fn code(mut self, v: impl Into<String>) -> Self {
757 self.code = v.into();
758 self
759 }
760 pub fn language(mut self, name: &str) -> Self {
763 self.lang = oxframe_text::Lang::parse(name);
764 self
765 }
766 pub fn theme(mut self, t: CodeTheme) -> Self {
769 self.theme = t;
770 self.theme_set = true;
771 self
772 }
773 pub fn font_size(mut self, v: f64) -> Self {
775 self.style.size = v;
776 self
777 }
778 pub fn line_height(mut self, v: f64) -> Self {
780 self.style.line_height = v;
781 self
782 }
783 pub fn focus(mut self, first: usize, last: usize) -> Self {
785 self.focus = Vec2::new(first as f64, last as f64);
786 self
787 }
788 pub fn dim(mut self, v: f64) -> Self {
790 self.dim = v;
791 self
792 }
793}
794
795impl NodeBuilder for CodeBuilder {
796 type Handle = Code;
797 base_accessor!();
798 fn build(self, g: &SceneGraph, parent: NodeId) -> Code {
799 let k = Self::KIND;
800 let theme = if self.theme_set {
801 self.theme
802 } else if theme::current().is_light() {
803 CodeTheme::light()
804 } else {
805 CodeTheme::dark()
806 };
807 let kind = NodeKind::Code {
808 code: sig(g, k, "code", self.code),
809 edit_from: sig(g, k, "edit_from", String::new()),
810 edit_to: sig(g, k, "edit_to", String::new()),
811 editing: sig(g, k, "editing", false),
812 edit_t: sig(g, k, "edit_t", 0.0),
813 edit_flash: sig(g, k, "edit_flash", false),
814 lang: self.lang,
815 theme,
816 font_size: sig(g, k, "font_size", self.style.size),
817 style: self.style,
818 focus: sig(g, k, "focus", self.focus),
819 dim: sig(g, k, "dim", self.dim),
820 };
821 Code::from_id(insert(g, parent, kind, self.base))
822 }
823}
824
825builder!(ImgBuilder => Img, kind "Img", defaults {},
828 fields { source: Option<ImageSource> = None, width: Option<f64> = None, height: Option<f64> = None });
829
830impl ImgBuilder {
831 pub fn src(mut self, bytes: &[u8]) -> Self {
838 self.source =
839 Some(ImageSource::decode(bytes).unwrap_or_else(|e| panic!("cannot decode image: {e}")));
840 self
841 }
842
843 #[cfg(not(target_arch = "wasm32"))]
849 pub fn file(self, path: impl AsRef<std::path::Path>) -> Self {
850 let path = path.as_ref();
851 let bytes =
852 std::fs::read(path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
853 self.src(&bytes)
854 }
855
856 pub fn width(mut self, v: f64) -> Self {
858 self.width = Some(v);
859 self
860 }
861
862 pub fn height(mut self, v: f64) -> Self {
864 self.height = Some(v);
865 self
866 }
867}
868
869impl NodeBuilder for ImgBuilder {
870 type Handle = Img;
871 base_accessor!();
872 fn build(self, g: &SceneGraph, parent: NodeId) -> Img {
873 let source = self
874 .source
875 .expect("Img needs a source: call .src(bytes) or .file(path)");
876 let (nw, nh) = source.natural_size();
877 let aspect = if nh > 0.0 { nw / nh } else { 1.0 };
878 let (w, h) = match (self.width, self.height) {
879 (Some(w), Some(h)) => (w, h),
880 (Some(w), None) => (w, w / aspect),
881 (None, Some(h)) => (h * aspect, h),
882 (None, None) => (nw, nh),
883 };
884 let k = Self::KIND;
885 let kind = NodeKind::Img {
886 source,
887 width: sig(g, k, "width", w),
888 height: sig(g, k, "height", h),
889 };
890 Img::from_id(insert(g, parent, kind, self.base))
891 }
892}
893
894builder!(
897 SvgBuilder => Svg, kind "Svg", defaults {},
899 fields { bytes: Vec<u8> = Vec::new(), width: Option<Span> = None, height: Option<Span> = None });
900
901impl SvgBuilder {
902 pub fn src(mut self, bytes: &[u8]) -> Self {
909 self.bytes = bytes.to_vec();
910 self
911 }
912
913 #[cfg(not(target_arch = "wasm32"))]
919 pub fn file(self, path: impl AsRef<std::path::Path>) -> Self {
920 let path = path.as_ref();
921 let bytes =
922 std::fs::read(path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
923 self.src(&bytes)
924 }
925
926 pub fn width(mut self, v: impl Into<Span>) -> Self {
929 self.width = Some(v.into());
930 self
931 }
932
933 pub fn height(mut self, v: impl Into<Span>) -> Self {
936 self.height = Some(v.into());
937 self
938 }
939}
940
941impl NodeBuilder for SvgBuilder {
942 type Handle = crate::handles::Svg;
943 base_accessor!();
944 fn build(mut self, g: &SceneGraph, parent: NodeId) -> crate::handles::Svg {
945 if self.bytes.is_empty() {
946 panic!("Svg needs a source: call .src(bytes) or .file(path)");
947 }
948 let tree = usvg::Tree::from_data(&self.bytes, &usvg::Options::default())
949 .unwrap_or_else(|e| panic!("cannot parse svg: {e}"));
950 let size = tree.size();
951 let (nw, nh) = (f64::from(size.width()), f64::from(size.height()));
952 let aspect = if nh > 0.0 { nw / nh } else { 1.0 };
953 let px = |span: Option<Span>, axis: Axis| {
955 span.map(|s| place::span_signal(g, parent, axis, s, String::new()).get())
956 };
957 let (w, h) = match (px(self.width, Axis::X), px(self.height, Axis::Y)) {
958 (Some(w), Some(h)) => (w, h),
959 (Some(w), None) => (w, if aspect > 0.0 { w / aspect } else { w }),
960 (None, Some(h)) => (h * aspect, h),
961 (None, None) => (nw, nh),
962 };
963 if self.base.name.is_none()
964 && let Some(id) = (!tree.root().id().is_empty()).then(|| tree.root().id())
965 {
966 self.base.name = Some(id.to_string());
967 }
968 self.base.opacity *= f64::from(tree.root().opacity().get());
969 self.base.blend = crate::svg::blend(tree.root().blend_mode());
970 let k = Self::KIND;
971 let kind = NodeKind::Svg {
972 width: sig(g, k, "width", w),
973 height: sig(g, k, "height", h),
974 };
975 let id = insert(g, parent, kind, self.base);
976 let fit = crate::svg::Fit {
977 nw,
978 nh,
979 sx: if nw > 0.0 { w / nw } else { 1.0 },
980 sy: if nh > 0.0 { h / nh } else { 1.0 },
981 };
982 crate::svg::inflate(g, id, tree.root(), &fit);
983 crate::handles::Svg::from_id(id)
984 }
985}
986
987builder!(
990 LayoutBuilder => Layout, kind "Layout", defaults {},
993 fields { direction: Direction = Direction::Row, align: LayoutAlign = LayoutAlign::Center,
994 justify: Justify = Justify::Center, wrap: bool = false, columns: Option<u16> = None,
995 gap: f64 = 0.0, padding: f64 = 0.0,
996 width: Option<Span> = None, height: Option<Span> = None, radius: f64 = 0.0 });
997setters!(LayoutBuilder {
998 direction: Direction,
999 align: LayoutAlign,
1000 justify: Justify
1001});
1002
1003impl LayoutBuilder {
1004 pub fn gap(mut self, v: impl Len) -> Self {
1006 self.gap = v.px();
1007 self
1008 }
1009 pub fn padding(mut self, v: impl Len) -> Self {
1011 self.padding = v.px();
1012 self
1013 }
1014 pub fn radius(mut self, v: impl Corner) -> Self {
1016 self.radius = v.radius();
1017 self
1018 }
1019}
1020
1021impl LayoutBuilder {
1022 pub fn wrap(mut self, v: bool) -> Self {
1024 self.wrap = v;
1025 self
1026 }
1027 pub fn grid(mut self, columns: u16) -> Self {
1029 self.columns = Some(columns.max(1));
1030 self
1031 }
1032 pub fn row(self) -> Self {
1034 self.direction(Direction::Row)
1035 }
1036 pub fn column(self) -> Self {
1038 self.direction(Direction::Column)
1039 }
1040 pub fn width(mut self, v: impl Into<Span>) -> Self {
1042 self.width = Some(v.into());
1043 self
1044 }
1045 pub fn height(mut self, v: impl Into<Span>) -> Self {
1047 self.height = Some(v.into());
1048 self
1049 }
1050}
1051
1052impl NodeBuilder for LayoutBuilder {
1053 type Handle = Layout;
1054 base_accessor!();
1055 fn build(self, g: &SceneGraph, parent: NodeId) -> Layout {
1056 let k = Self::KIND;
1057 let kind = NodeKind::Layout {
1058 direction: self.direction,
1059 align: self.align,
1060 justify: self.justify,
1061 wrap: self.wrap,
1062 columns: self.columns,
1063 gap: sig(g, k, "gap", self.gap),
1064 padding: sig(g, k, "padding", self.padding),
1065 width: place::span_option(
1066 g,
1067 parent,
1068 Axis::X,
1069 self.width,
1070 format!("{k}#{}.width", g.next_id()),
1071 ),
1072 height: place::span_option(
1073 g,
1074 parent,
1075 Axis::Y,
1076 self.height,
1077 format!("{k}#{}.height", g.next_id()),
1078 ),
1079 radius: sig(g, k, "radius", self.radius),
1080 };
1081 Layout::from_id(insert(g, parent, kind, self.base))
1082 }
1083}
1084
1085builder!(GridBuilder => Grid, kind "Grid",
1088 defaults { stroke: Color::rgba(1.0, 1.0, 1.0, 0.15), line_width: 1.0 },
1089 fields { width: f64 = 1920.0, height: f64 = 1080.0, spacing: f64 = 80.0 });
1090setters!(GridBuilder {
1091 width: f64,
1092 height: f64,
1093 spacing: f64
1094});
1095
1096impl NodeBuilder for GridBuilder {
1097 type Handle = Grid;
1098 base_accessor!();
1099 fn build(self, g: &SceneGraph, parent: NodeId) -> Grid {
1100 let k = Self::KIND;
1101 let kind = NodeKind::Grid {
1102 width: sig(g, k, "width", self.width),
1103 height: sig(g, k, "height", self.height),
1104 spacing: sig(g, k, "spacing", self.spacing),
1105 };
1106 Grid::from_id(insert(g, parent, kind, self.base))
1107 }
1108}
1109
1110#[must_use = "a builder does nothing until it is passed to `add`"]
1114pub struct CustomBuilder {
1115 base: BaseProps,
1116 draw: crate::draw::DrawFn,
1117 width: f64,
1118 height: f64,
1119 fullscreen: bool,
1120}
1121
1122impl CustomBuilder {
1123 pub(crate) fn from_draw(draw: impl Fn(&mut crate::draw::DrawCtx, f64) + 'static) -> Self {
1124 Self {
1125 base: BaseProps::default(),
1126 draw: std::rc::Rc::new(draw),
1127 width: 100.0,
1128 height: 100.0,
1129 fullscreen: false,
1130 }
1131 }
1132
1133 pub fn size(mut self, width: f64, height: f64) -> Self {
1135 self.width = width;
1136 self.height = height;
1137 self.fullscreen = false;
1138 self
1139 }
1140
1141 pub fn fullscreen(mut self) -> Self {
1144 self.fullscreen = true;
1145 self
1146 }
1147}
1148
1149impl NodeBuilder for CustomBuilder {
1150 type Handle = Custom;
1151 base_accessor!();
1152 fn build(self, graph: &SceneGraph, parent: NodeId) -> Custom {
1153 let (width, height) = if self.fullscreen {
1154 graph.frame_size()
1155 } else {
1156 (self.width, self.height)
1157 };
1158 let kind = NodeKind::Custom {
1159 draw: self.draw,
1160 width,
1161 height,
1162 measure: None,
1163 state: None,
1164 type_name: "Custom",
1165 };
1166 Custom::from_id(insert(graph, parent, kind, self.base))
1167 }
1168}
1169
1170#[must_use = "a builder does nothing until it is passed to `add`"]
1174pub struct ParticlesBuilder {
1175 base: BaseProps,
1176 seed: u64,
1177 gravity: f64,
1178 toward_camera: bool,
1179 life: f64,
1180 speed: f64,
1181 radius: f64,
1182}
1183
1184impl Default for ParticlesBuilder {
1185 fn default() -> Self {
1186 Self {
1187 base: BaseProps::default(),
1188 seed: 1,
1189 gravity: 980.0,
1190 toward_camera: false,
1191 life: 1.4,
1192 speed: 360.0,
1193 radius: 5.0,
1194 }
1195 }
1196}
1197
1198impl ParticlesBuilder {
1199 pub fn seed(mut self, seed: u64) -> Self {
1201 self.seed = seed;
1202 self
1203 }
1204
1205 pub fn gravity(mut self, gravity: f64) -> Self {
1207 self.gravity = gravity;
1208 self
1209 }
1210
1211 pub fn toward_camera(mut self) -> Self {
1213 self.toward_camera = true;
1214 self
1215 }
1216
1217 pub fn life(mut self, seconds: f64) -> Self {
1219 self.life = seconds;
1220 self
1221 }
1222
1223 pub fn speed(mut self, speed: f64) -> Self {
1225 self.speed = speed;
1226 self
1227 }
1228
1229 pub fn radius(mut self, radius: f64) -> Self {
1231 self.radius = radius;
1232 self
1233 }
1234}
1235
1236impl NodeBuilder for ParticlesBuilder {
1237 type Handle = Particles;
1238 base_accessor!();
1239 fn build(self, graph: &SceneGraph, parent: NodeId) -> Particles {
1240 let kind = NodeKind::Particles {
1241 seed: self.seed,
1242 gravity: sig(graph, "Particles", "gravity", self.gravity),
1243 toward_camera: self.toward_camera,
1244 life: self.life,
1245 speed: self.speed,
1246 radius: self.radius,
1247 state: crate::sim::ParticlesState::new(),
1248 };
1249 Particles::from_id(insert(graph, parent, kind, self.base))
1250 }
1251}
1252
1253enum Seed {
1256 Num(f64),
1257 Vec2(f64, f64),
1258 Color(Color),
1259 Bool(bool),
1260}
1261
1262#[must_use = "a builder does nothing until it is passed to `add`"]
1264pub struct ShaderBuilder {
1265 base: BaseProps,
1266 code: crate::shader::ShaderCode,
1267 preset: crate::shader::Preset,
1268 width: Span,
1269 height: Span,
1270 fullscreen: bool,
1271 uniforms: Vec<(String, Seed)>,
1272 inputs: Vec<(String, crate::post::Input)>,
1273}
1274
1275impl ShaderBuilder {
1276 pub(crate) fn new(code: crate::shader::ShaderCode) -> Self {
1277 Self {
1278 base: BaseProps::default(),
1279 code,
1280 preset: crate::shader::Preset::Custom,
1281 width: Span::Px(100.0),
1282 height: Span::Px(100.0),
1283 fullscreen: false,
1284 uniforms: Vec::new(),
1285 inputs: Vec::new(),
1286 }
1287 }
1288
1289 pub(crate) fn preset() -> Self {
1290 let mut s = Self::new(crate::shader::ShaderCode::Wgsl(
1291 crate::shader::waves_wgsl().into(),
1292 ));
1293 s.preset = crate::shader::Preset::Waves;
1294 s.uniform("amp", 0.05)
1295 .uniform("speed", 1.0)
1296 .uniform_color("deep", Color::rgba(0.102_f32, 0.322_f32, 0.549_f32, 1.0))
1297 .uniform_color("foam", Color::rgba(0.910_f32, 0.953_f32, 0.980_f32, 1.0))
1298 }
1299
1300 pub fn width(mut self, v: impl Into<Span>) -> Self {
1302 self.width = v.into();
1303 self.fullscreen = false;
1304 self
1305 }
1306
1307 pub fn height(mut self, v: impl Into<Span>) -> Self {
1309 self.height = v.into();
1310 self.fullscreen = false;
1311 self
1312 }
1313
1314 pub fn fullscreen(mut self) -> Self {
1317 self.fullscreen = true;
1318 self
1319 }
1320
1321 pub fn uniform(mut self, name: impl Into<String>, value: f64) -> Self {
1324 self.set_uniform(name.into(), Seed::Num(value));
1325 self
1326 }
1327
1328 pub fn uniform_vec2(mut self, name: impl Into<String>, value: impl Into<Vec2>) -> Self {
1330 let v = value.into();
1331 self.set_uniform(name.into(), Seed::Vec2(v.x, v.y));
1332 self
1333 }
1334
1335 pub fn uniform_color(mut self, name: impl Into<String>, value: impl IntoPaint) -> Self {
1337 self.set_uniform(name.into(), Seed::Color(value.into_paint()));
1338 self
1339 }
1340
1341 pub fn uniform_bool(mut self, name: impl Into<String>, value: bool) -> Self {
1343 self.set_uniform(name.into(), Seed::Bool(value));
1344 self
1345 }
1346
1347 pub fn amp(self, value: f64) -> Self {
1349 self.uniform("amp", value)
1350 }
1351
1352 pub fn speed(self, value: f64) -> Self {
1354 self.uniform("speed", value)
1355 }
1356
1357 pub fn deep(self, value: impl IntoPaint) -> Self {
1359 self.uniform_color("deep", value)
1360 }
1361
1362 pub fn foam(self, value: impl IntoPaint) -> Self {
1364 self.uniform_color("foam", value)
1365 }
1366
1367 pub fn input(mut self, name: impl Into<String>, input: crate::post::Input) -> Self {
1371 let name = name.into();
1372 if let Some(slot) = self.inputs.iter_mut().find(|(n, _)| *n == name) {
1373 slot.1 = input;
1374 } else {
1375 self.inputs.push((name, input));
1376 }
1377 self
1378 }
1379
1380 pub fn wetness(self, value: f64) -> Self {
1383 self.uniform("wetness", value)
1384 }
1385
1386 pub fn seed(self, value: u64) -> Self {
1388 self.uniform("seed", value as f64)
1389 }
1390
1391 pub fn drop_size(self, value: f64) -> Self {
1393 self.uniform("drop_size", value)
1394 }
1395
1396 fn set_uniform(&mut self, name: String, value: Seed) {
1397 if let Some(slot) = self.uniforms.iter_mut().find(|(n, _)| *n == name) {
1398 slot.1 = value;
1399 } else {
1400 self.uniforms.push((name, value));
1401 }
1402 }
1403}
1404
1405impl NodeBuilder for ShaderBuilder {
1406 type Handle = Shader;
1407 base_accessor!();
1408 fn build(self, graph: &SceneGraph, parent: NodeId) -> Shader {
1409 let (width, height) = if self.fullscreen {
1410 let (w, h) = graph.frame_size();
1411 (Span::Px(w), Span::Px(h))
1412 } else {
1413 (self.width, self.height)
1414 };
1415 let n = graph.next_id();
1416 let uniforms = self
1417 .uniforms
1418 .into_iter()
1419 .map(|(name, seed)| {
1420 let value = match seed {
1421 Seed::Num(v) => crate::shader::UniformValue::Num(
1422 Signal::new(v).with_name(format!("Shader#{n}.{name}")),
1423 ),
1424 Seed::Vec2(x, y) => crate::shader::UniformValue::Vec2(
1425 Signal::new(Vec2::new(x, y)).with_name(format!("Shader#{n}.{name}")),
1426 ),
1427 Seed::Color(c) => crate::shader::UniformValue::Color(
1428 Signal::new(c).with_name(format!("Shader#{n}.{name}")),
1429 ),
1430 Seed::Bool(v) => crate::shader::UniformValue::Bool(
1431 Signal::new(if v { 1.0 } else { 0.0 })
1432 .with_name(format!("Shader#{n}.{name}")),
1433 ),
1434 };
1435 crate::shader::Uniform { name, value }
1436 })
1437 .collect();
1438 let kind = NodeKind::Shader {
1439 code: self.code,
1440 preset: self.preset,
1441 width: place::span_signal(graph, parent, Axis::X, width, format!("Shader#{n}.width")),
1442 height: place::span_signal(
1443 graph,
1444 parent,
1445 Axis::Y,
1446 height,
1447 format!("Shader#{n}.height"),
1448 ),
1449 uniforms,
1450 inputs: self.inputs,
1451 };
1452 Shader::from_id(insert(graph, parent, kind, self.base))
1453 }
1454}
1455
1456pub fn waves() -> ShaderBuilder {
1458 ShaderBuilder::preset()
1459}
1460
1461pub fn lens_drops() -> ShaderBuilder {
1468 let mut s = ShaderBuilder::new(crate::shader::ShaderCode::Wgsl(String::new()));
1469 s.preset = crate::shader::Preset::LensDrops;
1470 s.uniform("wetness", 0.6)
1471 .uniform("seed", 0.0)
1472 .uniform("drop_size", 1.0)
1473}
1474
1475#[must_use = "an effect does nothing until it is passed to `effect`"]
1479pub struct EffectBuilder {
1480 kind: crate::post::EffectKind,
1481 strength: f64,
1482 speed: f64,
1483}
1484
1485impl EffectBuilder {
1486 pub fn strength(mut self, value: f64) -> Self {
1488 self.strength = value;
1489 self
1490 }
1491
1492 pub fn speed(mut self, value: f64) -> Self {
1494 self.speed = value;
1495 self
1496 }
1497
1498 pub(crate) fn build(self, id: NodeId) -> crate::post::EffectData {
1499 crate::post::EffectData {
1500 kind: self.kind,
1501 strength: Signal::new(self.strength).with_name(format!("Effect#{}.strength", id.0)),
1502 speed: Signal::new(self.speed).with_name(format!("Effect#{}.speed", id.0)),
1503 }
1504 }
1505}
1506
1507pub fn ripple() -> EffectBuilder {
1512 EffectBuilder {
1513 kind: crate::post::EffectKind::Ripple,
1514 strength: 1.0,
1515 speed: 6.0,
1516 }
1517}
1518
1519#[must_use = "a builder does nothing until it is passed to `add`"]
1523pub struct VideoBuilder {
1524 base: BaseProps,
1525 path: Option<std::path::PathBuf>,
1526 width: Option<Span>,
1527 height: Option<Span>,
1528 radius: f64,
1529 muted: bool,
1530 rate: f64,
1531 start: f64,
1532}
1533
1534impl VideoBuilder {
1535 pub(crate) fn new() -> Self {
1536 Self {
1537 base: BaseProps::default(),
1538 path: None,
1539 width: None,
1540 height: None,
1541 radius: 0.0,
1542 muted: false,
1543 rate: 1.0,
1544 start: 0.0,
1545 }
1546 }
1547
1548 pub fn file(mut self, path: impl AsRef<std::path::Path>) -> Self {
1551 self.path = Some(path.as_ref().to_path_buf());
1552 self
1553 }
1554
1555 pub fn width(mut self, v: impl Into<Span>) -> Self {
1558 self.width = Some(v.into());
1559 self
1560 }
1561
1562 pub fn height(mut self, v: impl Into<Span>) -> Self {
1564 self.height = Some(v.into());
1565 self
1566 }
1567
1568 pub fn radius(mut self, v: f64) -> Self {
1570 self.radius = v;
1571 self
1572 }
1573
1574 pub fn muted(mut self, v: bool) -> Self {
1576 self.muted = v;
1577 self
1578 }
1579
1580 pub fn playback_rate(mut self, v: f64) -> Self {
1582 self.rate = v;
1583 self
1584 }
1585
1586 pub fn start_at(mut self, seconds: f64) -> Self {
1588 self.start = seconds.max(0.0);
1589 self
1590 }
1591}
1592
1593impl NodeBuilder for VideoBuilder {
1594 type Handle = crate::Video;
1595 base_accessor!();
1596 fn build(self, g: &SceneGraph, parent: NodeId) -> crate::Video {
1597 let path = self.path.unwrap_or_default();
1598 let (source, problem) = crate::video::open(&path);
1599 let info = source.info();
1600 let aspect = f64::from(info.width.max(1)) / f64::from(info.height.max(1));
1601 let px = |span: Option<Span>, axis: Axis| {
1602 span.map(|s| place::span_signal(g, parent, axis, s, String::new()).get())
1603 };
1604 let (w, h) = match (px(self.width, Axis::X), px(self.height, Axis::Y)) {
1605 (Some(w), Some(h)) => (w, h),
1606 (Some(w), None) => (w, w / aspect),
1607 (None, Some(h)) => (h * aspect, h),
1608 (None, None) => (f64::from(info.width), f64::from(info.height)),
1609 };
1610 let n = g.next_id();
1611 let kind = NodeKind::Video {
1612 data: std::rc::Rc::new(crate::video::VideoData {
1613 source,
1614 path,
1615 problem,
1616 muted: self.muted,
1617 playback: std::rc::Rc::new(crate::video::Playback::default()),
1618 }),
1619 width: Signal::new(w).with_name(format!("Video#{n}.width")),
1620 height: Signal::new(h).with_name(format!("Video#{n}.height")),
1621 time: Signal::new(self.start).with_name(format!("Video#{n}.time")),
1622 rate: Signal::new(self.rate).with_name(format!("Video#{n}.playback_rate")),
1623 radius: Signal::new(self.radius).with_name(format!("Video#{n}.radius")),
1624 };
1625 crate::Video::from_id(insert(g, parent, kind, self.base))
1626 }
1627}