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, Grid, Group, Img, Layout, Line, Path, Polygon, Rect, Star, Txt,
9};
10use crate::image::ImageSource;
11use crate::place::{self, Axis, Edge, Place, Side, Span};
12use crate::text::{CodeTheme, RichSpan};
13use crate::theme::{self, Corner, IntoPaint, Len, Step, TypeRole};
14
15#[doc(hidden)]
17#[derive(Debug)]
18pub struct BaseProps {
19 pub name: Option<String>,
20 pub x: f64,
21 pub y: f64,
22 pub rotation: f64,
23 pub scale: Vec2,
24 pub opacity: f64,
25 pub fill: Color,
26 pub fill_explicit: bool,
29 pub stroke: Color,
30 pub line_width: f64,
31 pub start: f64,
32 pub end: f64,
33 pub fill_gradient: Option<Gradient>,
34 pub stroke_gradient: Option<Gradient>,
35 pub dash: Vec<f64>,
36 pub dash_offset: f64,
37 pub line_cap: LineCap,
38 pub line_join: LineJoin,
39 pub blur: f64,
40 pub shadow_color: Color,
41 pub shadow_blur: f64,
42 pub shadow_offset: Vec2,
43 pub clip: bool,
44 pub blend: BlendMode,
45 pub z_index: f64,
46 pub anchor: Vec2,
47 pub grow: f64,
48 pub shrink: f64,
49 pub basis: Option<f64>,
50 pub margin: f64,
51 pub align_self: Option<LayoutAlign>,
52 pub children: Vec<ChildBuilder>,
53 pub place: Option<Place>,
55}
56
57impl Default for BaseProps {
58 fn default() -> Self {
59 Self {
60 name: None,
61 x: 0.0,
62 y: 0.0,
63 rotation: 0.0,
64 scale: Vec2::new(1.0, 1.0),
65 opacity: 1.0,
66 fill: Color::TRANSPARENT,
67 fill_explicit: false,
68 stroke: Color::TRANSPARENT,
69 line_width: 0.0,
70 start: 0.0,
71 end: 1.0,
72 fill_gradient: None,
73 stroke_gradient: None,
74 dash: Vec::new(),
75 dash_offset: 0.0,
76 line_cap: LineCap::Round,
77 line_join: LineJoin::Round,
78 blur: 0.0,
79 shadow_color: Color::TRANSPARENT,
80 shadow_blur: 0.0,
81 shadow_offset: Vec2::ZERO,
82 clip: false,
83 blend: BlendMode::Normal,
84 z_index: 0.0,
85 anchor: Vec2::ZERO,
86 grow: 0.0,
87 shrink: 0.0,
88 basis: None,
89 margin: 0.0,
90 align_self: None,
91 children: Vec::new(),
92 place: None,
93 }
94 }
95}
96
97type BuildFn = Box<dyn FnOnce(&SceneGraph, NodeId)>;
99
100pub struct ChildBuilder(BuildFn);
102
103impl std::fmt::Debug for ChildBuilder {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 f.write_str("ChildBuilder")
106 }
107}
108
109pub trait NodeBuilder: Sized + 'static {
111 type Handle;
113
114 #[doc(hidden)]
115 fn base(&mut self) -> &mut BaseProps;
116
117 #[doc(hidden)]
118 fn build(self, graph: &SceneGraph, parent: NodeId) -> Self::Handle;
119
120 fn name(mut self, v: impl Into<String>) -> Self {
123 self.base().name = Some(v.into());
124 self
125 }
126 fn x(mut self, v: f64) -> Self {
128 self.base().x = v;
129 self
130 }
131 fn y(mut self, v: f64) -> Self {
133 self.base().y = v;
134 self
135 }
136 fn position(mut self, v: impl Into<Vec2>) -> Self {
138 let v = v.into();
139 let b = self.base();
140 (b.x, b.y) = (v.x, v.y);
141 self
142 }
143 fn rotation(mut self, v: f64) -> Self {
145 self.base().rotation = v;
146 self
147 }
148 fn scale(mut self, v: f64) -> Self {
150 self.base().scale = Vec2::new(v, v);
151 self
152 }
153 fn scale_xy(mut self, v: impl Into<Vec2>) -> Self {
155 self.base().scale = v.into();
156 self
157 }
158 fn opacity(mut self, v: f64) -> Self {
160 self.base().opacity = v;
161 self
162 }
163 fn fill(mut self, v: impl IntoPaint) -> Self {
166 let b = self.base();
167 b.fill = v.into_paint();
168 b.fill_explicit = true;
169 self
170 }
171 fn stroke(mut self, v: impl IntoPaint) -> Self {
174 self.base().stroke = v.into_paint();
175 self
176 }
177 fn line_width(mut self, v: f64) -> Self {
179 self.base().line_width = v;
180 self
181 }
182 fn start(mut self, v: f64) -> Self {
184 self.base().start = v;
185 self
186 }
187 fn end(mut self, v: f64) -> Self {
189 self.base().end = v;
190 self
191 }
192 fn fill_gradient(mut self, g: Gradient) -> Self {
195 self.base().fill_gradient = Some(g);
196 self
197 }
198 fn stroke_gradient(mut self, g: Gradient) -> Self {
200 self.base().stroke_gradient = Some(g);
201 self
202 }
203 fn dash(mut self, pattern: impl Into<Vec<f64>>) -> Self {
206 self.base().dash = pattern.into();
207 self
208 }
209 fn dash_offset(mut self, v: f64) -> Self {
211 self.base().dash_offset = v;
212 self
213 }
214 fn line_cap(mut self, v: LineCap) -> Self {
216 self.base().line_cap = v;
217 self
218 }
219 fn line_join(mut self, v: LineJoin) -> Self {
221 self.base().line_join = v;
222 self
223 }
224 fn blur(mut self, v: f64) -> Self {
227 self.base().blur = v;
228 self
229 }
230 fn shadow(mut self, color: impl IntoPaint, blur: f64, offset: impl Into<Vec2>) -> Self {
232 let b = self.base();
233 (b.shadow_color, b.shadow_blur, b.shadow_offset) =
234 (color.into_paint(), blur, offset.into());
235 self
236 }
237 fn elevation(self, step: Step) -> Self {
239 let s = theme::current().shadow(step);
240 self.shadow(s.color, s.blur, s.offset)
241 }
242 fn glow(self, color: impl IntoPaint, blur: f64) -> Self {
244 self.shadow(color, blur, Vec2::ZERO)
245 }
246 fn clip(mut self, v: bool) -> Self {
248 self.base().clip = v;
249 self
250 }
251 fn blend(mut self, v: BlendMode) -> Self {
253 self.base().blend = v;
254 self
255 }
256 fn z_index(mut self, v: f64) -> Self {
258 self.base().z_index = v;
259 self
260 }
261 fn anchor(mut self, v: impl Into<Vec2>) -> Self {
263 self.base().anchor = v.into();
264 self
265 }
266 fn grow(mut self, v: f64) -> Self {
268 self.base().grow = v;
269 self
270 }
271 fn shrink(mut self, v: f64) -> Self {
273 self.base().shrink = v;
274 self
275 }
276 fn basis(mut self, v: f64) -> Self {
278 self.base().basis = Some(v);
279 self
280 }
281 fn margin(mut self, v: impl Len) -> Self {
283 self.base().margin = v.px();
284 self
285 }
286 fn align_self(mut self, v: LayoutAlign) -> Self {
288 self.base().align_self = Some(v);
289 self
290 }
291 fn next_to(mut self, other: impl Into<crate::Node>, side: Side) -> Self {
294 self.base().place = Some(Place::next_to(other.into().id(), side));
295 self
296 }
297 fn align_to(mut self, other: impl Into<crate::Node>, edge: Edge) -> Self {
299 self.base().place = Some(Place::align_to(other.into().id(), edge));
300 self
301 }
302 fn to_edge(mut self, edge: Edge) -> Self {
305 self.base().place = Some(Place::to_edge(edge));
306 self
307 }
308 fn center_in(mut self, other: impl Into<crate::Node>) -> Self {
310 self.base().place = Some(Place::center_in(other.into().id()));
311 self
312 }
313 fn child<C: NodeBuilder>(mut self, child: C) -> Self {
316 self.base()
317 .children
318 .push(ChildBuilder(Box::new(move |g, parent| {
319 child.build(g, parent);
320 })));
321 self
322 }
323}
324
325pub(crate) fn add<B: NodeBuilder>(parent: NodeId, builder: B) -> B::Handle {
326 with_graph(|g| builder.build(g, parent))
327}
328
329fn insert(graph: &SceneGraph, parent: NodeId, kind: NodeKind, mut base: BaseProps) -> NodeId {
331 let children = std::mem::take(&mut base.children);
332 let id = graph.insert(Some(parent), kind, base);
333 for c in children {
334 (c.0)(graph, id);
335 }
336 id
337}
338
339fn sig<T: Clone + 'static>(graph: &SceneGraph, kind: &str, field: &str, v: T) -> Signal<T> {
340 Signal::new(v).with_name(format!("{kind}#{}.{field}", graph.next_id()))
341}
342
343macro_rules! builder {
344 (
345 $(#[$doc:meta])*
346 $builder:ident => $handle:ident, kind $kind:literal, defaults { $($bf:ident: $bv:expr),* $(,)? },
347 fields { $($field:ident: $ty:ty = $default:expr),* $(,)? }
348 ) => {
349 $(#[$doc])*
350 #[doc = ""]
351 #[doc = concat!("Builder for a [`", stringify!($handle), "`] node: set the initial values, then")]
352 #[doc = "pass it to `add`."]
353 #[derive(Debug)]
354 #[must_use = "a builder does nothing until it is passed to `add`"]
355 pub struct $builder {
356 base: BaseProps,
357 $(pub(crate) $field: $ty,)*
358 }
359
360 impl Default for $builder {
361 fn default() -> Self {
362 Self {
363 base: BaseProps { $($bf: $bv,)* ..Default::default() },
364 $($field: $default,)*
365 }
366 }
367 }
368
369 impl $builder {
370 #[allow(dead_code)]
371 const KIND: &'static str = $kind;
372 }
373 };
374}
375
376macro_rules! setters {
377 ($builder:ident { $($(#[$doc:meta])* $field:ident: $ty:ty),* $(,)? }) => {
378 impl $builder {
379 $(
380 $(#[$doc])*
381 #[doc = ""]
382 #[doc = concat!("Sets the initial `", stringify!($field), "`.")]
383 pub fn $field(mut self, v: impl Into<$ty>) -> Self {
384 self.$field = v.into();
385 self
386 }
387 )*
388 }
389 };
390}
391
392macro_rules! base_accessor {
393 () => {
394 fn base(&mut self) -> &mut BaseProps {
395 &mut self.base
396 }
397 };
398}
399
400builder!(GroupBuilder => Group, kind "Group", defaults {}, fields {});
403
404impl NodeBuilder for GroupBuilder {
405 type Handle = Group;
406 base_accessor!();
407 fn build(self, graph: &SceneGraph, parent: NodeId) -> Group {
408 Group::from_id(insert(graph, parent, NodeKind::Group, self.base))
409 }
410}
411
412builder!(
415 CameraBuilder => Camera, kind "Camera", defaults {}, fields { zoom: f64 = 1.0 });
420setters!(CameraBuilder {
421 zoom: f64
423});
424
425impl NodeBuilder for CameraBuilder {
426 type Handle = Camera;
427 base_accessor!();
428 fn build(self, g: &SceneGraph, parent: NodeId) -> Camera {
429 let kind = NodeKind::Camera {
430 zoom: sig(g, Self::KIND, "zoom", self.zoom),
431 };
432 Camera::from_id(insert(g, parent, kind, self.base))
433 }
434}
435
436builder!(RectBuilder => Rect, kind "Rect", defaults { fill: Color::WHITE, fill_explicit: true },
439 fields { width: Span = Span::Px(100.0), height: Span = Span::Px(100.0), radius: f64 = 0.0 });
440
441impl RectBuilder {
442 pub fn width(mut self, v: impl Into<Span>) -> Self {
444 self.width = v.into();
445 self
446 }
447 pub fn height(mut self, v: impl Into<Span>) -> Self {
449 self.height = v.into();
450 self
451 }
452 pub fn radius(mut self, v: impl Corner) -> Self {
455 self.radius = v.radius();
456 self
457 }
458 pub fn size(mut self, v: f64) -> Self {
460 (self.width, self.height) = (Span::Px(v), Span::Px(v));
461 self
462 }
463}
464
465impl NodeBuilder for RectBuilder {
466 type Handle = Rect;
467 base_accessor!();
468 fn build(self, g: &SceneGraph, parent: NodeId) -> Rect {
469 let k = Self::KIND;
470 let n = g.next_id();
471 let kind = NodeKind::Rect {
472 width: place::span_signal(g, parent, Axis::X, self.width, format!("{k}#{n}.width")),
473 height: place::span_signal(g, parent, Axis::Y, self.height, format!("{k}#{n}.height")),
474 radius: sig(g, k, "radius", self.radius),
475 };
476 Rect::from_id(insert(g, parent, kind, self.base))
477 }
478}
479
480builder!(CircleBuilder => Circle, kind "Circle", defaults { fill: Color::WHITE, fill_explicit: true },
483 fields { size: f64 = 100.0, start_angle: f64 = 0.0, end_angle: f64 = 360.0, pie: bool = false });
484setters!(CircleBuilder {
485 size: f64,
487 start_angle: f64,
489 end_angle: f64,
490 pie: bool
492});
493
494impl NodeBuilder for CircleBuilder {
495 type Handle = Circle;
496 base_accessor!();
497 fn build(self, g: &SceneGraph, parent: NodeId) -> Circle {
498 let k = Self::KIND;
499 let kind = NodeKind::Circle {
500 size: sig(g, k, "size", self.size),
501 start_angle: sig(g, k, "start_angle", self.start_angle),
502 end_angle: sig(g, k, "end_angle", self.end_angle),
503 pie: self.pie,
504 };
505 Circle::from_id(insert(g, parent, kind, self.base))
506 }
507}
508
509builder!(PolygonBuilder => Polygon, kind "Polygon", defaults { fill: Color::WHITE, fill_explicit: true },
512 fields { sides: f64 = 6.0, size: f64 = 100.0 });
513setters!(PolygonBuilder {
514 sides: f64,
516 size: f64
518});
519
520impl NodeBuilder for PolygonBuilder {
521 type Handle = Polygon;
522 base_accessor!();
523 fn build(self, g: &SceneGraph, parent: NodeId) -> Polygon {
524 let k = Self::KIND;
525 let kind = NodeKind::Polygon {
526 sides: sig(g, k, "sides", self.sides),
527 size: sig(g, k, "size", self.size),
528 };
529 Polygon::from_id(insert(g, parent, kind, self.base))
530 }
531}
532
533builder!(StarBuilder => Star, kind "Star", defaults { fill: Color::WHITE, fill_explicit: true },
536 fields { points: f64 = 5.0, size: f64 = 100.0, inner: f64 = 0.45 });
537setters!(StarBuilder {
538 points: f64,
539 size: f64,
541 inner: f64
543});
544
545impl NodeBuilder for StarBuilder {
546 type Handle = Star;
547 base_accessor!();
548 fn build(self, g: &SceneGraph, parent: NodeId) -> Star {
549 let k = Self::KIND;
550 let kind = NodeKind::Star {
551 points: sig(g, k, "points", self.points),
552 size: sig(g, k, "size", self.size),
553 inner: sig(g, k, "inner", self.inner),
554 };
555 Star::from_id(insert(g, parent, kind, self.base))
556 }
557}
558
559builder!(PathBuilder => Path, kind "Path", defaults { fill: Color::WHITE, fill_explicit: true },
562 fields { data: BezPath = BezPath::new() });
563setters!(PathBuilder { data: BezPath });
564
565impl PathBuilder {
566 pub fn svg(mut self, d: &str) -> Self {
572 self.data = BezPath::from_svg(d).unwrap_or_else(|e| panic!("invalid SVG path {d:?}: {e}"));
573 self
574 }
575}
576
577impl NodeBuilder for PathBuilder {
578 type Handle = Path;
579 base_accessor!();
580 fn build(self, g: &SceneGraph, parent: NodeId) -> Path {
581 let kind = NodeKind::Path {
582 data: sig(g, Self::KIND, "data", self.data),
583 };
584 Path::from_id(insert(g, parent, kind, self.base))
585 }
586}
587
588builder!(LineBuilder => Line, kind "Line",
591 defaults { stroke: Color::WHITE, line_width: 4.0 },
592 fields { points: Vec<Vec2> = Vec::new(), closed: bool = false, smooth: bool = false,
593 start_arrow: bool = false, end_arrow: bool = false, arrow_size: f64 = 24.0 });
594setters!(LineBuilder {
595 closed: bool,
596 smooth: bool,
598 start_arrow: bool,
599 end_arrow: bool,
600 arrow_size: f64
601});
602
603impl LineBuilder {
604 pub fn points<P: Into<Vec2>>(mut self, pts: impl IntoIterator<Item = P>) -> Self {
606 self.points = pts.into_iter().map(Into::into).collect();
607 self
608 }
609}
610
611impl NodeBuilder for LineBuilder {
612 type Handle = Line;
613 base_accessor!();
614 fn build(self, g: &SceneGraph, parent: NodeId) -> Line {
615 let kind = NodeKind::Line {
616 points: sig(g, Self::KIND, "points", self.points),
617 closed: self.closed,
618 smooth: self.smooth,
619 start_arrow: self.start_arrow,
620 end_arrow: self.end_arrow,
621 arrow_size: sig(g, Self::KIND, "arrow_size", self.arrow_size),
622 };
623 Line::from_id(insert(g, parent, kind, self.base))
624 }
625}
626
627builder!(TxtBuilder => Txt, kind "Txt", defaults { fill: Color::WHITE },
630 fields { text: String = String::new(), spans: Vec<RichSpan> = Vec::new(),
631 style: TextStyle = TextStyle::default() });
632
633impl TxtBuilder {
634 pub fn text(mut self, v: impl Into<String>) -> Self {
636 self.text = v.into();
637 self
638 }
639 pub fn rich(mut self, spans: impl IntoIterator<Item = RichSpan>) -> Self {
641 self.spans = spans.into_iter().collect();
642 self
643 }
644 pub fn font_size(mut self, v: f64) -> Self {
646 self.style.size = v;
647 self
648 }
649 pub fn weight(mut self, v: f64) -> Self {
651 self.style.weight = v;
652 self
653 }
654 pub fn family(mut self, v: impl Into<String>) -> Self {
657 self.style.family = v.into();
658 self
659 }
660 pub fn italic(mut self, v: bool) -> Self {
662 self.style.italic = v;
663 self
664 }
665 pub fn line_height(mut self, v: f64) -> Self {
667 self.style.line_height = v;
668 self
669 }
670 pub fn letter_spacing(mut self, v: f64) -> Self {
672 self.style.letter_spacing = v;
673 self
674 }
675 pub fn align(mut self, v: crate::TextAlign) -> Self {
677 self.style.align = v;
678 self
679 }
680 pub fn max_width(mut self, v: f64) -> Self {
682 self.style.max_width = Some(v);
683 self
684 }
685 pub fn display(self) -> Self {
687 self.role(TypeRole::Display)
688 }
689 pub fn h1(self) -> Self {
691 self.role(TypeRole::H1)
692 }
693 pub fn h2(self) -> Self {
695 self.role(TypeRole::H2)
696 }
697 pub fn body(self) -> Self {
699 self.role(TypeRole::Body)
700 }
701 pub fn caption(self) -> Self {
703 self.role(TypeRole::Caption)
704 }
705 pub fn code(self) -> Self {
707 self.family(crate::MONO).role(TypeRole::Code)
708 }
709
710 fn role(mut self, role: TypeRole) -> Self {
711 let r = theme::current().role(role);
712 self.style.size = r.size;
713 self.style.weight = r.weight;
714 self.style.line_height = r.line_height;
715 self
716 }
717}
718
719impl NodeBuilder for TxtBuilder {
720 type Handle = Txt;
721 base_accessor!();
722 fn build(mut self, g: &SceneGraph, parent: NodeId) -> Txt {
723 if !self.base.fill_explicit {
724 self.base.fill = theme::current().text;
725 }
726 let k = Self::KIND;
727 let kind = NodeKind::Txt {
728 text: sig(g, k, "text", self.text),
729 spans: sig(g, k, "spans", self.spans),
730 font_size: sig(g, k, "font_size", self.style.size),
731 weight: sig(g, k, "weight", self.style.weight),
732 style: self.style,
733 morph: sig(g, k, "morph", None),
734 morph_t: sig(g, k, "morph_t", 0.0),
735 };
736 Txt::from_id(insert(g, parent, kind, self.base))
737 }
738}
739
740builder!(CodeBuilder => Code, kind "Code", defaults {},
743 fields { code: String = String::new(), lang: oxframe_text::Lang = oxframe_text::Lang::Rust,
744 theme: CodeTheme = CodeTheme::dark(), theme_set: bool = false, style: TextStyle = TextStyle {
745 family: oxframe_text::MONO.to_owned(), size: 32.0, line_height: 1.45, ..TextStyle::default()
746 }, focus: Vec2 = Vec2::ZERO, dim: f64 = 0.3 });
747
748impl CodeBuilder {
749 pub fn code(mut self, v: impl Into<String>) -> Self {
751 self.code = v.into();
752 self
753 }
754 pub fn language(mut self, name: &str) -> Self {
757 self.lang = oxframe_text::Lang::parse(name);
758 self
759 }
760 pub fn theme(mut self, t: CodeTheme) -> Self {
763 self.theme = t;
764 self.theme_set = true;
765 self
766 }
767 pub fn font_size(mut self, v: f64) -> Self {
769 self.style.size = v;
770 self
771 }
772 pub fn line_height(mut self, v: f64) -> Self {
774 self.style.line_height = v;
775 self
776 }
777 pub fn focus(mut self, first: usize, last: usize) -> Self {
779 self.focus = Vec2::new(first as f64, last as f64);
780 self
781 }
782 pub fn dim(mut self, v: f64) -> Self {
784 self.dim = v;
785 self
786 }
787}
788
789impl NodeBuilder for CodeBuilder {
790 type Handle = Code;
791 base_accessor!();
792 fn build(self, g: &SceneGraph, parent: NodeId) -> Code {
793 let k = Self::KIND;
794 let theme = if self.theme_set {
795 self.theme
796 } else if theme::current().is_light() {
797 CodeTheme::light()
798 } else {
799 CodeTheme::dark()
800 };
801 let kind = NodeKind::Code {
802 code: sig(g, k, "code", self.code),
803 lang: self.lang,
804 theme,
805 font_size: sig(g, k, "font_size", self.style.size),
806 style: self.style,
807 focus: sig(g, k, "focus", self.focus),
808 dim: sig(g, k, "dim", self.dim),
809 };
810 Code::from_id(insert(g, parent, kind, self.base))
811 }
812}
813
814builder!(ImgBuilder => Img, kind "Img", defaults {},
817 fields { source: Option<ImageSource> = None, width: Option<f64> = None, height: Option<f64> = None });
818
819impl ImgBuilder {
820 pub fn src(mut self, bytes: &[u8]) -> Self {
827 self.source =
828 Some(ImageSource::decode(bytes).unwrap_or_else(|e| panic!("cannot decode image: {e}")));
829 self
830 }
831
832 #[cfg(not(target_arch = "wasm32"))]
838 pub fn file(self, path: impl AsRef<std::path::Path>) -> Self {
839 let path = path.as_ref();
840 let bytes =
841 std::fs::read(path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
842 self.src(&bytes)
843 }
844
845 pub fn width(mut self, v: f64) -> Self {
847 self.width = Some(v);
848 self
849 }
850
851 pub fn height(mut self, v: f64) -> Self {
853 self.height = Some(v);
854 self
855 }
856}
857
858impl NodeBuilder for ImgBuilder {
859 type Handle = Img;
860 base_accessor!();
861 fn build(self, g: &SceneGraph, parent: NodeId) -> Img {
862 let source = self
863 .source
864 .expect("Img needs a source: call .src(bytes) or .file(path)");
865 let (nw, nh) = source.natural_size();
866 let aspect = if nh > 0.0 { nw / nh } else { 1.0 };
867 let (w, h) = match (self.width, self.height) {
868 (Some(w), Some(h)) => (w, h),
869 (Some(w), None) => (w, w / aspect),
870 (None, Some(h)) => (h * aspect, h),
871 (None, None) => (nw, nh),
872 };
873 let k = Self::KIND;
874 let kind = NodeKind::Img {
875 source,
876 width: sig(g, k, "width", w),
877 height: sig(g, k, "height", h),
878 };
879 Img::from_id(insert(g, parent, kind, self.base))
880 }
881}
882
883builder!(
886 LayoutBuilder => Layout, kind "Layout", defaults {},
889 fields { direction: Direction = Direction::Row, align: LayoutAlign = LayoutAlign::Center,
890 justify: Justify = Justify::Center, wrap: bool = false, columns: Option<u16> = None,
891 gap: f64 = 0.0, padding: f64 = 0.0,
892 width: Option<Span> = None, height: Option<Span> = None, radius: f64 = 0.0 });
893setters!(LayoutBuilder {
894 direction: Direction,
895 align: LayoutAlign,
896 justify: Justify
897});
898
899impl LayoutBuilder {
900 pub fn gap(mut self, v: impl Len) -> Self {
902 self.gap = v.px();
903 self
904 }
905 pub fn padding(mut self, v: impl Len) -> Self {
907 self.padding = v.px();
908 self
909 }
910 pub fn radius(mut self, v: impl Corner) -> Self {
912 self.radius = v.radius();
913 self
914 }
915}
916
917impl LayoutBuilder {
918 pub fn wrap(mut self, v: bool) -> Self {
920 self.wrap = v;
921 self
922 }
923 pub fn grid(mut self, columns: u16) -> Self {
925 self.columns = Some(columns.max(1));
926 self
927 }
928 pub fn row(self) -> Self {
930 self.direction(Direction::Row)
931 }
932 pub fn column(self) -> Self {
934 self.direction(Direction::Column)
935 }
936 pub fn width(mut self, v: impl Into<Span>) -> Self {
938 self.width = Some(v.into());
939 self
940 }
941 pub fn height(mut self, v: impl Into<Span>) -> Self {
943 self.height = Some(v.into());
944 self
945 }
946}
947
948impl NodeBuilder for LayoutBuilder {
949 type Handle = Layout;
950 base_accessor!();
951 fn build(self, g: &SceneGraph, parent: NodeId) -> Layout {
952 let k = Self::KIND;
953 let kind = NodeKind::Layout {
954 direction: self.direction,
955 align: self.align,
956 justify: self.justify,
957 wrap: self.wrap,
958 columns: self.columns,
959 gap: sig(g, k, "gap", self.gap),
960 padding: sig(g, k, "padding", self.padding),
961 width: place::span_option(
962 g,
963 parent,
964 Axis::X,
965 self.width,
966 format!("{k}#{}.width", g.next_id()),
967 ),
968 height: place::span_option(
969 g,
970 parent,
971 Axis::Y,
972 self.height,
973 format!("{k}#{}.height", g.next_id()),
974 ),
975 radius: sig(g, k, "radius", self.radius),
976 };
977 Layout::from_id(insert(g, parent, kind, self.base))
978 }
979}
980
981builder!(GridBuilder => Grid, kind "Grid",
984 defaults { stroke: Color::rgba(1.0, 1.0, 1.0, 0.15), line_width: 1.0 },
985 fields { width: f64 = 1920.0, height: f64 = 1080.0, spacing: f64 = 80.0 });
986setters!(GridBuilder {
987 width: f64,
988 height: f64,
989 spacing: f64
990});
991
992impl NodeBuilder for GridBuilder {
993 type Handle = Grid;
994 base_accessor!();
995 fn build(self, g: &SceneGraph, parent: NodeId) -> Grid {
996 let k = Self::KIND;
997 let kind = NodeKind::Grid {
998 width: sig(g, k, "width", self.width),
999 height: sig(g, k, "height", self.height),
1000 spacing: sig(g, k, "spacing", self.spacing),
1001 };
1002 Grid::from_id(insert(g, parent, kind, self.base))
1003 }
1004}