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 Circle, Code, Grid, Group, Img, Layout, Line, Path, Polygon, Rect, Star, Txt,
9};
10use crate::image::ImageSource;
11use crate::text::{CodeTheme, RichSpan};
12
13#[doc(hidden)]
15#[derive(Debug)]
16pub struct BaseProps {
17 pub name: Option<String>,
18 pub x: f64,
19 pub y: f64,
20 pub rotation: f64,
21 pub scale: Vec2,
22 pub opacity: f64,
23 pub fill: Color,
24 pub stroke: Color,
25 pub line_width: f64,
26 pub start: f64,
27 pub end: f64,
28 pub fill_gradient: Option<Gradient>,
29 pub stroke_gradient: Option<Gradient>,
30 pub dash: Vec<f64>,
31 pub dash_offset: f64,
32 pub line_cap: LineCap,
33 pub line_join: LineJoin,
34 pub blur: f64,
35 pub shadow_color: Color,
36 pub shadow_blur: f64,
37 pub shadow_offset: Vec2,
38 pub clip: bool,
39 pub blend: BlendMode,
40 pub z_index: f64,
41 pub anchor: Vec2,
42 pub grow: f64,
43 pub shrink: f64,
44 pub basis: Option<f64>,
45 pub margin: f64,
46 pub align_self: Option<LayoutAlign>,
47 pub children: Vec<ChildBuilder>,
48}
49
50impl Default for BaseProps {
51 fn default() -> Self {
52 Self {
53 name: None,
54 x: 0.0,
55 y: 0.0,
56 rotation: 0.0,
57 scale: Vec2::new(1.0, 1.0),
58 opacity: 1.0,
59 fill: Color::TRANSPARENT,
60 stroke: Color::TRANSPARENT,
61 line_width: 0.0,
62 start: 0.0,
63 end: 1.0,
64 fill_gradient: None,
65 stroke_gradient: None,
66 dash: Vec::new(),
67 dash_offset: 0.0,
68 line_cap: LineCap::Round,
69 line_join: LineJoin::Round,
70 blur: 0.0,
71 shadow_color: Color::TRANSPARENT,
72 shadow_blur: 0.0,
73 shadow_offset: Vec2::ZERO,
74 clip: false,
75 blend: BlendMode::Normal,
76 z_index: 0.0,
77 anchor: Vec2::ZERO,
78 grow: 0.0,
79 shrink: 0.0,
80 basis: None,
81 margin: 0.0,
82 align_self: None,
83 children: Vec::new(),
84 }
85 }
86}
87
88type BuildFn = Box<dyn FnOnce(&SceneGraph, NodeId)>;
90
91pub struct ChildBuilder(BuildFn);
93
94impl std::fmt::Debug for ChildBuilder {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 f.write_str("ChildBuilder")
97 }
98}
99
100pub trait NodeBuilder: Sized + 'static {
102 type Handle;
104
105 #[doc(hidden)]
106 fn base(&mut self) -> &mut BaseProps;
107
108 #[doc(hidden)]
109 fn build(self, graph: &SceneGraph, parent: NodeId) -> Self::Handle;
110
111 fn name(mut self, v: impl Into<String>) -> Self {
113 self.base().name = Some(v.into());
114 self
115 }
116 fn x(mut self, v: f64) -> Self {
118 self.base().x = v;
119 self
120 }
121 fn y(mut self, v: f64) -> Self {
123 self.base().y = v;
124 self
125 }
126 fn position(mut self, v: impl Into<Vec2>) -> Self {
128 let v = v.into();
129 let b = self.base();
130 (b.x, b.y) = (v.x, v.y);
131 self
132 }
133 fn rotation(mut self, v: f64) -> Self {
135 self.base().rotation = v;
136 self
137 }
138 fn scale(mut self, v: f64) -> Self {
140 self.base().scale = Vec2::new(v, v);
141 self
142 }
143 fn scale_xy(mut self, v: impl Into<Vec2>) -> Self {
145 self.base().scale = v.into();
146 self
147 }
148 fn opacity(mut self, v: f64) -> Self {
150 self.base().opacity = v;
151 self
152 }
153 fn fill(mut self, v: Color) -> Self {
155 self.base().fill = v;
156 self
157 }
158 fn stroke(mut self, v: Color) -> Self {
160 self.base().stroke = v;
161 self
162 }
163 fn line_width(mut self, v: f64) -> Self {
165 self.base().line_width = v;
166 self
167 }
168 fn start(mut self, v: f64) -> Self {
170 self.base().start = v;
171 self
172 }
173 fn end(mut self, v: f64) -> Self {
175 self.base().end = v;
176 self
177 }
178 fn fill_gradient(mut self, g: Gradient) -> Self {
181 self.base().fill_gradient = Some(g);
182 self
183 }
184 fn stroke_gradient(mut self, g: Gradient) -> Self {
186 self.base().stroke_gradient = Some(g);
187 self
188 }
189 fn dash(mut self, pattern: impl Into<Vec<f64>>) -> Self {
192 self.base().dash = pattern.into();
193 self
194 }
195 fn dash_offset(mut self, v: f64) -> Self {
197 self.base().dash_offset = v;
198 self
199 }
200 fn line_cap(mut self, v: LineCap) -> Self {
202 self.base().line_cap = v;
203 self
204 }
205 fn line_join(mut self, v: LineJoin) -> Self {
207 self.base().line_join = v;
208 self
209 }
210 fn blur(mut self, v: f64) -> Self {
213 self.base().blur = v;
214 self
215 }
216 fn shadow(mut self, color: Color, blur: f64, offset: impl Into<Vec2>) -> Self {
218 let b = self.base();
219 (b.shadow_color, b.shadow_blur, b.shadow_offset) = (color, blur, offset.into());
220 self
221 }
222 fn glow(self, color: Color, blur: f64) -> Self {
224 self.shadow(color, blur, Vec2::ZERO)
225 }
226 fn clip(mut self, v: bool) -> Self {
228 self.base().clip = v;
229 self
230 }
231 fn blend(mut self, v: BlendMode) -> Self {
233 self.base().blend = v;
234 self
235 }
236 fn z_index(mut self, v: f64) -> Self {
238 self.base().z_index = v;
239 self
240 }
241 fn anchor(mut self, v: impl Into<Vec2>) -> Self {
243 self.base().anchor = v.into();
244 self
245 }
246 fn grow(mut self, v: f64) -> Self {
248 self.base().grow = v;
249 self
250 }
251 fn shrink(mut self, v: f64) -> Self {
253 self.base().shrink = v;
254 self
255 }
256 fn basis(mut self, v: f64) -> Self {
258 self.base().basis = Some(v);
259 self
260 }
261 fn margin(mut self, v: f64) -> Self {
263 self.base().margin = v;
264 self
265 }
266 fn align_self(mut self, v: LayoutAlign) -> Self {
268 self.base().align_self = Some(v);
269 self
270 }
271 fn child<C: NodeBuilder>(mut self, child: C) -> Self {
274 self.base()
275 .children
276 .push(ChildBuilder(Box::new(move |g, parent| {
277 child.build(g, parent);
278 })));
279 self
280 }
281}
282
283pub(crate) fn add<B: NodeBuilder>(parent: NodeId, builder: B) -> B::Handle {
284 with_graph(|g| builder.build(g, parent))
285}
286
287fn insert(graph: &SceneGraph, parent: NodeId, kind: NodeKind, mut base: BaseProps) -> NodeId {
289 let children = std::mem::take(&mut base.children);
290 let id = graph.insert(Some(parent), kind, base);
291 for c in children {
292 (c.0)(graph, id);
293 }
294 id
295}
296
297fn sig<T: Clone + 'static>(graph: &SceneGraph, kind: &str, field: &str, v: T) -> Signal<T> {
298 Signal::new(v).with_name(format!("{kind}#{}.{field}", graph.next_id()))
299}
300
301macro_rules! builder {
302 (
303 $(#[$doc:meta])*
304 $builder:ident => $handle:ident, kind $kind:literal, defaults { $($bf:ident: $bv:expr),* $(,)? },
305 fields { $($field:ident: $ty:ty = $default:expr),* $(,)? }
306 ) => {
307 $(#[$doc])*
308 #[doc = ""]
309 #[doc = concat!("Builder for a [`", stringify!($handle), "`] node: set the initial values, then")]
310 #[doc = "pass it to `add`."]
311 #[derive(Debug)]
312 #[must_use = "a builder does nothing until it is passed to `add`"]
313 pub struct $builder {
314 base: BaseProps,
315 $(pub(crate) $field: $ty,)*
316 }
317
318 impl Default for $builder {
319 fn default() -> Self {
320 Self {
321 base: BaseProps { $($bf: $bv,)* ..Default::default() },
322 $($field: $default,)*
323 }
324 }
325 }
326
327 impl $builder {
328 #[allow(dead_code)]
329 const KIND: &'static str = $kind;
330 }
331 };
332}
333
334macro_rules! setters {
335 ($builder:ident { $($(#[$doc:meta])* $field:ident: $ty:ty),* $(,)? }) => {
336 impl $builder {
337 $(
338 $(#[$doc])*
339 #[doc = ""]
340 #[doc = concat!("Sets the initial `", stringify!($field), "`.")]
341 pub fn $field(mut self, v: impl Into<$ty>) -> Self {
342 self.$field = v.into();
343 self
344 }
345 )*
346 }
347 };
348}
349
350macro_rules! base_accessor {
351 () => {
352 fn base(&mut self) -> &mut BaseProps {
353 &mut self.base
354 }
355 };
356}
357
358builder!(GroupBuilder => Group, kind "Group", defaults {}, fields {});
361
362impl NodeBuilder for GroupBuilder {
363 type Handle = Group;
364 base_accessor!();
365 fn build(self, graph: &SceneGraph, parent: NodeId) -> Group {
366 Group::from_id(insert(graph, parent, NodeKind::Group, self.base))
367 }
368}
369
370builder!(RectBuilder => Rect, kind "Rect", defaults { fill: Color::WHITE },
373 fields { width: f64 = 100.0, height: f64 = 100.0, radius: f64 = 0.0 });
374setters!(RectBuilder {
375 width: f64,
376 height: f64,
377 radius: f64
379});
380
381impl RectBuilder {
382 pub fn size(mut self, v: f64) -> Self {
384 (self.width, self.height) = (v, v);
385 self
386 }
387}
388
389impl NodeBuilder for RectBuilder {
390 type Handle = Rect;
391 base_accessor!();
392 fn build(self, g: &SceneGraph, parent: NodeId) -> Rect {
393 let k = Self::KIND;
394 let kind = NodeKind::Rect {
395 width: sig(g, k, "width", self.width),
396 height: sig(g, k, "height", self.height),
397 radius: sig(g, k, "radius", self.radius),
398 };
399 Rect::from_id(insert(g, parent, kind, self.base))
400 }
401}
402
403builder!(CircleBuilder => Circle, kind "Circle", defaults { fill: Color::WHITE },
406 fields { size: f64 = 100.0, start_angle: f64 = 0.0, end_angle: f64 = 360.0, pie: bool = false });
407setters!(CircleBuilder {
408 size: f64,
410 start_angle: f64,
412 end_angle: f64,
413 pie: bool
415});
416
417impl NodeBuilder for CircleBuilder {
418 type Handle = Circle;
419 base_accessor!();
420 fn build(self, g: &SceneGraph, parent: NodeId) -> Circle {
421 let k = Self::KIND;
422 let kind = NodeKind::Circle {
423 size: sig(g, k, "size", self.size),
424 start_angle: sig(g, k, "start_angle", self.start_angle),
425 end_angle: sig(g, k, "end_angle", self.end_angle),
426 pie: self.pie,
427 };
428 Circle::from_id(insert(g, parent, kind, self.base))
429 }
430}
431
432builder!(PolygonBuilder => Polygon, kind "Polygon", defaults { fill: Color::WHITE },
435 fields { sides: f64 = 6.0, size: f64 = 100.0 });
436setters!(PolygonBuilder {
437 sides: f64,
439 size: f64
441});
442
443impl NodeBuilder for PolygonBuilder {
444 type Handle = Polygon;
445 base_accessor!();
446 fn build(self, g: &SceneGraph, parent: NodeId) -> Polygon {
447 let k = Self::KIND;
448 let kind = NodeKind::Polygon {
449 sides: sig(g, k, "sides", self.sides),
450 size: sig(g, k, "size", self.size),
451 };
452 Polygon::from_id(insert(g, parent, kind, self.base))
453 }
454}
455
456builder!(StarBuilder => Star, kind "Star", defaults { fill: Color::WHITE },
459 fields { points: f64 = 5.0, size: f64 = 100.0, inner: f64 = 0.45 });
460setters!(StarBuilder {
461 points: f64,
462 size: f64,
464 inner: f64
466});
467
468impl NodeBuilder for StarBuilder {
469 type Handle = Star;
470 base_accessor!();
471 fn build(self, g: &SceneGraph, parent: NodeId) -> Star {
472 let k = Self::KIND;
473 let kind = NodeKind::Star {
474 points: sig(g, k, "points", self.points),
475 size: sig(g, k, "size", self.size),
476 inner: sig(g, k, "inner", self.inner),
477 };
478 Star::from_id(insert(g, parent, kind, self.base))
479 }
480}
481
482builder!(PathBuilder => Path, kind "Path", defaults { fill: Color::WHITE },
485 fields { data: BezPath = BezPath::new() });
486setters!(PathBuilder { data: BezPath });
487
488impl PathBuilder {
489 pub fn svg(mut self, d: &str) -> Self {
495 self.data = BezPath::from_svg(d).unwrap_or_else(|e| panic!("invalid SVG path {d:?}: {e}"));
496 self
497 }
498}
499
500impl NodeBuilder for PathBuilder {
501 type Handle = Path;
502 base_accessor!();
503 fn build(self, g: &SceneGraph, parent: NodeId) -> Path {
504 let kind = NodeKind::Path {
505 data: sig(g, Self::KIND, "data", self.data),
506 };
507 Path::from_id(insert(g, parent, kind, self.base))
508 }
509}
510
511builder!(LineBuilder => Line, kind "Line",
514 defaults { stroke: Color::WHITE, line_width: 4.0 },
515 fields { points: Vec<Vec2> = Vec::new(), closed: bool = false, smooth: bool = false,
516 start_arrow: bool = false, end_arrow: bool = false, arrow_size: f64 = 24.0 });
517setters!(LineBuilder {
518 closed: bool,
519 smooth: bool,
521 start_arrow: bool,
522 end_arrow: bool,
523 arrow_size: f64
524});
525
526impl LineBuilder {
527 pub fn points<P: Into<Vec2>>(mut self, pts: impl IntoIterator<Item = P>) -> Self {
529 self.points = pts.into_iter().map(Into::into).collect();
530 self
531 }
532}
533
534impl NodeBuilder for LineBuilder {
535 type Handle = Line;
536 base_accessor!();
537 fn build(self, g: &SceneGraph, parent: NodeId) -> Line {
538 let kind = NodeKind::Line {
539 points: sig(g, Self::KIND, "points", self.points),
540 closed: self.closed,
541 smooth: self.smooth,
542 start_arrow: self.start_arrow,
543 end_arrow: self.end_arrow,
544 arrow_size: sig(g, Self::KIND, "arrow_size", self.arrow_size),
545 };
546 Line::from_id(insert(g, parent, kind, self.base))
547 }
548}
549
550builder!(TxtBuilder => Txt, kind "Txt", defaults { fill: Color::WHITE },
553 fields { text: String = String::new(), spans: Vec<RichSpan> = Vec::new(),
554 style: TextStyle = TextStyle::default() });
555
556impl TxtBuilder {
557 pub fn text(mut self, v: impl Into<String>) -> Self {
559 self.text = v.into();
560 self
561 }
562 pub fn rich(mut self, spans: impl IntoIterator<Item = RichSpan>) -> Self {
564 self.spans = spans.into_iter().collect();
565 self
566 }
567 pub fn font_size(mut self, v: f64) -> Self {
569 self.style.size = v;
570 self
571 }
572 pub fn weight(mut self, v: f64) -> Self {
574 self.style.weight = v;
575 self
576 }
577 pub fn family(mut self, v: impl Into<String>) -> Self {
580 self.style.family = v.into();
581 self
582 }
583 pub fn italic(mut self, v: bool) -> Self {
585 self.style.italic = v;
586 self
587 }
588 pub fn line_height(mut self, v: f64) -> Self {
590 self.style.line_height = v;
591 self
592 }
593 pub fn letter_spacing(mut self, v: f64) -> Self {
595 self.style.letter_spacing = v;
596 self
597 }
598 pub fn align(mut self, v: crate::TextAlign) -> Self {
600 self.style.align = v;
601 self
602 }
603 pub fn max_width(mut self, v: f64) -> Self {
605 self.style.max_width = Some(v);
606 self
607 }
608}
609
610impl NodeBuilder for TxtBuilder {
611 type Handle = Txt;
612 base_accessor!();
613 fn build(self, g: &SceneGraph, parent: NodeId) -> Txt {
614 let k = Self::KIND;
615 let kind = NodeKind::Txt {
616 text: sig(g, k, "text", self.text),
617 spans: sig(g, k, "spans", self.spans),
618 font_size: sig(g, k, "font_size", self.style.size),
619 weight: sig(g, k, "weight", self.style.weight),
620 style: self.style,
621 morph: sig(g, k, "morph", None),
622 morph_t: sig(g, k, "morph_t", 0.0),
623 };
624 Txt::from_id(insert(g, parent, kind, self.base))
625 }
626}
627
628builder!(CodeBuilder => Code, kind "Code", defaults {},
631 fields { code: String = String::new(), lang: oxframe_text::Lang = oxframe_text::Lang::Rust,
632 theme: CodeTheme = CodeTheme::dark(), style: TextStyle = TextStyle {
633 family: oxframe_text::MONO.to_owned(), size: 32.0, line_height: 1.45, ..TextStyle::default()
634 }, focus: Vec2 = Vec2::ZERO, dim: f64 = 0.3 });
635
636impl CodeBuilder {
637 pub fn code(mut self, v: impl Into<String>) -> Self {
639 self.code = v.into();
640 self
641 }
642 pub fn language(mut self, name: &str) -> Self {
645 self.lang = oxframe_text::Lang::parse(name);
646 self
647 }
648 pub fn theme(mut self, t: CodeTheme) -> Self {
650 self.theme = t;
651 self
652 }
653 pub fn font_size(mut self, v: f64) -> Self {
655 self.style.size = v;
656 self
657 }
658 pub fn line_height(mut self, v: f64) -> Self {
660 self.style.line_height = v;
661 self
662 }
663 pub fn focus(mut self, first: usize, last: usize) -> Self {
665 self.focus = Vec2::new(first as f64, last as f64);
666 self
667 }
668 pub fn dim(mut self, v: f64) -> Self {
670 self.dim = v;
671 self
672 }
673}
674
675impl NodeBuilder for CodeBuilder {
676 type Handle = Code;
677 base_accessor!();
678 fn build(self, g: &SceneGraph, parent: NodeId) -> Code {
679 let k = Self::KIND;
680 let kind = NodeKind::Code {
681 code: sig(g, k, "code", self.code),
682 lang: self.lang,
683 theme: self.theme,
684 font_size: sig(g, k, "font_size", self.style.size),
685 style: self.style,
686 focus: sig(g, k, "focus", self.focus),
687 dim: sig(g, k, "dim", self.dim),
688 };
689 Code::from_id(insert(g, parent, kind, self.base))
690 }
691}
692
693builder!(ImgBuilder => Img, kind "Img", defaults {},
696 fields { source: Option<ImageSource> = None, width: Option<f64> = None, height: Option<f64> = None });
697
698impl ImgBuilder {
699 pub fn src(mut self, bytes: &[u8]) -> Self {
706 self.source =
707 Some(ImageSource::decode(bytes).unwrap_or_else(|e| panic!("cannot decode image: {e}")));
708 self
709 }
710
711 #[cfg(not(target_arch = "wasm32"))]
717 pub fn file(self, path: impl AsRef<std::path::Path>) -> Self {
718 let path = path.as_ref();
719 let bytes =
720 std::fs::read(path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
721 self.src(&bytes)
722 }
723
724 pub fn width(mut self, v: f64) -> Self {
726 self.width = Some(v);
727 self
728 }
729
730 pub fn height(mut self, v: f64) -> Self {
732 self.height = Some(v);
733 self
734 }
735}
736
737impl NodeBuilder for ImgBuilder {
738 type Handle = Img;
739 base_accessor!();
740 fn build(self, g: &SceneGraph, parent: NodeId) -> Img {
741 let source = self
742 .source
743 .expect("Img needs a source: call .src(bytes) or .file(path)");
744 let (nw, nh) = source.natural_size();
745 let aspect = if nh > 0.0 { nw / nh } else { 1.0 };
746 let (w, h) = match (self.width, self.height) {
747 (Some(w), Some(h)) => (w, h),
748 (Some(w), None) => (w, w / aspect),
749 (None, Some(h)) => (h * aspect, h),
750 (None, None) => (nw, nh),
751 };
752 let k = Self::KIND;
753 let kind = NodeKind::Img {
754 source,
755 width: sig(g, k, "width", w),
756 height: sig(g, k, "height", h),
757 };
758 Img::from_id(insert(g, parent, kind, self.base))
759 }
760}
761
762builder!(
765 LayoutBuilder => Layout, kind "Layout", defaults {},
768 fields { direction: Direction = Direction::Row, align: LayoutAlign = LayoutAlign::Center,
769 justify: Justify = Justify::Center, wrap: bool = false, columns: Option<u16> = None,
770 gap: f64 = 0.0, padding: f64 = 0.0,
771 width: Option<f64> = None, height: Option<f64> = None, radius: f64 = 0.0 });
772setters!(LayoutBuilder {
773 direction: Direction,
774 align: LayoutAlign,
775 justify: Justify,
776 gap: f64,
777 padding: f64,
778 radius: f64
779});
780
781impl LayoutBuilder {
782 pub fn wrap(mut self, v: bool) -> Self {
784 self.wrap = v;
785 self
786 }
787 pub fn grid(mut self, columns: u16) -> Self {
789 self.columns = Some(columns.max(1));
790 self
791 }
792 pub fn row(self) -> Self {
794 self.direction(Direction::Row)
795 }
796 pub fn column(self) -> Self {
798 self.direction(Direction::Column)
799 }
800 pub fn width(mut self, v: f64) -> Self {
802 self.width = Some(v);
803 self
804 }
805 pub fn height(mut self, v: f64) -> Self {
807 self.height = Some(v);
808 self
809 }
810}
811
812impl NodeBuilder for LayoutBuilder {
813 type Handle = Layout;
814 base_accessor!();
815 fn build(self, g: &SceneGraph, parent: NodeId) -> Layout {
816 let k = Self::KIND;
817 let kind = NodeKind::Layout {
818 direction: self.direction,
819 align: self.align,
820 justify: self.justify,
821 wrap: self.wrap,
822 columns: self.columns,
823 gap: sig(g, k, "gap", self.gap),
824 padding: sig(g, k, "padding", self.padding),
825 width: sig(g, k, "width", self.width),
826 height: sig(g, k, "height", self.height),
827 radius: sig(g, k, "radius", self.radius),
828 };
829 Layout::from_id(insert(g, parent, kind, self.base))
830 }
831}
832
833builder!(GridBuilder => Grid, kind "Grid",
836 defaults { stroke: Color::rgba(1.0, 1.0, 1.0, 0.15), line_width: 1.0 },
837 fields { width: f64 = 1920.0, height: f64 = 1080.0, spacing: f64 = 80.0 });
838setters!(GridBuilder {
839 width: f64,
840 height: f64,
841 spacing: f64
842});
843
844impl NodeBuilder for GridBuilder {
845 type Handle = Grid;
846 base_accessor!();
847 fn build(self, g: &SceneGraph, parent: NodeId) -> Grid {
848 let k = Self::KIND;
849 let kind = NodeKind::Grid {
850 width: sig(g, k, "width", self.width),
851 height: sig(g, k, "height", self.height),
852 spacing: sig(g, k, "spacing", self.spacing),
853 };
854 Grid::from_id(insert(g, parent, kind, self.base))
855 }
856}