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 offscreen: bool,
54 pub children: Vec<ChildBuilder>,
55 pub place: Option<Place>,
57}
58
59impl Default for BaseProps {
60 fn default() -> Self {
61 Self {
62 name: None,
63 x: 0.0,
64 y: 0.0,
65 rotation: 0.0,
66 scale: Vec2::new(1.0, 1.0),
67 opacity: 1.0,
68 fill: Color::TRANSPARENT,
69 fill_explicit: false,
70 stroke: Color::TRANSPARENT,
71 line_width: 0.0,
72 start: 0.0,
73 end: 1.0,
74 fill_gradient: None,
75 stroke_gradient: None,
76 dash: Vec::new(),
77 dash_offset: 0.0,
78 line_cap: LineCap::Round,
79 line_join: LineJoin::Round,
80 blur: 0.0,
81 shadow_color: Color::TRANSPARENT,
82 shadow_blur: 0.0,
83 shadow_offset: Vec2::ZERO,
84 clip: false,
85 blend: BlendMode::Normal,
86 z_index: 0.0,
87 anchor: Vec2::ZERO,
88 grow: 0.0,
89 shrink: 0.0,
90 basis: None,
91 margin: 0.0,
92 align_self: None,
93 offscreen: false,
94 children: Vec::new(),
95 place: None,
96 }
97 }
98}
99
100type BuildFn = Box<dyn FnOnce(&SceneGraph, NodeId)>;
102
103pub struct ChildBuilder(BuildFn);
105
106impl std::fmt::Debug for ChildBuilder {
107 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108 f.write_str("ChildBuilder")
109 }
110}
111
112pub trait NodeBuilder: Sized + 'static {
114 type Handle;
116
117 #[doc(hidden)]
118 fn base(&mut self) -> &mut BaseProps;
119
120 #[doc(hidden)]
121 fn build(self, graph: &SceneGraph, parent: NodeId) -> Self::Handle;
122
123 fn name(mut self, v: impl Into<String>) -> Self {
126 self.base().name = Some(v.into());
127 self
128 }
129 fn x(mut self, v: f64) -> Self {
131 self.base().x = v;
132 self
133 }
134 fn y(mut self, v: f64) -> Self {
136 self.base().y = v;
137 self
138 }
139 fn position(mut self, v: impl Into<Vec2>) -> Self {
141 let v = v.into();
142 let b = self.base();
143 (b.x, b.y) = (v.x, v.y);
144 self
145 }
146 fn rotation(mut self, v: f64) -> Self {
148 self.base().rotation = v;
149 self
150 }
151 fn scale(mut self, v: impl crate::IntoScale) -> Self {
154 self.base().scale = v.into_scale();
155 self
156 }
157 fn scale_xy(mut self, v: impl Into<Vec2>) -> Self {
159 self.base().scale = v.into();
160 self
161 }
162 fn opacity(mut self, v: f64) -> Self {
164 self.base().opacity = v;
165 self
166 }
167 fn fill(mut self, v: impl IntoPaint) -> Self {
170 let b = self.base();
171 b.fill = v.into_paint();
172 b.fill_explicit = true;
173 self
174 }
175 fn stroke(mut self, v: impl IntoPaint) -> Self {
178 self.base().stroke = v.into_paint();
179 self
180 }
181 fn line_width(mut self, v: f64) -> Self {
183 self.base().line_width = v;
184 self
185 }
186 fn start(mut self, v: f64) -> Self {
188 self.base().start = v;
189 self
190 }
191 fn end(mut self, v: f64) -> Self {
193 self.base().end = v;
194 self
195 }
196 fn fill_gradient(mut self, g: Gradient) -> Self {
199 self.base().fill_gradient = Some(g);
200 self
201 }
202 fn stroke_gradient(mut self, g: Gradient) -> Self {
204 self.base().stroke_gradient = Some(g);
205 self
206 }
207 fn dash(mut self, pattern: impl Into<Vec<f64>>) -> Self {
210 self.base().dash = pattern.into();
211 self
212 }
213 fn dash_offset(mut self, v: f64) -> Self {
215 self.base().dash_offset = v;
216 self
217 }
218 fn line_cap(mut self, v: LineCap) -> Self {
220 self.base().line_cap = v;
221 self
222 }
223 fn line_join(mut self, v: LineJoin) -> Self {
225 self.base().line_join = v;
226 self
227 }
228 fn blur(mut self, v: f64) -> Self {
231 self.base().blur = v;
232 self
233 }
234 fn shadow(mut self, color: impl IntoPaint, blur: f64, offset: impl Into<Vec2>) -> Self {
236 let b = self.base();
237 (b.shadow_color, b.shadow_blur, b.shadow_offset) =
238 (color.into_paint(), blur, offset.into());
239 self
240 }
241 fn elevation(self, step: Step) -> Self {
243 let s = theme::current().shadow(step);
244 self.shadow(s.color, s.blur, s.offset)
245 }
246 fn glow(self, color: impl IntoPaint, blur: f64) -> Self {
248 self.shadow(color, blur, Vec2::ZERO)
249 }
250 fn clip(mut self, v: bool) -> Self {
252 self.base().clip = v;
253 self
254 }
255 fn blend(mut self, v: BlendMode) -> Self {
257 self.base().blend = v;
258 self
259 }
260 fn offscreen(mut self) -> Self {
264 self.base().offscreen = true;
265 self
266 }
267 fn z_index(mut self, v: f64) -> Self {
269 self.base().z_index = v;
270 self
271 }
272 fn anchor(mut self, v: impl Into<Vec2>) -> Self {
274 self.base().anchor = v.into();
275 self
276 }
277 fn grow(mut self, v: f64) -> Self {
279 self.base().grow = v;
280 self
281 }
282 fn shrink(mut self, v: f64) -> Self {
284 self.base().shrink = v;
285 self
286 }
287 fn basis(mut self, v: f64) -> Self {
289 self.base().basis = Some(v);
290 self
291 }
292 fn margin(mut self, v: impl Len) -> Self {
294 self.base().margin = v.px();
295 self
296 }
297 fn align_self(mut self, v: LayoutAlign) -> Self {
299 self.base().align_self = Some(v);
300 self
301 }
302 fn next_to(mut self, other: impl Into<crate::Node>, side: Side) -> Self {
305 self.base().place = Some(Place::next_to(other.into().id(), side));
306 self
307 }
308 fn align_to(mut self, other: impl Into<crate::Node>, edge: Edge) -> Self {
310 self.base().place = Some(Place::align_to(other.into().id(), edge));
311 self
312 }
313 fn to_edge(mut self, edge: Edge) -> Self {
316 self.base().place = Some(Place::to_edge(edge));
317 self
318 }
319 fn center_in(mut self, other: impl Into<crate::Node>) -> Self {
321 self.base().place = Some(Place::center_in(other.into().id()));
322 self
323 }
324 fn child<C: NodeBuilder>(mut self, child: C) -> Self {
327 self.base()
328 .children
329 .push(ChildBuilder(Box::new(move |g, parent| {
330 child.build(g, parent);
331 })));
332 self
333 }
334}
335
336pub(crate) fn add<B: NodeBuilder>(parent: NodeId, builder: B) -> B::Handle {
337 with_graph(|g| builder.build(g, parent))
338}
339
340pub(crate) fn insert(
342 graph: &SceneGraph,
343 parent: NodeId,
344 kind: NodeKind,
345 mut base: BaseProps,
346) -> NodeId {
347 let children = std::mem::take(&mut base.children);
348 let id = graph.insert(Some(parent), kind, base);
349 for c in children {
350 (c.0)(graph, id);
351 }
352 id
353}
354
355fn sig<T: Clone + 'static>(graph: &SceneGraph, kind: &str, field: &str, v: T) -> Signal<T> {
356 Signal::new(v).with_name(format!("{kind}#{}.{field}", graph.next_id()))
357}
358
359macro_rules! builder {
360 (
361 $(#[$doc:meta])*
362 $builder:ident => $handle:ident, kind $kind:literal, defaults { $($bf:ident: $bv:expr),* $(,)? },
363 fields { $($field:ident: $ty:ty = $default:expr),* $(,)? }
364 ) => {
365 $(#[$doc])*
366 #[doc = ""]
367 #[doc = concat!("Builder for a [`", stringify!($handle), "`] node: set the initial values, then")]
368 #[doc = "pass it to `add`."]
369 #[derive(Debug)]
370 #[must_use = "a builder does nothing until it is passed to `add`"]
371 pub struct $builder {
372 base: BaseProps,
373 $(pub(crate) $field: $ty,)*
374 }
375
376 impl Default for $builder {
377 fn default() -> Self {
378 Self {
379 base: BaseProps { $($bf: $bv,)* ..Default::default() },
380 $($field: $default,)*
381 }
382 }
383 }
384
385 impl $builder {
386 #[allow(dead_code)]
387 const KIND: &'static str = $kind;
388 }
389 };
390}
391
392macro_rules! setters {
393 ($builder:ident { $($(#[$doc:meta])* $field:ident: $ty:ty),* $(,)? }) => {
394 impl $builder {
395 $(
396 $(#[$doc])*
397 #[doc = ""]
398 #[doc = concat!("Sets the initial `", stringify!($field), "`.")]
399 pub fn $field(mut self, v: impl Into<$ty>) -> Self {
400 self.$field = v.into();
401 self
402 }
403 )*
404 }
405 };
406}
407
408macro_rules! base_accessor {
409 () => {
410 fn base(&mut self) -> &mut BaseProps {
411 &mut self.base
412 }
413 };
414}
415
416builder!(GroupBuilder => Group, kind "Group", defaults {}, fields {});
419
420impl NodeBuilder for GroupBuilder {
421 type Handle = Group;
422 base_accessor!();
423 fn build(self, graph: &SceneGraph, parent: NodeId) -> Group {
424 Group::from_id(insert(graph, parent, NodeKind::Group, self.base))
425 }
426}
427
428builder!(
431 CameraBuilder => Camera, kind "Camera", defaults {}, fields { zoom: f64 = 1.0 });
436setters!(CameraBuilder {
437 zoom: f64
439});
440
441impl NodeBuilder for CameraBuilder {
442 type Handle = Camera;
443 base_accessor!();
444 fn build(self, g: &SceneGraph, parent: NodeId) -> Camera {
445 let kind = NodeKind::Camera {
446 zoom: sig(g, Self::KIND, "zoom", self.zoom),
447 };
448 Camera::from_id(insert(g, parent, kind, self.base))
449 }
450}
451
452builder!(RectBuilder => Rect, kind "Rect", defaults { fill: Color::WHITE, fill_explicit: true },
455 fields { width: Span = Span::Px(100.0), height: Span = Span::Px(100.0), radius: f64 = 0.0 });
456
457impl RectBuilder {
458 pub fn width(mut self, v: impl Into<Span>) -> Self {
460 self.width = v.into();
461 self
462 }
463 pub fn height(mut self, v: impl Into<Span>) -> Self {
465 self.height = v.into();
466 self
467 }
468 pub fn radius(mut self, v: impl Corner) -> Self {
471 self.radius = v.radius();
472 self
473 }
474 pub fn size(mut self, v: f64) -> Self {
476 (self.width, self.height) = (Span::Px(v), Span::Px(v));
477 self
478 }
479}
480
481impl NodeBuilder for RectBuilder {
482 type Handle = Rect;
483 base_accessor!();
484 fn build(self, g: &SceneGraph, parent: NodeId) -> Rect {
485 let k = Self::KIND;
486 let n = g.next_id();
487 let kind = NodeKind::Rect {
488 width: place::span_signal(g, parent, Axis::X, self.width, format!("{k}#{n}.width")),
489 height: place::span_signal(g, parent, Axis::Y, self.height, format!("{k}#{n}.height")),
490 radius: sig(g, k, "radius", self.radius),
491 };
492 Rect::from_id(insert(g, parent, kind, self.base))
493 }
494}
495
496builder!(CircleBuilder => Circle, kind "Circle", defaults { fill: Color::WHITE, fill_explicit: true },
499 fields { size: f64 = 100.0, start_angle: f64 = 0.0, end_angle: f64 = 360.0, pie: bool = false });
500setters!(CircleBuilder {
501 size: f64,
503 start_angle: f64,
505 end_angle: f64,
506 pie: bool
508});
509
510impl NodeBuilder for CircleBuilder {
511 type Handle = Circle;
512 base_accessor!();
513 fn build(self, g: &SceneGraph, parent: NodeId) -> Circle {
514 let k = Self::KIND;
515 let kind = NodeKind::Circle {
516 size: sig(g, k, "size", self.size),
517 start_angle: sig(g, k, "start_angle", self.start_angle),
518 end_angle: sig(g, k, "end_angle", self.end_angle),
519 pie: self.pie,
520 };
521 Circle::from_id(insert(g, parent, kind, self.base))
522 }
523}
524
525builder!(PolygonBuilder => Polygon, kind "Polygon", defaults { fill: Color::WHITE, fill_explicit: true },
528 fields { sides: f64 = 6.0, size: f64 = 100.0 });
529setters!(PolygonBuilder {
530 sides: f64,
532 size: f64
534});
535
536impl NodeBuilder for PolygonBuilder {
537 type Handle = Polygon;
538 base_accessor!();
539 fn build(self, g: &SceneGraph, parent: NodeId) -> Polygon {
540 let k = Self::KIND;
541 let kind = NodeKind::Polygon {
542 sides: sig(g, k, "sides", self.sides),
543 size: sig(g, k, "size", self.size),
544 };
545 Polygon::from_id(insert(g, parent, kind, self.base))
546 }
547}
548
549builder!(StarBuilder => Star, kind "Star", defaults { fill: Color::WHITE, fill_explicit: true },
552 fields { points: f64 = 5.0, size: f64 = 100.0, inner: f64 = 0.45 });
553setters!(StarBuilder {
554 points: f64,
555 size: f64,
557 inner: f64
559});
560
561impl NodeBuilder for StarBuilder {
562 type Handle = Star;
563 base_accessor!();
564 fn build(self, g: &SceneGraph, parent: NodeId) -> Star {
565 let k = Self::KIND;
566 let kind = NodeKind::Star {
567 points: sig(g, k, "points", self.points),
568 size: sig(g, k, "size", self.size),
569 inner: sig(g, k, "inner", self.inner),
570 };
571 Star::from_id(insert(g, parent, kind, self.base))
572 }
573}
574
575builder!(PathBuilder => Path, kind "Path", defaults { fill: Color::WHITE, fill_explicit: true },
578 fields { data: BezPath = BezPath::new() });
579setters!(PathBuilder { data: BezPath });
580
581impl PathBuilder {
582 pub fn svg(mut self, d: &str) -> Self {
588 self.data = BezPath::from_svg(d).unwrap_or_else(|e| panic!("invalid SVG path {d:?}: {e}"));
589 self
590 }
591}
592
593impl NodeBuilder for PathBuilder {
594 type Handle = Path;
595 base_accessor!();
596 fn build(self, g: &SceneGraph, parent: NodeId) -> Path {
597 let kind = NodeKind::Path {
598 data: sig(g, Self::KIND, "data", self.data),
599 };
600 Path::from_id(insert(g, parent, kind, self.base))
601 }
602}
603
604builder!(LineBuilder => Line, kind "Line",
607 defaults { stroke: Color::WHITE, line_width: 4.0 },
608 fields { points: Vec<Vec2> = Vec::new(), closed: bool = false, smooth: bool = false,
609 start_arrow: bool = false, end_arrow: bool = false, arrow_size: f64 = 24.0 });
610setters!(LineBuilder {
611 closed: bool,
612 smooth: bool,
614 start_arrow: bool,
615 end_arrow: bool,
616 arrow_size: f64
617});
618
619impl LineBuilder {
620 pub fn points<P: Into<Vec2>>(mut self, pts: impl IntoIterator<Item = P>) -> Self {
622 self.points = pts.into_iter().map(Into::into).collect();
623 self
624 }
625}
626
627impl NodeBuilder for LineBuilder {
628 type Handle = Line;
629 base_accessor!();
630 fn build(self, g: &SceneGraph, parent: NodeId) -> Line {
631 let kind = NodeKind::Line {
632 points: sig(g, Self::KIND, "points", self.points),
633 closed: self.closed,
634 smooth: self.smooth,
635 start_arrow: self.start_arrow,
636 end_arrow: self.end_arrow,
637 arrow_size: sig(g, Self::KIND, "arrow_size", self.arrow_size),
638 };
639 Line::from_id(insert(g, parent, kind, self.base))
640 }
641}
642
643builder!(TxtBuilder => Txt, kind "Txt", defaults { fill: Color::WHITE },
646 fields { text: String = String::new(), spans: Vec<RichSpan> = Vec::new(),
647 style: TextStyle = TextStyle::default() });
648
649impl TxtBuilder {
650 pub fn text(mut self, v: impl Into<String>) -> Self {
652 self.text = v.into();
653 self
654 }
655 pub fn rich(mut self, spans: impl IntoIterator<Item = RichSpan>) -> Self {
657 self.spans = spans.into_iter().collect();
658 self
659 }
660 pub fn font_size(mut self, v: impl Into<f64>) -> Self {
662 self.style.size = v.into();
663 self
664 }
665 pub fn weight(mut self, v: impl Into<f64>) -> Self {
667 self.style.weight = v.into();
668 self
669 }
670 pub fn family(mut self, v: impl Into<String>) -> Self {
673 self.style.family = v.into();
674 self
675 }
676 pub fn italic(mut self, v: bool) -> Self {
678 self.style.italic = v;
679 self
680 }
681 pub fn line_height(mut self, v: f64) -> Self {
683 self.style.line_height = v;
684 self
685 }
686 pub fn letter_spacing(mut self, v: f64) -> Self {
688 self.style.letter_spacing = v;
689 self
690 }
691 pub fn align(mut self, v: crate::TextAlign) -> Self {
693 self.style.align = v;
694 self
695 }
696 pub fn max_width(mut self, v: f64) -> Self {
698 self.style.max_width = Some(v);
699 self
700 }
701 pub fn display(self) -> Self {
703 self.role(TypeRole::Display)
704 }
705 pub fn h1(self) -> Self {
707 self.role(TypeRole::H1)
708 }
709 pub fn h2(self) -> Self {
711 self.role(TypeRole::H2)
712 }
713 pub fn body(self) -> Self {
715 self.role(TypeRole::Body)
716 }
717 pub fn caption(self) -> Self {
719 self.role(TypeRole::Caption)
720 }
721 pub fn code(self) -> Self {
723 self.family(crate::MONO).role(TypeRole::Code)
724 }
725
726 fn role(mut self, role: TypeRole) -> Self {
727 let r = theme::current().role(role);
728 self.style.size = r.size;
729 self.style.weight = r.weight;
730 self.style.line_height = r.line_height;
731 self
732 }
733}
734
735impl NodeBuilder for TxtBuilder {
736 type Handle = Txt;
737 base_accessor!();
738 fn build(mut self, g: &SceneGraph, parent: NodeId) -> Txt {
739 if !self.base.fill_explicit {
740 self.base.fill = theme::current().text;
741 }
742 let k = Self::KIND;
743 let kind = NodeKind::Txt {
744 text: sig(g, k, "text", self.text),
745 spans: sig(g, k, "spans", self.spans),
746 font_size: sig(g, k, "font_size", self.style.size),
747 weight: sig(g, k, "weight", self.style.weight),
748 style: self.style,
749 morph: sig(g, k, "morph", None),
750 morph_t: sig(g, k, "morph_t", 0.0),
751 };
752 Txt::from_id(insert(g, parent, kind, self.base))
753 }
754}
755
756builder!(CodeBuilder => Code, kind "Code", defaults {},
759 fields { code: String = String::new(), lang: oxframe_text::Lang = oxframe_text::Lang::Rust,
760 theme: CodeTheme = CodeTheme::dark(), theme_set: bool = false, style: TextStyle = TextStyle {
761 family: oxframe_text::MONO.to_owned(), size: 32.0, line_height: 1.45, ..TextStyle::default()
762 }, focus: Vec2 = Vec2::ZERO, dim: f64 = 0.3 });
763
764impl CodeBuilder {
765 pub fn code(mut self, v: impl Into<String>) -> Self {
767 self.code = v.into();
768 self
769 }
770 pub fn language(mut self, name: &str) -> Self {
773 self.lang = oxframe_text::Lang::parse(name);
774 self
775 }
776 pub fn theme(mut self, t: CodeTheme) -> Self {
779 self.theme = t;
780 self.theme_set = true;
781 self
782 }
783 pub fn font_size(mut self, v: impl Into<f64>) -> Self {
785 self.style.size = v.into();
786 self
787 }
788 pub fn line_height(mut self, v: f64) -> Self {
790 self.style.line_height = v;
791 self
792 }
793 pub fn focus(mut self, first: usize, last: usize) -> Self {
795 self.focus = Vec2::new(first as f64, last as f64);
796 self
797 }
798 pub fn dim(mut self, v: f64) -> Self {
800 self.dim = v;
801 self
802 }
803}
804
805impl NodeBuilder for CodeBuilder {
806 type Handle = Code;
807 base_accessor!();
808 fn build(self, g: &SceneGraph, parent: NodeId) -> Code {
809 let k = Self::KIND;
810 let theme = if self.theme_set {
811 self.theme
812 } else if theme::current().is_light() {
813 CodeTheme::light()
814 } else {
815 CodeTheme::dark()
816 };
817 let kind = NodeKind::Code {
818 code: sig(g, k, "code", self.code),
819 edit_from: sig(g, k, "edit_from", String::new()),
820 edit_to: sig(g, k, "edit_to", String::new()),
821 editing: sig(g, k, "editing", false),
822 edit_t: sig(g, k, "edit_t", 0.0),
823 edit_flash: sig(g, k, "edit_flash", false),
824 lang: self.lang,
825 theme,
826 font_size: sig(g, k, "font_size", self.style.size),
827 style: self.style,
828 focus: sig(g, k, "focus", self.focus),
829 focus_span: sig(g, k, "focus_span", Vec2::ZERO),
830 dim: sig(g, k, "dim", self.dim),
831 };
832 Code::from_id(insert(g, parent, kind, self.base))
833 }
834}
835
836builder!(ImgBuilder => Img, kind "Img", defaults {},
839 fields { source: Option<ImageSource> = None, width: Option<f64> = None, height: Option<f64> = None });
840
841impl ImgBuilder {
842 pub fn src(mut self, bytes: &[u8]) -> Self {
849 self.source =
850 Some(ImageSource::decode(bytes).unwrap_or_else(|e| panic!("cannot decode image: {e}")));
851 self
852 }
853
854 #[cfg(not(target_arch = "wasm32"))]
860 pub fn file(self, path: impl AsRef<std::path::Path>) -> Self {
861 let path = path.as_ref();
862 let bytes =
863 std::fs::read(path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
864 self.src(&bytes)
865 }
866
867 pub fn width(mut self, v: f64) -> Self {
869 self.width = Some(v);
870 self
871 }
872
873 pub fn height(mut self, v: f64) -> Self {
875 self.height = Some(v);
876 self
877 }
878}
879
880impl NodeBuilder for ImgBuilder {
881 type Handle = Img;
882 base_accessor!();
883 fn build(self, g: &SceneGraph, parent: NodeId) -> Img {
884 let source = self
885 .source
886 .expect("Img needs a source: call .src(bytes) or .file(path)");
887 let (nw, nh) = source.natural_size();
888 let aspect = if nh > 0.0 { nw / nh } else { 1.0 };
889 let (w, h) = match (self.width, self.height) {
890 (Some(w), Some(h)) => (w, h),
891 (Some(w), None) => (w, w / aspect),
892 (None, Some(h)) => (h * aspect, h),
893 (None, None) => (nw, nh),
894 };
895 let k = Self::KIND;
896 let kind = NodeKind::Img {
897 source,
898 width: sig(g, k, "width", w),
899 height: sig(g, k, "height", h),
900 };
901 Img::from_id(insert(g, parent, kind, self.base))
902 }
903}
904
905builder!(
908 SvgBuilder => Svg, kind "Svg", defaults {},
910 fields { bytes: Vec<u8> = Vec::new(), width: Option<Span> = None, height: Option<Span> = None });
911
912impl SvgBuilder {
913 pub fn src(mut self, bytes: &[u8]) -> Self {
920 self.bytes = bytes.to_vec();
921 self
922 }
923
924 #[cfg(not(target_arch = "wasm32"))]
930 pub fn file(self, path: impl AsRef<std::path::Path>) -> Self {
931 let path = path.as_ref();
932 let bytes =
933 std::fs::read(path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
934 self.src(&bytes)
935 }
936
937 pub fn width(mut self, v: impl Into<Span>) -> Self {
940 self.width = Some(v.into());
941 self
942 }
943
944 pub fn height(mut self, v: impl Into<Span>) -> Self {
947 self.height = Some(v.into());
948 self
949 }
950}
951
952impl NodeBuilder for SvgBuilder {
953 type Handle = crate::handles::Svg;
954 base_accessor!();
955 fn build(mut self, g: &SceneGraph, parent: NodeId) -> crate::handles::Svg {
956 if self.bytes.is_empty() {
957 panic!("Svg needs a source: call .src(bytes) or .file(path)");
958 }
959 let tree = usvg::Tree::from_data(&self.bytes, &usvg::Options::default())
960 .unwrap_or_else(|e| panic!("cannot parse svg: {e}"));
961 let size = tree.size();
962 let (nw, nh) = (f64::from(size.width()), f64::from(size.height()));
963 let aspect = if nh > 0.0 { nw / nh } else { 1.0 };
964 let px = |span: Option<Span>, axis: Axis| {
966 span.map(|s| place::span_signal(g, parent, axis, s, String::new()).get())
967 };
968 let (w, h) = match (px(self.width, Axis::X), px(self.height, Axis::Y)) {
969 (Some(w), Some(h)) => (w, h),
970 (Some(w), None) => (w, if aspect > 0.0 { w / aspect } else { w }),
971 (None, Some(h)) => (h * aspect, h),
972 (None, None) => (nw, nh),
973 };
974 if self.base.name.is_none()
975 && let Some(id) = (!tree.root().id().is_empty()).then(|| tree.root().id())
976 {
977 self.base.name = Some(id.to_string());
978 }
979 self.base.opacity *= f64::from(tree.root().opacity().get());
980 self.base.blend = crate::svg::blend(tree.root().blend_mode());
981 let k = Self::KIND;
982 let kind = NodeKind::Svg {
983 width: sig(g, k, "width", w),
984 height: sig(g, k, "height", h),
985 };
986 let id = insert(g, parent, kind, self.base);
987 let fit = crate::svg::Fit {
988 nw,
989 nh,
990 sx: if nw > 0.0 { w / nw } else { 1.0 },
991 sy: if nh > 0.0 { h / nh } else { 1.0 },
992 };
993 crate::svg::inflate(g, id, tree.root(), &fit);
994 crate::handles::Svg::from_id(id)
995 }
996}
997
998builder!(
1001 LayoutBuilder => Layout, kind "Layout", defaults {},
1004 fields { direction: Direction = Direction::Row, align: LayoutAlign = LayoutAlign::Center,
1005 justify: Justify = Justify::Center, wrap: bool = false, columns: Option<u16> = None,
1006 gap: f64 = 0.0, padding: f64 = 0.0,
1007 width: Option<Span> = None, height: Option<Span> = None, radius: f64 = 0.0 });
1008setters!(LayoutBuilder {
1009 direction: Direction,
1010 align: LayoutAlign,
1011 justify: Justify
1012});
1013
1014impl LayoutBuilder {
1015 pub fn gap(mut self, v: impl Len) -> Self {
1017 self.gap = v.px();
1018 self
1019 }
1020 pub fn padding(mut self, v: impl Len) -> Self {
1022 self.padding = v.px();
1023 self
1024 }
1025 pub fn radius(mut self, v: impl Corner) -> Self {
1027 self.radius = v.radius();
1028 self
1029 }
1030}
1031
1032impl LayoutBuilder {
1033 pub fn wrap(mut self, v: bool) -> Self {
1035 self.wrap = v;
1036 self
1037 }
1038 pub fn grid(mut self, columns: u16) -> Self {
1040 self.columns = Some(columns.max(1));
1041 self
1042 }
1043 pub fn row(self) -> Self {
1045 self.direction(Direction::Row)
1046 }
1047 pub fn column(self) -> Self {
1049 self.direction(Direction::Column)
1050 }
1051 pub fn width(mut self, v: impl Into<Span>) -> Self {
1053 self.width = Some(v.into());
1054 self
1055 }
1056 pub fn height(mut self, v: impl Into<Span>) -> Self {
1058 self.height = Some(v.into());
1059 self
1060 }
1061}
1062
1063impl NodeBuilder for LayoutBuilder {
1064 type Handle = Layout;
1065 base_accessor!();
1066 fn build(self, g: &SceneGraph, parent: NodeId) -> Layout {
1067 let k = Self::KIND;
1068 let kind = NodeKind::Layout {
1069 direction: self.direction,
1070 align: self.align,
1071 justify: self.justify,
1072 wrap: self.wrap,
1073 columns: self.columns,
1074 gap: sig(g, k, "gap", self.gap),
1075 padding: sig(g, k, "padding", self.padding),
1076 width: place::span_option(
1077 g,
1078 parent,
1079 Axis::X,
1080 self.width,
1081 format!("{k}#{}.width", g.next_id()),
1082 ),
1083 height: place::span_option(
1084 g,
1085 parent,
1086 Axis::Y,
1087 self.height,
1088 format!("{k}#{}.height", g.next_id()),
1089 ),
1090 radius: sig(g, k, "radius", self.radius),
1091 };
1092 Layout::from_id(insert(g, parent, kind, self.base))
1093 }
1094}
1095
1096builder!(GridBuilder => Grid, kind "Grid",
1099 defaults { stroke: Color::rgba(1.0, 1.0, 1.0, 0.15), line_width: 1.0 },
1100 fields { width: f64 = 1920.0, height: f64 = 1080.0, spacing: f64 = 80.0 });
1101setters!(GridBuilder {
1102 width: f64,
1103 height: f64,
1104 spacing: f64
1105});
1106
1107impl NodeBuilder for GridBuilder {
1108 type Handle = Grid;
1109 base_accessor!();
1110 fn build(self, g: &SceneGraph, parent: NodeId) -> Grid {
1111 let k = Self::KIND;
1112 let kind = NodeKind::Grid {
1113 width: sig(g, k, "width", self.width),
1114 height: sig(g, k, "height", self.height),
1115 spacing: sig(g, k, "spacing", self.spacing),
1116 };
1117 Grid::from_id(insert(g, parent, kind, self.base))
1118 }
1119}
1120
1121#[must_use = "a builder does nothing until it is passed to `add`"]
1125pub struct CustomBuilder {
1126 base: BaseProps,
1127 draw: crate::draw::DrawFn,
1128 width: f64,
1129 height: f64,
1130 fullscreen: bool,
1131}
1132
1133impl CustomBuilder {
1134 pub(crate) fn from_draw(draw: impl Fn(&mut crate::draw::DrawCtx, f64) + 'static) -> Self {
1135 Self {
1136 base: BaseProps::default(),
1137 draw: std::rc::Rc::new(draw),
1138 width: 100.0,
1139 height: 100.0,
1140 fullscreen: false,
1141 }
1142 }
1143
1144 pub fn size(mut self, width: f64, height: f64) -> Self {
1146 self.width = width;
1147 self.height = height;
1148 self.fullscreen = false;
1149 self
1150 }
1151
1152 pub fn fullscreen(mut self) -> Self {
1155 self.fullscreen = true;
1156 self
1157 }
1158}
1159
1160impl NodeBuilder for CustomBuilder {
1161 type Handle = Custom;
1162 base_accessor!();
1163 fn build(self, graph: &SceneGraph, parent: NodeId) -> Custom {
1164 let (width, height) = if self.fullscreen {
1165 graph.frame_size()
1166 } else {
1167 (self.width, self.height)
1168 };
1169 let kind = NodeKind::Custom {
1170 draw: self.draw,
1171 width,
1172 height,
1173 measure: None,
1174 state: None,
1175 type_name: "Custom",
1176 };
1177 Custom::from_id(insert(graph, parent, kind, self.base))
1178 }
1179}
1180
1181#[must_use = "a builder does nothing until it is passed to `add`"]
1185pub struct ParticlesBuilder {
1186 base: BaseProps,
1187 seed: u64,
1188 gravity: f64,
1189 toward_camera: bool,
1190 life: f64,
1191 speed: f64,
1192 radius: f64,
1193}
1194
1195impl Default for ParticlesBuilder {
1196 fn default() -> Self {
1197 Self {
1198 base: BaseProps::default(),
1199 seed: 1,
1200 gravity: 980.0,
1201 toward_camera: false,
1202 life: 1.4,
1203 speed: 360.0,
1204 radius: 5.0,
1205 }
1206 }
1207}
1208
1209impl ParticlesBuilder {
1210 pub fn seed(mut self, seed: u64) -> Self {
1212 self.seed = seed;
1213 self
1214 }
1215
1216 pub fn gravity(mut self, gravity: f64) -> Self {
1218 self.gravity = gravity;
1219 self
1220 }
1221
1222 pub fn toward_camera(mut self) -> Self {
1224 self.toward_camera = true;
1225 self
1226 }
1227
1228 pub fn life(mut self, seconds: f64) -> Self {
1230 self.life = seconds;
1231 self
1232 }
1233
1234 pub fn speed(mut self, speed: f64) -> Self {
1236 self.speed = speed;
1237 self
1238 }
1239
1240 pub fn radius(mut self, radius: f64) -> Self {
1242 self.radius = radius;
1243 self
1244 }
1245}
1246
1247impl NodeBuilder for ParticlesBuilder {
1248 type Handle = Particles;
1249 base_accessor!();
1250 fn build(self, graph: &SceneGraph, parent: NodeId) -> Particles {
1251 let kind = NodeKind::Particles {
1252 seed: self.seed,
1253 gravity: sig(graph, "Particles", "gravity", self.gravity),
1254 toward_camera: self.toward_camera,
1255 life: self.life,
1256 speed: self.speed,
1257 radius: self.radius,
1258 state: crate::sim::ParticlesState::new(),
1259 };
1260 Particles::from_id(insert(graph, parent, kind, self.base))
1261 }
1262}
1263
1264enum Seed {
1267 Num(f64),
1268 Vec2(f64, f64),
1269 Color(Color),
1270 Bool(bool),
1271}
1272
1273#[must_use = "a builder does nothing until it is passed to `add`"]
1275pub struct ShaderBuilder {
1276 base: BaseProps,
1277 code: crate::shader::ShaderCode,
1278 preset: crate::shader::Preset,
1279 width: Span,
1280 height: Span,
1281 fullscreen: bool,
1282 uniforms: Vec<(String, Seed)>,
1283 inputs: Vec<(String, crate::post::Input)>,
1284}
1285
1286impl ShaderBuilder {
1287 pub(crate) fn new(code: crate::shader::ShaderCode) -> Self {
1288 Self {
1289 base: BaseProps::default(),
1290 code,
1291 preset: crate::shader::Preset::Custom,
1292 width: Span::Px(100.0),
1293 height: Span::Px(100.0),
1294 fullscreen: false,
1295 uniforms: Vec::new(),
1296 inputs: Vec::new(),
1297 }
1298 }
1299
1300 pub(crate) fn preset() -> Self {
1301 let mut s = Self::new(crate::shader::ShaderCode::Wgsl(
1302 crate::shader::waves_wgsl().into(),
1303 ));
1304 s.preset = crate::shader::Preset::Waves;
1305 s.uniform("amp", 0.05)
1306 .uniform("speed", 1.0)
1307 .uniform_color("deep", Color::rgba(0.102_f32, 0.322_f32, 0.549_f32, 1.0))
1308 .uniform_color("foam", Color::rgba(0.910_f32, 0.953_f32, 0.980_f32, 1.0))
1309 }
1310
1311 pub fn width(mut self, v: impl Into<Span>) -> Self {
1313 self.width = v.into();
1314 self.fullscreen = false;
1315 self
1316 }
1317
1318 pub fn height(mut self, v: impl Into<Span>) -> Self {
1320 self.height = v.into();
1321 self.fullscreen = false;
1322 self
1323 }
1324
1325 pub fn size(self, width: impl Into<Span>, height: impl Into<Span>) -> Self {
1328 self.width(width).height(height)
1329 }
1330
1331 pub fn fullscreen(mut self) -> Self {
1334 self.fullscreen = true;
1335 self
1336 }
1337
1338 pub fn uniform(mut self, name: impl Into<String>, value: f64) -> Self {
1341 self.set_uniform(name.into(), Seed::Num(value));
1342 self
1343 }
1344
1345 pub fn uniform_vec2(mut self, name: impl Into<String>, value: impl Into<Vec2>) -> Self {
1347 let v = value.into();
1348 self.set_uniform(name.into(), Seed::Vec2(v.x, v.y));
1349 self
1350 }
1351
1352 pub fn uniform_color(mut self, name: impl Into<String>, value: impl IntoPaint) -> Self {
1354 self.set_uniform(name.into(), Seed::Color(value.into_paint()));
1355 self
1356 }
1357
1358 pub fn uniform_bool(mut self, name: impl Into<String>, value: bool) -> Self {
1360 self.set_uniform(name.into(), Seed::Bool(value));
1361 self
1362 }
1363
1364 pub fn amp(self, value: f64) -> Self {
1366 self.uniform("amp", value)
1367 }
1368
1369 pub fn speed(self, value: f64) -> Self {
1371 self.uniform("speed", value)
1372 }
1373
1374 pub fn deep(self, value: impl IntoPaint) -> Self {
1376 self.uniform_color("deep", value)
1377 }
1378
1379 pub fn foam(self, value: impl IntoPaint) -> Self {
1381 self.uniform_color("foam", value)
1382 }
1383
1384 pub fn input(mut self, name: impl Into<String>, input: crate::post::Input) -> Self {
1398 let name = name.into();
1399 if let Some(slot) = self.inputs.iter_mut().find(|(n, _)| *n == name) {
1400 slot.1 = input;
1401 } else {
1402 self.inputs.push((name, input));
1403 }
1404 self
1405 }
1406
1407 pub fn wetness(self, value: f64) -> Self {
1410 self.uniform("wetness", value)
1411 }
1412
1413 pub fn seed(self, value: u64) -> Self {
1415 self.uniform("seed", value as f64)
1416 }
1417
1418 pub fn drop_size(self, value: f64) -> Self {
1420 self.uniform("drop_size", value)
1421 }
1422
1423 fn set_uniform(&mut self, name: String, value: Seed) {
1424 if let Some(slot) = self.uniforms.iter_mut().find(|(n, _)| *n == name) {
1425 slot.1 = value;
1426 } else {
1427 self.uniforms.push((name, value));
1428 }
1429 }
1430}
1431
1432impl NodeBuilder for ShaderBuilder {
1433 type Handle = Shader;
1434 base_accessor!();
1435 fn build(self, graph: &SceneGraph, parent: NodeId) -> Shader {
1436 let (width, height) = if self.fullscreen {
1437 let (w, h) = graph.frame_size();
1438 (Span::Px(w), Span::Px(h))
1439 } else {
1440 (self.width, self.height)
1441 };
1442 let n = graph.next_id();
1443 let uniforms = self
1444 .uniforms
1445 .into_iter()
1446 .map(|(name, seed)| {
1447 let value = match seed {
1448 Seed::Num(v) => crate::shader::UniformValue::Num(
1449 Signal::new(v).with_name(format!("Shader#{n}.{name}")),
1450 ),
1451 Seed::Vec2(x, y) => crate::shader::UniformValue::Vec2(
1452 Signal::new(Vec2::new(x, y)).with_name(format!("Shader#{n}.{name}")),
1453 ),
1454 Seed::Color(c) => crate::shader::UniformValue::Color(
1455 Signal::new(c).with_name(format!("Shader#{n}.{name}")),
1456 ),
1457 Seed::Bool(v) => crate::shader::UniformValue::Bool(
1458 Signal::new(if v { 1.0 } else { 0.0 })
1459 .with_name(format!("Shader#{n}.{name}")),
1460 ),
1461 };
1462 crate::shader::Uniform { name, value }
1463 })
1464 .collect();
1465 let kind = NodeKind::Shader {
1466 code: self.code,
1467 preset: self.preset,
1468 width: place::span_signal(graph, parent, Axis::X, width, format!("Shader#{n}.width")),
1469 height: place::span_signal(
1470 graph,
1471 parent,
1472 Axis::Y,
1473 height,
1474 format!("Shader#{n}.height"),
1475 ),
1476 uniforms,
1477 inputs: self.inputs,
1478 };
1479 Shader::from_id(insert(graph, parent, kind, self.base))
1480 }
1481}
1482
1483pub fn waves() -> ShaderBuilder {
1485 ShaderBuilder::preset()
1486}
1487
1488pub fn lens_drops() -> ShaderBuilder {
1495 let mut s = ShaderBuilder::new(crate::shader::ShaderCode::Wgsl(String::new()));
1496 s.preset = crate::shader::Preset::LensDrops;
1497 s.uniform("wetness", 0.6)
1498 .uniform("seed", 0.0)
1499 .uniform("drop_size", 1.0)
1500}
1501
1502#[must_use = "an effect does nothing until it is passed to `effect`"]
1506pub struct EffectBuilder {
1507 kind: crate::post::EffectKind,
1508 strength: f64,
1509 speed: f64,
1510}
1511
1512impl EffectBuilder {
1513 pub fn strength(mut self, value: f64) -> Self {
1515 self.strength = value;
1516 self
1517 }
1518
1519 pub fn speed(mut self, value: f64) -> Self {
1521 self.speed = value;
1522 self
1523 }
1524
1525 pub(crate) fn build(self, id: NodeId) -> crate::post::EffectData {
1526 crate::post::EffectData {
1527 kind: self.kind,
1528 strength: Signal::new(self.strength).with_name(format!("Effect#{}.strength", id.0)),
1529 speed: Signal::new(self.speed).with_name(format!("Effect#{}.speed", id.0)),
1530 }
1531 }
1532}
1533
1534pub fn ripple() -> EffectBuilder {
1540 EffectBuilder {
1541 kind: crate::post::EffectKind::Ripple,
1542 strength: 1.0,
1543 speed: 6.0,
1544 }
1545}
1546
1547#[must_use = "a builder does nothing until it is passed to `add`"]
1551pub struct VideoBuilder {
1552 base: BaseProps,
1553 path: Option<std::path::PathBuf>,
1554 width: Option<Span>,
1555 height: Option<Span>,
1556 radius: f64,
1557 muted: bool,
1558 rate: f64,
1559 start: f64,
1560}
1561
1562impl VideoBuilder {
1563 pub(crate) fn new() -> Self {
1564 Self {
1565 base: BaseProps::default(),
1566 path: None,
1567 width: None,
1568 height: None,
1569 radius: 0.0,
1570 muted: false,
1571 rate: 1.0,
1572 start: 0.0,
1573 }
1574 }
1575
1576 pub fn file(mut self, path: impl AsRef<std::path::Path>) -> Self {
1579 self.path = Some(path.as_ref().to_path_buf());
1580 self
1581 }
1582
1583 pub fn width(mut self, v: impl Into<Span>) -> Self {
1586 self.width = Some(v.into());
1587 self
1588 }
1589
1590 pub fn height(mut self, v: impl Into<Span>) -> Self {
1592 self.height = Some(v.into());
1593 self
1594 }
1595
1596 pub fn radius(mut self, v: f64) -> Self {
1598 self.radius = v;
1599 self
1600 }
1601
1602 pub fn muted(mut self, v: bool) -> Self {
1604 self.muted = v;
1605 self
1606 }
1607
1608 pub fn playback_rate(mut self, v: f64) -> Self {
1610 self.rate = v;
1611 self
1612 }
1613
1614 pub fn start_at(mut self, seconds: f64) -> Self {
1616 self.start = seconds.max(0.0);
1617 self
1618 }
1619}
1620
1621impl NodeBuilder for VideoBuilder {
1622 type Handle = crate::Video;
1623 base_accessor!();
1624 fn build(self, g: &SceneGraph, parent: NodeId) -> crate::Video {
1625 let path = self.path.unwrap_or_default();
1626 let (source, problem) = crate::video::open(&path);
1627 let info = source.info();
1628 let aspect = f64::from(info.width.max(1)) / f64::from(info.height.max(1));
1629 let px = |span: Option<Span>, axis: Axis| {
1630 span.map(|s| place::span_signal(g, parent, axis, s, String::new()).get())
1631 };
1632 let (w, h) = match (px(self.width, Axis::X), px(self.height, Axis::Y)) {
1633 (Some(w), Some(h)) => (w, h),
1634 (Some(w), None) => (w, w / aspect),
1635 (None, Some(h)) => (h * aspect, h),
1636 (None, None) => (f64::from(info.width), f64::from(info.height)),
1637 };
1638 let n = g.next_id();
1639 let kind = NodeKind::Video {
1640 data: std::rc::Rc::new(crate::video::VideoData {
1641 source,
1642 path,
1643 problem,
1644 muted: self.muted,
1645 playback: std::rc::Rc::new(crate::video::Playback::default()),
1646 }),
1647 width: Signal::new(w).with_name(format!("Video#{n}.width")),
1648 height: Signal::new(h).with_name(format!("Video#{n}.height")),
1649 time: Signal::new(self.start).with_name(format!("Video#{n}.time")),
1650 rate: Signal::new(self.rate).with_name(format!("Video#{n}.playback_rate")),
1651 radius: Signal::new(self.radius).with_name(format!("Video#{n}.radius")),
1652 };
1653 crate::Video::from_id(insert(g, parent, kind, self.base))
1654 }
1655}