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
330fn insert(graph: &SceneGraph, parent: NodeId, kind: NodeKind, mut base: BaseProps) -> NodeId {
332 let children = std::mem::take(&mut base.children);
333 let id = graph.insert(Some(parent), kind, base);
334 for c in children {
335 (c.0)(graph, id);
336 }
337 id
338}
339
340fn sig<T: Clone + 'static>(graph: &SceneGraph, kind: &str, field: &str, v: T) -> Signal<T> {
341 Signal::new(v).with_name(format!("{kind}#{}.{field}", graph.next_id()))
342}
343
344macro_rules! builder {
345 (
346 $(#[$doc:meta])*
347 $builder:ident => $handle:ident, kind $kind:literal, defaults { $($bf:ident: $bv:expr),* $(,)? },
348 fields { $($field:ident: $ty:ty = $default:expr),* $(,)? }
349 ) => {
350 $(#[$doc])*
351 #[doc = ""]
352 #[doc = concat!("Builder for a [`", stringify!($handle), "`] node: set the initial values, then")]
353 #[doc = "pass it to `add`."]
354 #[derive(Debug)]
355 #[must_use = "a builder does nothing until it is passed to `add`"]
356 pub struct $builder {
357 base: BaseProps,
358 $(pub(crate) $field: $ty,)*
359 }
360
361 impl Default for $builder {
362 fn default() -> Self {
363 Self {
364 base: BaseProps { $($bf: $bv,)* ..Default::default() },
365 $($field: $default,)*
366 }
367 }
368 }
369
370 impl $builder {
371 #[allow(dead_code)]
372 const KIND: &'static str = $kind;
373 }
374 };
375}
376
377macro_rules! setters {
378 ($builder:ident { $($(#[$doc:meta])* $field:ident: $ty:ty),* $(,)? }) => {
379 impl $builder {
380 $(
381 $(#[$doc])*
382 #[doc = ""]
383 #[doc = concat!("Sets the initial `", stringify!($field), "`.")]
384 pub fn $field(mut self, v: impl Into<$ty>) -> Self {
385 self.$field = v.into();
386 self
387 }
388 )*
389 }
390 };
391}
392
393macro_rules! base_accessor {
394 () => {
395 fn base(&mut self) -> &mut BaseProps {
396 &mut self.base
397 }
398 };
399}
400
401builder!(GroupBuilder => Group, kind "Group", defaults {}, fields {});
404
405impl NodeBuilder for GroupBuilder {
406 type Handle = Group;
407 base_accessor!();
408 fn build(self, graph: &SceneGraph, parent: NodeId) -> Group {
409 Group::from_id(insert(graph, parent, NodeKind::Group, self.base))
410 }
411}
412
413builder!(
416 CameraBuilder => Camera, kind "Camera", defaults {}, fields { zoom: f64 = 1.0 });
421setters!(CameraBuilder {
422 zoom: f64
424});
425
426impl NodeBuilder for CameraBuilder {
427 type Handle = Camera;
428 base_accessor!();
429 fn build(self, g: &SceneGraph, parent: NodeId) -> Camera {
430 let kind = NodeKind::Camera {
431 zoom: sig(g, Self::KIND, "zoom", self.zoom),
432 };
433 Camera::from_id(insert(g, parent, kind, self.base))
434 }
435}
436
437builder!(RectBuilder => Rect, kind "Rect", defaults { fill: Color::WHITE, fill_explicit: true },
440 fields { width: Span = Span::Px(100.0), height: Span = Span::Px(100.0), radius: f64 = 0.0 });
441
442impl RectBuilder {
443 pub fn width(mut self, v: impl Into<Span>) -> Self {
445 self.width = v.into();
446 self
447 }
448 pub fn height(mut self, v: impl Into<Span>) -> Self {
450 self.height = v.into();
451 self
452 }
453 pub fn radius(mut self, v: impl Corner) -> Self {
456 self.radius = v.radius();
457 self
458 }
459 pub fn size(mut self, v: f64) -> Self {
461 (self.width, self.height) = (Span::Px(v), Span::Px(v));
462 self
463 }
464}
465
466impl NodeBuilder for RectBuilder {
467 type Handle = Rect;
468 base_accessor!();
469 fn build(self, g: &SceneGraph, parent: NodeId) -> Rect {
470 let k = Self::KIND;
471 let n = g.next_id();
472 let kind = NodeKind::Rect {
473 width: place::span_signal(g, parent, Axis::X, self.width, format!("{k}#{n}.width")),
474 height: place::span_signal(g, parent, Axis::Y, self.height, format!("{k}#{n}.height")),
475 radius: sig(g, k, "radius", self.radius),
476 };
477 Rect::from_id(insert(g, parent, kind, self.base))
478 }
479}
480
481builder!(CircleBuilder => Circle, kind "Circle", defaults { fill: Color::WHITE, fill_explicit: true },
484 fields { size: f64 = 100.0, start_angle: f64 = 0.0, end_angle: f64 = 360.0, pie: bool = false });
485setters!(CircleBuilder {
486 size: f64,
488 start_angle: f64,
490 end_angle: f64,
491 pie: bool
493});
494
495impl NodeBuilder for CircleBuilder {
496 type Handle = Circle;
497 base_accessor!();
498 fn build(self, g: &SceneGraph, parent: NodeId) -> Circle {
499 let k = Self::KIND;
500 let kind = NodeKind::Circle {
501 size: sig(g, k, "size", self.size),
502 start_angle: sig(g, k, "start_angle", self.start_angle),
503 end_angle: sig(g, k, "end_angle", self.end_angle),
504 pie: self.pie,
505 };
506 Circle::from_id(insert(g, parent, kind, self.base))
507 }
508}
509
510builder!(PolygonBuilder => Polygon, kind "Polygon", defaults { fill: Color::WHITE, fill_explicit: true },
513 fields { sides: f64 = 6.0, size: f64 = 100.0 });
514setters!(PolygonBuilder {
515 sides: f64,
517 size: f64
519});
520
521impl NodeBuilder for PolygonBuilder {
522 type Handle = Polygon;
523 base_accessor!();
524 fn build(self, g: &SceneGraph, parent: NodeId) -> Polygon {
525 let k = Self::KIND;
526 let kind = NodeKind::Polygon {
527 sides: sig(g, k, "sides", self.sides),
528 size: sig(g, k, "size", self.size),
529 };
530 Polygon::from_id(insert(g, parent, kind, self.base))
531 }
532}
533
534builder!(StarBuilder => Star, kind "Star", defaults { fill: Color::WHITE, fill_explicit: true },
537 fields { points: f64 = 5.0, size: f64 = 100.0, inner: f64 = 0.45 });
538setters!(StarBuilder {
539 points: f64,
540 size: f64,
542 inner: f64
544});
545
546impl NodeBuilder for StarBuilder {
547 type Handle = Star;
548 base_accessor!();
549 fn build(self, g: &SceneGraph, parent: NodeId) -> Star {
550 let k = Self::KIND;
551 let kind = NodeKind::Star {
552 points: sig(g, k, "points", self.points),
553 size: sig(g, k, "size", self.size),
554 inner: sig(g, k, "inner", self.inner),
555 };
556 Star::from_id(insert(g, parent, kind, self.base))
557 }
558}
559
560builder!(PathBuilder => Path, kind "Path", defaults { fill: Color::WHITE, fill_explicit: true },
563 fields { data: BezPath = BezPath::new() });
564setters!(PathBuilder { data: BezPath });
565
566impl PathBuilder {
567 pub fn svg(mut self, d: &str) -> Self {
573 self.data = BezPath::from_svg(d).unwrap_or_else(|e| panic!("invalid SVG path {d:?}: {e}"));
574 self
575 }
576}
577
578impl NodeBuilder for PathBuilder {
579 type Handle = Path;
580 base_accessor!();
581 fn build(self, g: &SceneGraph, parent: NodeId) -> Path {
582 let kind = NodeKind::Path {
583 data: sig(g, Self::KIND, "data", self.data),
584 };
585 Path::from_id(insert(g, parent, kind, self.base))
586 }
587}
588
589builder!(LineBuilder => Line, kind "Line",
592 defaults { stroke: Color::WHITE, line_width: 4.0 },
593 fields { points: Vec<Vec2> = Vec::new(), closed: bool = false, smooth: bool = false,
594 start_arrow: bool = false, end_arrow: bool = false, arrow_size: f64 = 24.0 });
595setters!(LineBuilder {
596 closed: bool,
597 smooth: bool,
599 start_arrow: bool,
600 end_arrow: bool,
601 arrow_size: f64
602});
603
604impl LineBuilder {
605 pub fn points<P: Into<Vec2>>(mut self, pts: impl IntoIterator<Item = P>) -> Self {
607 self.points = pts.into_iter().map(Into::into).collect();
608 self
609 }
610}
611
612impl NodeBuilder for LineBuilder {
613 type Handle = Line;
614 base_accessor!();
615 fn build(self, g: &SceneGraph, parent: NodeId) -> Line {
616 let kind = NodeKind::Line {
617 points: sig(g, Self::KIND, "points", self.points),
618 closed: self.closed,
619 smooth: self.smooth,
620 start_arrow: self.start_arrow,
621 end_arrow: self.end_arrow,
622 arrow_size: sig(g, Self::KIND, "arrow_size", self.arrow_size),
623 };
624 Line::from_id(insert(g, parent, kind, self.base))
625 }
626}
627
628builder!(TxtBuilder => Txt, kind "Txt", defaults { fill: Color::WHITE },
631 fields { text: String = String::new(), spans: Vec<RichSpan> = Vec::new(),
632 style: TextStyle = TextStyle::default() });
633
634impl TxtBuilder {
635 pub fn text(mut self, v: impl Into<String>) -> Self {
637 self.text = v.into();
638 self
639 }
640 pub fn rich(mut self, spans: impl IntoIterator<Item = RichSpan>) -> Self {
642 self.spans = spans.into_iter().collect();
643 self
644 }
645 pub fn font_size(mut self, v: f64) -> Self {
647 self.style.size = v;
648 self
649 }
650 pub fn weight(mut self, v: f64) -> Self {
652 self.style.weight = v;
653 self
654 }
655 pub fn family(mut self, v: impl Into<String>) -> Self {
658 self.style.family = v.into();
659 self
660 }
661 pub fn italic(mut self, v: bool) -> Self {
663 self.style.italic = v;
664 self
665 }
666 pub fn line_height(mut self, v: f64) -> Self {
668 self.style.line_height = v;
669 self
670 }
671 pub fn letter_spacing(mut self, v: f64) -> Self {
673 self.style.letter_spacing = v;
674 self
675 }
676 pub fn align(mut self, v: crate::TextAlign) -> Self {
678 self.style.align = v;
679 self
680 }
681 pub fn max_width(mut self, v: f64) -> Self {
683 self.style.max_width = Some(v);
684 self
685 }
686 pub fn display(self) -> Self {
688 self.role(TypeRole::Display)
689 }
690 pub fn h1(self) -> Self {
692 self.role(TypeRole::H1)
693 }
694 pub fn h2(self) -> Self {
696 self.role(TypeRole::H2)
697 }
698 pub fn body(self) -> Self {
700 self.role(TypeRole::Body)
701 }
702 pub fn caption(self) -> Self {
704 self.role(TypeRole::Caption)
705 }
706 pub fn code(self) -> Self {
708 self.family(crate::MONO).role(TypeRole::Code)
709 }
710
711 fn role(mut self, role: TypeRole) -> Self {
712 let r = theme::current().role(role);
713 self.style.size = r.size;
714 self.style.weight = r.weight;
715 self.style.line_height = r.line_height;
716 self
717 }
718}
719
720impl NodeBuilder for TxtBuilder {
721 type Handle = Txt;
722 base_accessor!();
723 fn build(mut self, g: &SceneGraph, parent: NodeId) -> Txt {
724 if !self.base.fill_explicit {
725 self.base.fill = theme::current().text;
726 }
727 let k = Self::KIND;
728 let kind = NodeKind::Txt {
729 text: sig(g, k, "text", self.text),
730 spans: sig(g, k, "spans", self.spans),
731 font_size: sig(g, k, "font_size", self.style.size),
732 weight: sig(g, k, "weight", self.style.weight),
733 style: self.style,
734 morph: sig(g, k, "morph", None),
735 morph_t: sig(g, k, "morph_t", 0.0),
736 };
737 Txt::from_id(insert(g, parent, kind, self.base))
738 }
739}
740
741builder!(CodeBuilder => Code, kind "Code", defaults {},
744 fields { code: String = String::new(), lang: oxframe_text::Lang = oxframe_text::Lang::Rust,
745 theme: CodeTheme = CodeTheme::dark(), theme_set: bool = false, style: TextStyle = TextStyle {
746 family: oxframe_text::MONO.to_owned(), size: 32.0, line_height: 1.45, ..TextStyle::default()
747 }, focus: Vec2 = Vec2::ZERO, dim: f64 = 0.3 });
748
749impl CodeBuilder {
750 pub fn code(mut self, v: impl Into<String>) -> Self {
752 self.code = v.into();
753 self
754 }
755 pub fn language(mut self, name: &str) -> Self {
758 self.lang = oxframe_text::Lang::parse(name);
759 self
760 }
761 pub fn theme(mut self, t: CodeTheme) -> Self {
764 self.theme = t;
765 self.theme_set = true;
766 self
767 }
768 pub fn font_size(mut self, v: f64) -> Self {
770 self.style.size = v;
771 self
772 }
773 pub fn line_height(mut self, v: f64) -> Self {
775 self.style.line_height = v;
776 self
777 }
778 pub fn focus(mut self, first: usize, last: usize) -> Self {
780 self.focus = Vec2::new(first as f64, last as f64);
781 self
782 }
783 pub fn dim(mut self, v: f64) -> Self {
785 self.dim = v;
786 self
787 }
788}
789
790impl NodeBuilder for CodeBuilder {
791 type Handle = Code;
792 base_accessor!();
793 fn build(self, g: &SceneGraph, parent: NodeId) -> Code {
794 let k = Self::KIND;
795 let theme = if self.theme_set {
796 self.theme
797 } else if theme::current().is_light() {
798 CodeTheme::light()
799 } else {
800 CodeTheme::dark()
801 };
802 let kind = NodeKind::Code {
803 code: sig(g, k, "code", self.code),
804 edit_from: sig(g, k, "edit_from", String::new()),
805 edit_to: sig(g, k, "edit_to", String::new()),
806 editing: sig(g, k, "editing", false),
807 edit_t: sig(g, k, "edit_t", 0.0),
808 edit_flash: sig(g, k, "edit_flash", false),
809 lang: self.lang,
810 theme,
811 font_size: sig(g, k, "font_size", self.style.size),
812 style: self.style,
813 focus: sig(g, k, "focus", self.focus),
814 dim: sig(g, k, "dim", self.dim),
815 };
816 Code::from_id(insert(g, parent, kind, self.base))
817 }
818}
819
820builder!(ImgBuilder => Img, kind "Img", defaults {},
823 fields { source: Option<ImageSource> = None, width: Option<f64> = None, height: Option<f64> = None });
824
825impl ImgBuilder {
826 pub fn src(mut self, bytes: &[u8]) -> Self {
833 self.source =
834 Some(ImageSource::decode(bytes).unwrap_or_else(|e| panic!("cannot decode image: {e}")));
835 self
836 }
837
838 #[cfg(not(target_arch = "wasm32"))]
844 pub fn file(self, path: impl AsRef<std::path::Path>) -> Self {
845 let path = path.as_ref();
846 let bytes =
847 std::fs::read(path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
848 self.src(&bytes)
849 }
850
851 pub fn width(mut self, v: f64) -> Self {
853 self.width = Some(v);
854 self
855 }
856
857 pub fn height(mut self, v: f64) -> Self {
859 self.height = Some(v);
860 self
861 }
862}
863
864impl NodeBuilder for ImgBuilder {
865 type Handle = Img;
866 base_accessor!();
867 fn build(self, g: &SceneGraph, parent: NodeId) -> Img {
868 let source = self
869 .source
870 .expect("Img needs a source: call .src(bytes) or .file(path)");
871 let (nw, nh) = source.natural_size();
872 let aspect = if nh > 0.0 { nw / nh } else { 1.0 };
873 let (w, h) = match (self.width, self.height) {
874 (Some(w), Some(h)) => (w, h),
875 (Some(w), None) => (w, w / aspect),
876 (None, Some(h)) => (h * aspect, h),
877 (None, None) => (nw, nh),
878 };
879 let k = Self::KIND;
880 let kind = NodeKind::Img {
881 source,
882 width: sig(g, k, "width", w),
883 height: sig(g, k, "height", h),
884 };
885 Img::from_id(insert(g, parent, kind, self.base))
886 }
887}
888
889builder!(
892 SvgBuilder => Svg, kind "Svg", defaults {},
894 fields { bytes: Vec<u8> = Vec::new(), width: Option<Span> = None, height: Option<Span> = None });
895
896impl SvgBuilder {
897 pub fn src(mut self, bytes: &[u8]) -> Self {
904 self.bytes = bytes.to_vec();
905 self
906 }
907
908 #[cfg(not(target_arch = "wasm32"))]
914 pub fn file(self, path: impl AsRef<std::path::Path>) -> Self {
915 let path = path.as_ref();
916 let bytes =
917 std::fs::read(path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
918 self.src(&bytes)
919 }
920
921 pub fn width(mut self, v: impl Into<Span>) -> Self {
924 self.width = Some(v.into());
925 self
926 }
927
928 pub fn height(mut self, v: impl Into<Span>) -> Self {
931 self.height = Some(v.into());
932 self
933 }
934}
935
936impl NodeBuilder for SvgBuilder {
937 type Handle = crate::handles::Svg;
938 base_accessor!();
939 fn build(mut self, g: &SceneGraph, parent: NodeId) -> crate::handles::Svg {
940 if self.bytes.is_empty() {
941 panic!("Svg needs a source: call .src(bytes) or .file(path)");
942 }
943 let tree = usvg::Tree::from_data(&self.bytes, &usvg::Options::default())
944 .unwrap_or_else(|e| panic!("cannot parse svg: {e}"));
945 let size = tree.size();
946 let (nw, nh) = (f64::from(size.width()), f64::from(size.height()));
947 let aspect = if nh > 0.0 { nw / nh } else { 1.0 };
948 let px = |span: Option<Span>, axis: Axis| {
950 span.map(|s| place::span_signal(g, parent, axis, s, String::new()).get())
951 };
952 let (w, h) = match (px(self.width, Axis::X), px(self.height, Axis::Y)) {
953 (Some(w), Some(h)) => (w, h),
954 (Some(w), None) => (w, if aspect > 0.0 { w / aspect } else { w }),
955 (None, Some(h)) => (h * aspect, h),
956 (None, None) => (nw, nh),
957 };
958 if self.base.name.is_none()
959 && let Some(id) = (!tree.root().id().is_empty()).then(|| tree.root().id())
960 {
961 self.base.name = Some(id.to_string());
962 }
963 self.base.opacity *= f64::from(tree.root().opacity().get());
964 self.base.blend = crate::svg::blend(tree.root().blend_mode());
965 let k = Self::KIND;
966 let kind = NodeKind::Svg {
967 width: sig(g, k, "width", w),
968 height: sig(g, k, "height", h),
969 };
970 let id = insert(g, parent, kind, self.base);
971 let fit = crate::svg::Fit {
972 nw,
973 nh,
974 sx: if nw > 0.0 { w / nw } else { 1.0 },
975 sy: if nh > 0.0 { h / nh } else { 1.0 },
976 };
977 crate::svg::inflate(g, id, tree.root(), &fit);
978 crate::handles::Svg::from_id(id)
979 }
980}
981
982builder!(
985 LayoutBuilder => Layout, kind "Layout", defaults {},
988 fields { direction: Direction = Direction::Row, align: LayoutAlign = LayoutAlign::Center,
989 justify: Justify = Justify::Center, wrap: bool = false, columns: Option<u16> = None,
990 gap: f64 = 0.0, padding: f64 = 0.0,
991 width: Option<Span> = None, height: Option<Span> = None, radius: f64 = 0.0 });
992setters!(LayoutBuilder {
993 direction: Direction,
994 align: LayoutAlign,
995 justify: Justify
996});
997
998impl LayoutBuilder {
999 pub fn gap(mut self, v: impl Len) -> Self {
1001 self.gap = v.px();
1002 self
1003 }
1004 pub fn padding(mut self, v: impl Len) -> Self {
1006 self.padding = v.px();
1007 self
1008 }
1009 pub fn radius(mut self, v: impl Corner) -> Self {
1011 self.radius = v.radius();
1012 self
1013 }
1014}
1015
1016impl LayoutBuilder {
1017 pub fn wrap(mut self, v: bool) -> Self {
1019 self.wrap = v;
1020 self
1021 }
1022 pub fn grid(mut self, columns: u16) -> Self {
1024 self.columns = Some(columns.max(1));
1025 self
1026 }
1027 pub fn row(self) -> Self {
1029 self.direction(Direction::Row)
1030 }
1031 pub fn column(self) -> Self {
1033 self.direction(Direction::Column)
1034 }
1035 pub fn width(mut self, v: impl Into<Span>) -> Self {
1037 self.width = Some(v.into());
1038 self
1039 }
1040 pub fn height(mut self, v: impl Into<Span>) -> Self {
1042 self.height = Some(v.into());
1043 self
1044 }
1045}
1046
1047impl NodeBuilder for LayoutBuilder {
1048 type Handle = Layout;
1049 base_accessor!();
1050 fn build(self, g: &SceneGraph, parent: NodeId) -> Layout {
1051 let k = Self::KIND;
1052 let kind = NodeKind::Layout {
1053 direction: self.direction,
1054 align: self.align,
1055 justify: self.justify,
1056 wrap: self.wrap,
1057 columns: self.columns,
1058 gap: sig(g, k, "gap", self.gap),
1059 padding: sig(g, k, "padding", self.padding),
1060 width: place::span_option(
1061 g,
1062 parent,
1063 Axis::X,
1064 self.width,
1065 format!("{k}#{}.width", g.next_id()),
1066 ),
1067 height: place::span_option(
1068 g,
1069 parent,
1070 Axis::Y,
1071 self.height,
1072 format!("{k}#{}.height", g.next_id()),
1073 ),
1074 radius: sig(g, k, "radius", self.radius),
1075 };
1076 Layout::from_id(insert(g, parent, kind, self.base))
1077 }
1078}
1079
1080builder!(GridBuilder => Grid, kind "Grid",
1083 defaults { stroke: Color::rgba(1.0, 1.0, 1.0, 0.15), line_width: 1.0 },
1084 fields { width: f64 = 1920.0, height: f64 = 1080.0, spacing: f64 = 80.0 });
1085setters!(GridBuilder {
1086 width: f64,
1087 height: f64,
1088 spacing: f64
1089});
1090
1091impl NodeBuilder for GridBuilder {
1092 type Handle = Grid;
1093 base_accessor!();
1094 fn build(self, g: &SceneGraph, parent: NodeId) -> Grid {
1095 let k = Self::KIND;
1096 let kind = NodeKind::Grid {
1097 width: sig(g, k, "width", self.width),
1098 height: sig(g, k, "height", self.height),
1099 spacing: sig(g, k, "spacing", self.spacing),
1100 };
1101 Grid::from_id(insert(g, parent, kind, self.base))
1102 }
1103}
1104
1105#[must_use = "a builder does nothing until it is passed to `add`"]
1109pub struct CustomBuilder {
1110 base: BaseProps,
1111 draw: crate::draw::DrawFn,
1112 width: f64,
1113 height: f64,
1114 fullscreen: bool,
1115}
1116
1117impl CustomBuilder {
1118 pub(crate) fn from_draw(draw: impl Fn(&mut crate::draw::DrawCtx, f64) + 'static) -> Self {
1119 Self {
1120 base: BaseProps::default(),
1121 draw: std::rc::Rc::new(draw),
1122 width: 100.0,
1123 height: 100.0,
1124 fullscreen: false,
1125 }
1126 }
1127
1128 pub fn size(mut self, width: f64, height: f64) -> Self {
1130 self.width = width;
1131 self.height = height;
1132 self.fullscreen = false;
1133 self
1134 }
1135
1136 pub fn fullscreen(mut self) -> Self {
1139 self.fullscreen = true;
1140 self
1141 }
1142}
1143
1144impl NodeBuilder for CustomBuilder {
1145 type Handle = Custom;
1146 base_accessor!();
1147 fn build(self, graph: &SceneGraph, parent: NodeId) -> Custom {
1148 let (width, height) = if self.fullscreen {
1149 graph.frame_size()
1150 } else {
1151 (self.width, self.height)
1152 };
1153 let kind = NodeKind::Custom {
1154 draw: self.draw,
1155 width,
1156 height,
1157 };
1158 Custom::from_id(insert(graph, parent, kind, self.base))
1159 }
1160}
1161
1162#[must_use = "a builder does nothing until it is passed to `add`"]
1166pub struct ParticlesBuilder {
1167 base: BaseProps,
1168 seed: u64,
1169 gravity: f64,
1170 toward_camera: bool,
1171 life: f64,
1172 speed: f64,
1173 radius: f64,
1174}
1175
1176impl Default for ParticlesBuilder {
1177 fn default() -> Self {
1178 Self {
1179 base: BaseProps::default(),
1180 seed: 1,
1181 gravity: 980.0,
1182 toward_camera: false,
1183 life: 1.4,
1184 speed: 360.0,
1185 radius: 5.0,
1186 }
1187 }
1188}
1189
1190impl ParticlesBuilder {
1191 pub fn seed(mut self, seed: u64) -> Self {
1193 self.seed = seed;
1194 self
1195 }
1196
1197 pub fn gravity(mut self, gravity: f64) -> Self {
1199 self.gravity = gravity;
1200 self
1201 }
1202
1203 pub fn toward_camera(mut self) -> Self {
1205 self.toward_camera = true;
1206 self
1207 }
1208
1209 pub fn life(mut self, seconds: f64) -> Self {
1211 self.life = seconds;
1212 self
1213 }
1214
1215 pub fn speed(mut self, speed: f64) -> Self {
1217 self.speed = speed;
1218 self
1219 }
1220
1221 pub fn radius(mut self, radius: f64) -> Self {
1223 self.radius = radius;
1224 self
1225 }
1226}
1227
1228impl NodeBuilder for ParticlesBuilder {
1229 type Handle = Particles;
1230 base_accessor!();
1231 fn build(self, graph: &SceneGraph, parent: NodeId) -> Particles {
1232 let kind = NodeKind::Particles {
1233 seed: self.seed,
1234 gravity: sig(graph, "Particles", "gravity", self.gravity),
1235 toward_camera: self.toward_camera,
1236 life: self.life,
1237 speed: self.speed,
1238 radius: self.radius,
1239 state: crate::sim::ParticlesState::new(),
1240 };
1241 Particles::from_id(insert(graph, parent, kind, self.base))
1242 }
1243}
1244
1245enum Seed {
1248 Num(f64),
1249 Vec2(f64, f64),
1250 Color(Color),
1251 Bool(bool),
1252}
1253
1254#[must_use = "a builder does nothing until it is passed to `add`"]
1256pub struct ShaderBuilder {
1257 base: BaseProps,
1258 code: crate::shader::ShaderCode,
1259 preset: crate::shader::Preset,
1260 width: Span,
1261 height: Span,
1262 fullscreen: bool,
1263 uniforms: Vec<(String, Seed)>,
1264 inputs: Vec<(String, crate::post::Input)>,
1265}
1266
1267impl ShaderBuilder {
1268 pub(crate) fn new(code: crate::shader::ShaderCode) -> Self {
1269 Self {
1270 base: BaseProps::default(),
1271 code,
1272 preset: crate::shader::Preset::Custom,
1273 width: Span::Px(100.0),
1274 height: Span::Px(100.0),
1275 fullscreen: false,
1276 uniforms: Vec::new(),
1277 inputs: Vec::new(),
1278 }
1279 }
1280
1281 pub(crate) fn preset() -> Self {
1282 let mut s = Self::new(crate::shader::ShaderCode::Wgsl(
1283 crate::shader::waves_wgsl().into(),
1284 ));
1285 s.preset = crate::shader::Preset::Waves;
1286 s.uniform("amp", 0.05)
1287 .uniform("speed", 1.0)
1288 .uniform_color("deep", Color::rgba(0.102_f32, 0.322_f32, 0.549_f32, 1.0))
1289 .uniform_color("foam", Color::rgba(0.910_f32, 0.953_f32, 0.980_f32, 1.0))
1290 }
1291
1292 pub fn width(mut self, v: impl Into<Span>) -> Self {
1294 self.width = v.into();
1295 self.fullscreen = false;
1296 self
1297 }
1298
1299 pub fn height(mut self, v: impl Into<Span>) -> Self {
1301 self.height = v.into();
1302 self.fullscreen = false;
1303 self
1304 }
1305
1306 pub fn fullscreen(mut self) -> Self {
1309 self.fullscreen = true;
1310 self
1311 }
1312
1313 pub fn uniform(mut self, name: impl Into<String>, value: f64) -> Self {
1316 self.set_uniform(name.into(), Seed::Num(value));
1317 self
1318 }
1319
1320 pub fn uniform_vec2(mut self, name: impl Into<String>, value: impl Into<Vec2>) -> Self {
1322 let v = value.into();
1323 self.set_uniform(name.into(), Seed::Vec2(v.x, v.y));
1324 self
1325 }
1326
1327 pub fn uniform_color(mut self, name: impl Into<String>, value: impl IntoPaint) -> Self {
1329 self.set_uniform(name.into(), Seed::Color(value.into_paint()));
1330 self
1331 }
1332
1333 pub fn uniform_bool(mut self, name: impl Into<String>, value: bool) -> Self {
1335 self.set_uniform(name.into(), Seed::Bool(value));
1336 self
1337 }
1338
1339 pub fn amp(self, value: f64) -> Self {
1341 self.uniform("amp", value)
1342 }
1343
1344 pub fn speed(self, value: f64) -> Self {
1346 self.uniform("speed", value)
1347 }
1348
1349 pub fn deep(self, value: impl IntoPaint) -> Self {
1351 self.uniform_color("deep", value)
1352 }
1353
1354 pub fn foam(self, value: impl IntoPaint) -> Self {
1356 self.uniform_color("foam", value)
1357 }
1358
1359 pub fn input(mut self, name: impl Into<String>, input: crate::post::Input) -> Self {
1363 let name = name.into();
1364 if let Some(slot) = self.inputs.iter_mut().find(|(n, _)| *n == name) {
1365 slot.1 = input;
1366 } else {
1367 self.inputs.push((name, input));
1368 }
1369 self
1370 }
1371
1372 pub fn wetness(self, value: f64) -> Self {
1375 self.uniform("wetness", value)
1376 }
1377
1378 pub fn seed(self, value: u64) -> Self {
1380 self.uniform("seed", value as f64)
1381 }
1382
1383 pub fn drop_size(self, value: f64) -> Self {
1385 self.uniform("drop_size", value)
1386 }
1387
1388 fn set_uniform(&mut self, name: String, value: Seed) {
1389 if let Some(slot) = self.uniforms.iter_mut().find(|(n, _)| *n == name) {
1390 slot.1 = value;
1391 } else {
1392 self.uniforms.push((name, value));
1393 }
1394 }
1395}
1396
1397impl NodeBuilder for ShaderBuilder {
1398 type Handle = Shader;
1399 base_accessor!();
1400 fn build(self, graph: &SceneGraph, parent: NodeId) -> Shader {
1401 let (width, height) = if self.fullscreen {
1402 let (w, h) = graph.frame_size();
1403 (Span::Px(w), Span::Px(h))
1404 } else {
1405 (self.width, self.height)
1406 };
1407 let n = graph.next_id();
1408 let uniforms = self
1409 .uniforms
1410 .into_iter()
1411 .map(|(name, seed)| {
1412 let value = match seed {
1413 Seed::Num(v) => crate::shader::UniformValue::Num(
1414 Signal::new(v).with_name(format!("Shader#{n}.{name}")),
1415 ),
1416 Seed::Vec2(x, y) => crate::shader::UniformValue::Vec2(
1417 Signal::new(Vec2::new(x, y)).with_name(format!("Shader#{n}.{name}")),
1418 ),
1419 Seed::Color(c) => crate::shader::UniformValue::Color(
1420 Signal::new(c).with_name(format!("Shader#{n}.{name}")),
1421 ),
1422 Seed::Bool(v) => crate::shader::UniformValue::Bool(
1423 Signal::new(if v { 1.0 } else { 0.0 })
1424 .with_name(format!("Shader#{n}.{name}")),
1425 ),
1426 };
1427 crate::shader::Uniform { name, value }
1428 })
1429 .collect();
1430 let kind = NodeKind::Shader {
1431 code: self.code,
1432 preset: self.preset,
1433 width: place::span_signal(graph, parent, Axis::X, width, format!("Shader#{n}.width")),
1434 height: place::span_signal(
1435 graph,
1436 parent,
1437 Axis::Y,
1438 height,
1439 format!("Shader#{n}.height"),
1440 ),
1441 uniforms,
1442 inputs: self.inputs,
1443 };
1444 Shader::from_id(insert(graph, parent, kind, self.base))
1445 }
1446}
1447
1448pub fn waves() -> ShaderBuilder {
1450 ShaderBuilder::preset()
1451}
1452
1453pub fn lens_drops() -> ShaderBuilder {
1460 let mut s = ShaderBuilder::new(crate::shader::ShaderCode::Wgsl(String::new()));
1461 s.preset = crate::shader::Preset::LensDrops;
1462 s.uniform("wetness", 0.6)
1463 .uniform("seed", 0.0)
1464 .uniform("drop_size", 1.0)
1465}
1466
1467#[must_use = "an effect does nothing until it is passed to `effect`"]
1471pub struct EffectBuilder {
1472 kind: crate::post::EffectKind,
1473 strength: f64,
1474 speed: f64,
1475}
1476
1477impl EffectBuilder {
1478 pub fn strength(mut self, value: f64) -> Self {
1480 self.strength = value;
1481 self
1482 }
1483
1484 pub fn speed(mut self, value: f64) -> Self {
1486 self.speed = value;
1487 self
1488 }
1489
1490 pub(crate) fn build(self, id: NodeId) -> crate::post::EffectData {
1491 crate::post::EffectData {
1492 kind: self.kind,
1493 strength: Signal::new(self.strength).with_name(format!("Effect#{}.strength", id.0)),
1494 speed: Signal::new(self.speed).with_name(format!("Effect#{}.speed", id.0)),
1495 }
1496 }
1497}
1498
1499pub fn ripple() -> EffectBuilder {
1504 EffectBuilder {
1505 kind: crate::post::EffectKind::Ripple,
1506 strength: 1.0,
1507 speed: 6.0,
1508 }
1509}