use kurbo::{BezPath, Vec2};
use oxframe_core::{BlendMode, Color, Gradient, Signal};
use oxframe_text::TextStyle;
use crate::draw::{LineCap, LineJoin};
use crate::graph::{Direction, Justify, LayoutAlign, NodeId, NodeKind, SceneGraph, with_graph};
use crate::handles::{
Camera, Circle, Code, Custom, Grid, Group, Img, Layout, Line, Particles, Path, Polygon, Rect,
Shader, Star, Txt,
};
use crate::image::ImageSource;
use crate::place::{self, Axis, Edge, Place, Side, Span};
use crate::text::{CodeTheme, RichSpan};
use crate::theme::{self, Corner, IntoPaint, Len, Step, TypeRole};
#[doc(hidden)]
#[derive(Debug)]
pub struct BaseProps {
pub name: Option<String>,
pub x: f64,
pub y: f64,
pub rotation: f64,
pub scale: Vec2,
pub opacity: f64,
pub fill: Color,
pub fill_explicit: bool,
pub stroke: Color,
pub line_width: f64,
pub start: f64,
pub end: f64,
pub fill_gradient: Option<Gradient>,
pub stroke_gradient: Option<Gradient>,
pub dash: Vec<f64>,
pub dash_offset: f64,
pub line_cap: LineCap,
pub line_join: LineJoin,
pub blur: f64,
pub shadow_color: Color,
pub shadow_blur: f64,
pub shadow_offset: Vec2,
pub clip: bool,
pub blend: BlendMode,
pub z_index: f64,
pub anchor: Vec2,
pub grow: f64,
pub shrink: f64,
pub basis: Option<f64>,
pub margin: f64,
pub align_self: Option<LayoutAlign>,
pub children: Vec<ChildBuilder>,
pub place: Option<Place>,
}
impl Default for BaseProps {
fn default() -> Self {
Self {
name: None,
x: 0.0,
y: 0.0,
rotation: 0.0,
scale: Vec2::new(1.0, 1.0),
opacity: 1.0,
fill: Color::TRANSPARENT,
fill_explicit: false,
stroke: Color::TRANSPARENT,
line_width: 0.0,
start: 0.0,
end: 1.0,
fill_gradient: None,
stroke_gradient: None,
dash: Vec::new(),
dash_offset: 0.0,
line_cap: LineCap::Round,
line_join: LineJoin::Round,
blur: 0.0,
shadow_color: Color::TRANSPARENT,
shadow_blur: 0.0,
shadow_offset: Vec2::ZERO,
clip: false,
blend: BlendMode::Normal,
z_index: 0.0,
anchor: Vec2::ZERO,
grow: 0.0,
shrink: 0.0,
basis: None,
margin: 0.0,
align_self: None,
children: Vec::new(),
place: None,
}
}
}
type BuildFn = Box<dyn FnOnce(&SceneGraph, NodeId)>;
pub struct ChildBuilder(BuildFn);
impl std::fmt::Debug for ChildBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("ChildBuilder")
}
}
pub trait NodeBuilder: Sized + 'static {
type Handle;
#[doc(hidden)]
fn base(&mut self) -> &mut BaseProps;
#[doc(hidden)]
fn build(self, graph: &SceneGraph, parent: NodeId) -> Self::Handle;
fn name(mut self, v: impl Into<String>) -> Self {
self.base().name = Some(v.into());
self
}
fn x(mut self, v: f64) -> Self {
self.base().x = v;
self
}
fn y(mut self, v: f64) -> Self {
self.base().y = v;
self
}
fn position(mut self, v: impl Into<Vec2>) -> Self {
let v = v.into();
let b = self.base();
(b.x, b.y) = (v.x, v.y);
self
}
fn rotation(mut self, v: f64) -> Self {
self.base().rotation = v;
self
}
fn scale(mut self, v: f64) -> Self {
self.base().scale = Vec2::new(v, v);
self
}
fn scale_xy(mut self, v: impl Into<Vec2>) -> Self {
self.base().scale = v.into();
self
}
fn opacity(mut self, v: f64) -> Self {
self.base().opacity = v;
self
}
fn fill(mut self, v: impl IntoPaint) -> Self {
let b = self.base();
b.fill = v.into_paint();
b.fill_explicit = true;
self
}
fn stroke(mut self, v: impl IntoPaint) -> Self {
self.base().stroke = v.into_paint();
self
}
fn line_width(mut self, v: f64) -> Self {
self.base().line_width = v;
self
}
fn start(mut self, v: f64) -> Self {
self.base().start = v;
self
}
fn end(mut self, v: f64) -> Self {
self.base().end = v;
self
}
fn fill_gradient(mut self, g: Gradient) -> Self {
self.base().fill_gradient = Some(g);
self
}
fn stroke_gradient(mut self, g: Gradient) -> Self {
self.base().stroke_gradient = Some(g);
self
}
fn dash(mut self, pattern: impl Into<Vec<f64>>) -> Self {
self.base().dash = pattern.into();
self
}
fn dash_offset(mut self, v: f64) -> Self {
self.base().dash_offset = v;
self
}
fn line_cap(mut self, v: LineCap) -> Self {
self.base().line_cap = v;
self
}
fn line_join(mut self, v: LineJoin) -> Self {
self.base().line_join = v;
self
}
fn blur(mut self, v: f64) -> Self {
self.base().blur = v;
self
}
fn shadow(mut self, color: impl IntoPaint, blur: f64, offset: impl Into<Vec2>) -> Self {
let b = self.base();
(b.shadow_color, b.shadow_blur, b.shadow_offset) =
(color.into_paint(), blur, offset.into());
self
}
fn elevation(self, step: Step) -> Self {
let s = theme::current().shadow(step);
self.shadow(s.color, s.blur, s.offset)
}
fn glow(self, color: impl IntoPaint, blur: f64) -> Self {
self.shadow(color, blur, Vec2::ZERO)
}
fn clip(mut self, v: bool) -> Self {
self.base().clip = v;
self
}
fn blend(mut self, v: BlendMode) -> Self {
self.base().blend = v;
self
}
fn z_index(mut self, v: f64) -> Self {
self.base().z_index = v;
self
}
fn anchor(mut self, v: impl Into<Vec2>) -> Self {
self.base().anchor = v.into();
self
}
fn grow(mut self, v: f64) -> Self {
self.base().grow = v;
self
}
fn shrink(mut self, v: f64) -> Self {
self.base().shrink = v;
self
}
fn basis(mut self, v: f64) -> Self {
self.base().basis = Some(v);
self
}
fn margin(mut self, v: impl Len) -> Self {
self.base().margin = v.px();
self
}
fn align_self(mut self, v: LayoutAlign) -> Self {
self.base().align_self = Some(v);
self
}
fn next_to(mut self, other: impl Into<crate::Node>, side: Side) -> Self {
self.base().place = Some(Place::next_to(other.into().id(), side));
self
}
fn align_to(mut self, other: impl Into<crate::Node>, edge: Edge) -> Self {
self.base().place = Some(Place::align_to(other.into().id(), edge));
self
}
fn to_edge(mut self, edge: Edge) -> Self {
self.base().place = Some(Place::to_edge(edge));
self
}
fn center_in(mut self, other: impl Into<crate::Node>) -> Self {
self.base().place = Some(Place::center_in(other.into().id()));
self
}
fn child<C: NodeBuilder>(mut self, child: C) -> Self {
self.base()
.children
.push(ChildBuilder(Box::new(move |g, parent| {
child.build(g, parent);
})));
self
}
}
pub(crate) fn add<B: NodeBuilder>(parent: NodeId, builder: B) -> B::Handle {
with_graph(|g| builder.build(g, parent))
}
fn insert(graph: &SceneGraph, parent: NodeId, kind: NodeKind, mut base: BaseProps) -> NodeId {
let children = std::mem::take(&mut base.children);
let id = graph.insert(Some(parent), kind, base);
for c in children {
(c.0)(graph, id);
}
id
}
fn sig<T: Clone + 'static>(graph: &SceneGraph, kind: &str, field: &str, v: T) -> Signal<T> {
Signal::new(v).with_name(format!("{kind}#{}.{field}", graph.next_id()))
}
macro_rules! builder {
(
$(#[$doc:meta])*
$builder:ident => $handle:ident, kind $kind:literal, defaults { $($bf:ident: $bv:expr),* $(,)? },
fields { $($field:ident: $ty:ty = $default:expr),* $(,)? }
) => {
$(#[$doc])*
#[doc = ""]
#[doc = concat!("Builder for a [`", stringify!($handle), "`] node: set the initial values, then")]
#[doc = "pass it to `add`."]
#[derive(Debug)]
#[must_use = "a builder does nothing until it is passed to `add`"]
pub struct $builder {
base: BaseProps,
$(pub(crate) $field: $ty,)*
}
impl Default for $builder {
fn default() -> Self {
Self {
base: BaseProps { $($bf: $bv,)* ..Default::default() },
$($field: $default,)*
}
}
}
impl $builder {
#[allow(dead_code)]
const KIND: &'static str = $kind;
}
};
}
macro_rules! setters {
($builder:ident { $($(#[$doc:meta])* $field:ident: $ty:ty),* $(,)? }) => {
impl $builder {
$(
$(#[$doc])*
#[doc = ""]
#[doc = concat!("Sets the initial `", stringify!($field), "`.")]
pub fn $field(mut self, v: impl Into<$ty>) -> Self {
self.$field = v.into();
self
}
)*
}
};
}
macro_rules! base_accessor {
() => {
fn base(&mut self) -> &mut BaseProps {
&mut self.base
}
};
}
builder!(GroupBuilder => Group, kind "Group", defaults {}, fields {});
impl NodeBuilder for GroupBuilder {
type Handle = Group;
base_accessor!();
fn build(self, graph: &SceneGraph, parent: NodeId) -> Group {
Group::from_id(insert(graph, parent, NodeKind::Group, self.base))
}
}
builder!(
CameraBuilder => Camera, kind "Camera", defaults {}, fields { zoom: f64 = 1.0 });
setters!(CameraBuilder {
zoom: f64
});
impl NodeBuilder for CameraBuilder {
type Handle = Camera;
base_accessor!();
fn build(self, g: &SceneGraph, parent: NodeId) -> Camera {
let kind = NodeKind::Camera {
zoom: sig(g, Self::KIND, "zoom", self.zoom),
};
Camera::from_id(insert(g, parent, kind, self.base))
}
}
builder!(RectBuilder => Rect, kind "Rect", defaults { fill: Color::WHITE, fill_explicit: true },
fields { width: Span = Span::Px(100.0), height: Span = Span::Px(100.0), radius: f64 = 0.0 });
impl RectBuilder {
pub fn width(mut self, v: impl Into<Span>) -> Self {
self.width = v.into();
self
}
pub fn height(mut self, v: impl Into<Span>) -> Self {
self.height = v.into();
self
}
pub fn radius(mut self, v: impl Corner) -> Self {
self.radius = v.radius();
self
}
pub fn size(mut self, v: f64) -> Self {
(self.width, self.height) = (Span::Px(v), Span::Px(v));
self
}
}
impl NodeBuilder for RectBuilder {
type Handle = Rect;
base_accessor!();
fn build(self, g: &SceneGraph, parent: NodeId) -> Rect {
let k = Self::KIND;
let n = g.next_id();
let kind = NodeKind::Rect {
width: place::span_signal(g, parent, Axis::X, self.width, format!("{k}#{n}.width")),
height: place::span_signal(g, parent, Axis::Y, self.height, format!("{k}#{n}.height")),
radius: sig(g, k, "radius", self.radius),
};
Rect::from_id(insert(g, parent, kind, self.base))
}
}
builder!(CircleBuilder => Circle, kind "Circle", defaults { fill: Color::WHITE, fill_explicit: true },
fields { size: f64 = 100.0, start_angle: f64 = 0.0, end_angle: f64 = 360.0, pie: bool = false });
setters!(CircleBuilder {
size: f64,
start_angle: f64,
end_angle: f64,
pie: bool
});
impl NodeBuilder for CircleBuilder {
type Handle = Circle;
base_accessor!();
fn build(self, g: &SceneGraph, parent: NodeId) -> Circle {
let k = Self::KIND;
let kind = NodeKind::Circle {
size: sig(g, k, "size", self.size),
start_angle: sig(g, k, "start_angle", self.start_angle),
end_angle: sig(g, k, "end_angle", self.end_angle),
pie: self.pie,
};
Circle::from_id(insert(g, parent, kind, self.base))
}
}
builder!(PolygonBuilder => Polygon, kind "Polygon", defaults { fill: Color::WHITE, fill_explicit: true },
fields { sides: f64 = 6.0, size: f64 = 100.0 });
setters!(PolygonBuilder {
sides: f64,
size: f64
});
impl NodeBuilder for PolygonBuilder {
type Handle = Polygon;
base_accessor!();
fn build(self, g: &SceneGraph, parent: NodeId) -> Polygon {
let k = Self::KIND;
let kind = NodeKind::Polygon {
sides: sig(g, k, "sides", self.sides),
size: sig(g, k, "size", self.size),
};
Polygon::from_id(insert(g, parent, kind, self.base))
}
}
builder!(StarBuilder => Star, kind "Star", defaults { fill: Color::WHITE, fill_explicit: true },
fields { points: f64 = 5.0, size: f64 = 100.0, inner: f64 = 0.45 });
setters!(StarBuilder {
points: f64,
size: f64,
inner: f64
});
impl NodeBuilder for StarBuilder {
type Handle = Star;
base_accessor!();
fn build(self, g: &SceneGraph, parent: NodeId) -> Star {
let k = Self::KIND;
let kind = NodeKind::Star {
points: sig(g, k, "points", self.points),
size: sig(g, k, "size", self.size),
inner: sig(g, k, "inner", self.inner),
};
Star::from_id(insert(g, parent, kind, self.base))
}
}
builder!(PathBuilder => Path, kind "Path", defaults { fill: Color::WHITE, fill_explicit: true },
fields { data: BezPath = BezPath::new() });
setters!(PathBuilder { data: BezPath });
impl PathBuilder {
pub fn svg(mut self, d: &str) -> Self {
self.data = BezPath::from_svg(d).unwrap_or_else(|e| panic!("invalid SVG path {d:?}: {e}"));
self
}
}
impl NodeBuilder for PathBuilder {
type Handle = Path;
base_accessor!();
fn build(self, g: &SceneGraph, parent: NodeId) -> Path {
let kind = NodeKind::Path {
data: sig(g, Self::KIND, "data", self.data),
};
Path::from_id(insert(g, parent, kind, self.base))
}
}
builder!(LineBuilder => Line, kind "Line",
defaults { stroke: Color::WHITE, line_width: 4.0 },
fields { points: Vec<Vec2> = Vec::new(), closed: bool = false, smooth: bool = false,
start_arrow: bool = false, end_arrow: bool = false, arrow_size: f64 = 24.0 });
setters!(LineBuilder {
closed: bool,
smooth: bool,
start_arrow: bool,
end_arrow: bool,
arrow_size: f64
});
impl LineBuilder {
pub fn points<P: Into<Vec2>>(mut self, pts: impl IntoIterator<Item = P>) -> Self {
self.points = pts.into_iter().map(Into::into).collect();
self
}
}
impl NodeBuilder for LineBuilder {
type Handle = Line;
base_accessor!();
fn build(self, g: &SceneGraph, parent: NodeId) -> Line {
let kind = NodeKind::Line {
points: sig(g, Self::KIND, "points", self.points),
closed: self.closed,
smooth: self.smooth,
start_arrow: self.start_arrow,
end_arrow: self.end_arrow,
arrow_size: sig(g, Self::KIND, "arrow_size", self.arrow_size),
};
Line::from_id(insert(g, parent, kind, self.base))
}
}
builder!(TxtBuilder => Txt, kind "Txt", defaults { fill: Color::WHITE },
fields { text: String = String::new(), spans: Vec<RichSpan> = Vec::new(),
style: TextStyle = TextStyle::default() });
impl TxtBuilder {
pub fn text(mut self, v: impl Into<String>) -> Self {
self.text = v.into();
self
}
pub fn rich(mut self, spans: impl IntoIterator<Item = RichSpan>) -> Self {
self.spans = spans.into_iter().collect();
self
}
pub fn font_size(mut self, v: f64) -> Self {
self.style.size = v;
self
}
pub fn weight(mut self, v: f64) -> Self {
self.style.weight = v;
self
}
pub fn family(mut self, v: impl Into<String>) -> Self {
self.style.family = v.into();
self
}
pub fn italic(mut self, v: bool) -> Self {
self.style.italic = v;
self
}
pub fn line_height(mut self, v: f64) -> Self {
self.style.line_height = v;
self
}
pub fn letter_spacing(mut self, v: f64) -> Self {
self.style.letter_spacing = v;
self
}
pub fn align(mut self, v: crate::TextAlign) -> Self {
self.style.align = v;
self
}
pub fn max_width(mut self, v: f64) -> Self {
self.style.max_width = Some(v);
self
}
pub fn display(self) -> Self {
self.role(TypeRole::Display)
}
pub fn h1(self) -> Self {
self.role(TypeRole::H1)
}
pub fn h2(self) -> Self {
self.role(TypeRole::H2)
}
pub fn body(self) -> Self {
self.role(TypeRole::Body)
}
pub fn caption(self) -> Self {
self.role(TypeRole::Caption)
}
pub fn code(self) -> Self {
self.family(crate::MONO).role(TypeRole::Code)
}
fn role(mut self, role: TypeRole) -> Self {
let r = theme::current().role(role);
self.style.size = r.size;
self.style.weight = r.weight;
self.style.line_height = r.line_height;
self
}
}
impl NodeBuilder for TxtBuilder {
type Handle = Txt;
base_accessor!();
fn build(mut self, g: &SceneGraph, parent: NodeId) -> Txt {
if !self.base.fill_explicit {
self.base.fill = theme::current().text;
}
let k = Self::KIND;
let kind = NodeKind::Txt {
text: sig(g, k, "text", self.text),
spans: sig(g, k, "spans", self.spans),
font_size: sig(g, k, "font_size", self.style.size),
weight: sig(g, k, "weight", self.style.weight),
style: self.style,
morph: sig(g, k, "morph", None),
morph_t: sig(g, k, "morph_t", 0.0),
};
Txt::from_id(insert(g, parent, kind, self.base))
}
}
builder!(CodeBuilder => Code, kind "Code", defaults {},
fields { code: String = String::new(), lang: oxframe_text::Lang = oxframe_text::Lang::Rust,
theme: CodeTheme = CodeTheme::dark(), theme_set: bool = false, style: TextStyle = TextStyle {
family: oxframe_text::MONO.to_owned(), size: 32.0, line_height: 1.45, ..TextStyle::default()
}, focus: Vec2 = Vec2::ZERO, dim: f64 = 0.3 });
impl CodeBuilder {
pub fn code(mut self, v: impl Into<String>) -> Self {
self.code = v.into();
self
}
pub fn language(mut self, name: &str) -> Self {
self.lang = oxframe_text::Lang::parse(name);
self
}
pub fn theme(mut self, t: CodeTheme) -> Self {
self.theme = t;
self.theme_set = true;
self
}
pub fn font_size(mut self, v: f64) -> Self {
self.style.size = v;
self
}
pub fn line_height(mut self, v: f64) -> Self {
self.style.line_height = v;
self
}
pub fn focus(mut self, first: usize, last: usize) -> Self {
self.focus = Vec2::new(first as f64, last as f64);
self
}
pub fn dim(mut self, v: f64) -> Self {
self.dim = v;
self
}
}
impl NodeBuilder for CodeBuilder {
type Handle = Code;
base_accessor!();
fn build(self, g: &SceneGraph, parent: NodeId) -> Code {
let k = Self::KIND;
let theme = if self.theme_set {
self.theme
} else if theme::current().is_light() {
CodeTheme::light()
} else {
CodeTheme::dark()
};
let kind = NodeKind::Code {
code: sig(g, k, "code", self.code),
edit_from: sig(g, k, "edit_from", String::new()),
edit_to: sig(g, k, "edit_to", String::new()),
editing: sig(g, k, "editing", false),
edit_t: sig(g, k, "edit_t", 0.0),
edit_flash: sig(g, k, "edit_flash", false),
lang: self.lang,
theme,
font_size: sig(g, k, "font_size", self.style.size),
style: self.style,
focus: sig(g, k, "focus", self.focus),
dim: sig(g, k, "dim", self.dim),
};
Code::from_id(insert(g, parent, kind, self.base))
}
}
builder!(ImgBuilder => Img, kind "Img", defaults {},
fields { source: Option<ImageSource> = None, width: Option<f64> = None, height: Option<f64> = None });
impl ImgBuilder {
pub fn src(mut self, bytes: &[u8]) -> Self {
self.source =
Some(ImageSource::decode(bytes).unwrap_or_else(|e| panic!("cannot decode image: {e}")));
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn file(self, path: impl AsRef<std::path::Path>) -> Self {
let path = path.as_ref();
let bytes =
std::fs::read(path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
self.src(&bytes)
}
pub fn width(mut self, v: f64) -> Self {
self.width = Some(v);
self
}
pub fn height(mut self, v: f64) -> Self {
self.height = Some(v);
self
}
}
impl NodeBuilder for ImgBuilder {
type Handle = Img;
base_accessor!();
fn build(self, g: &SceneGraph, parent: NodeId) -> Img {
let source = self
.source
.expect("Img needs a source: call .src(bytes) or .file(path)");
let (nw, nh) = source.natural_size();
let aspect = if nh > 0.0 { nw / nh } else { 1.0 };
let (w, h) = match (self.width, self.height) {
(Some(w), Some(h)) => (w, h),
(Some(w), None) => (w, w / aspect),
(None, Some(h)) => (h * aspect, h),
(None, None) => (nw, nh),
};
let k = Self::KIND;
let kind = NodeKind::Img {
source,
width: sig(g, k, "width", w),
height: sig(g, k, "height", h),
};
Img::from_id(insert(g, parent, kind, self.base))
}
}
builder!(
SvgBuilder => Svg, kind "Svg", defaults {},
fields { bytes: Vec<u8> = Vec::new(), width: Option<f64> = None, height: Option<f64> = None });
impl SvgBuilder {
pub fn src(mut self, bytes: &[u8]) -> Self {
self.bytes = bytes.to_vec();
self
}
#[cfg(not(target_arch = "wasm32"))]
pub fn file(self, path: impl AsRef<std::path::Path>) -> Self {
let path = path.as_ref();
let bytes =
std::fs::read(path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()));
self.src(&bytes)
}
pub fn width(mut self, v: f64) -> Self {
self.width = Some(v);
self
}
pub fn height(mut self, v: f64) -> Self {
self.height = Some(v);
self
}
}
impl NodeBuilder for SvgBuilder {
type Handle = crate::handles::Svg;
base_accessor!();
fn build(mut self, g: &SceneGraph, parent: NodeId) -> crate::handles::Svg {
if self.bytes.is_empty() {
panic!("Svg needs a source: call .src(bytes) or .file(path)");
}
let tree = usvg::Tree::from_data(&self.bytes, &usvg::Options::default())
.unwrap_or_else(|e| panic!("cannot parse svg: {e}"));
let size = tree.size();
let (nw, nh) = (f64::from(size.width()), f64::from(size.height()));
let aspect = if nh > 0.0 { nw / nh } else { 1.0 };
let (w, h) = match (self.width, self.height) {
(Some(w), Some(h)) => (w, h),
(Some(w), None) => (w, if aspect > 0.0 { w / aspect } else { w }),
(None, Some(h)) => (h * aspect, h),
(None, None) => (nw, nh),
};
if self.base.name.is_none()
&& let Some(id) = (!tree.root().id().is_empty()).then(|| tree.root().id())
{
self.base.name = Some(id.to_string());
}
self.base.opacity *= f64::from(tree.root().opacity().get());
self.base.blend = crate::svg::blend(tree.root().blend_mode());
let k = Self::KIND;
let kind = NodeKind::Svg {
width: sig(g, k, "width", w),
height: sig(g, k, "height", h),
};
let id = insert(g, parent, kind, self.base);
let fit = crate::svg::Fit {
nw,
nh,
sx: if nw > 0.0 { w / nw } else { 1.0 },
sy: if nh > 0.0 { h / nh } else { 1.0 },
};
crate::svg::inflate(g, id, tree.root(), &fit);
crate::handles::Svg::from_id(id)
}
}
builder!(
LayoutBuilder => Layout, kind "Layout", defaults {},
fields { direction: Direction = Direction::Row, align: LayoutAlign = LayoutAlign::Center,
justify: Justify = Justify::Center, wrap: bool = false, columns: Option<u16> = None,
gap: f64 = 0.0, padding: f64 = 0.0,
width: Option<Span> = None, height: Option<Span> = None, radius: f64 = 0.0 });
setters!(LayoutBuilder {
direction: Direction,
align: LayoutAlign,
justify: Justify
});
impl LayoutBuilder {
pub fn gap(mut self, v: impl Len) -> Self {
self.gap = v.px();
self
}
pub fn padding(mut self, v: impl Len) -> Self {
self.padding = v.px();
self
}
pub fn radius(mut self, v: impl Corner) -> Self {
self.radius = v.radius();
self
}
}
impl LayoutBuilder {
pub fn wrap(mut self, v: bool) -> Self {
self.wrap = v;
self
}
pub fn grid(mut self, columns: u16) -> Self {
self.columns = Some(columns.max(1));
self
}
pub fn row(self) -> Self {
self.direction(Direction::Row)
}
pub fn column(self) -> Self {
self.direction(Direction::Column)
}
pub fn width(mut self, v: impl Into<Span>) -> Self {
self.width = Some(v.into());
self
}
pub fn height(mut self, v: impl Into<Span>) -> Self {
self.height = Some(v.into());
self
}
}
impl NodeBuilder for LayoutBuilder {
type Handle = Layout;
base_accessor!();
fn build(self, g: &SceneGraph, parent: NodeId) -> Layout {
let k = Self::KIND;
let kind = NodeKind::Layout {
direction: self.direction,
align: self.align,
justify: self.justify,
wrap: self.wrap,
columns: self.columns,
gap: sig(g, k, "gap", self.gap),
padding: sig(g, k, "padding", self.padding),
width: place::span_option(
g,
parent,
Axis::X,
self.width,
format!("{k}#{}.width", g.next_id()),
),
height: place::span_option(
g,
parent,
Axis::Y,
self.height,
format!("{k}#{}.height", g.next_id()),
),
radius: sig(g, k, "radius", self.radius),
};
Layout::from_id(insert(g, parent, kind, self.base))
}
}
builder!(GridBuilder => Grid, kind "Grid",
defaults { stroke: Color::rgba(1.0, 1.0, 1.0, 0.15), line_width: 1.0 },
fields { width: f64 = 1920.0, height: f64 = 1080.0, spacing: f64 = 80.0 });
setters!(GridBuilder {
width: f64,
height: f64,
spacing: f64
});
impl NodeBuilder for GridBuilder {
type Handle = Grid;
base_accessor!();
fn build(self, g: &SceneGraph, parent: NodeId) -> Grid {
let k = Self::KIND;
let kind = NodeKind::Grid {
width: sig(g, k, "width", self.width),
height: sig(g, k, "height", self.height),
spacing: sig(g, k, "spacing", self.spacing),
};
Grid::from_id(insert(g, parent, kind, self.base))
}
}
#[must_use = "a builder does nothing until it is passed to `add`"]
pub struct CustomBuilder {
base: BaseProps,
draw: crate::draw::DrawFn,
width: f64,
height: f64,
fullscreen: bool,
}
impl CustomBuilder {
pub(crate) fn from_draw(draw: impl Fn(&mut crate::draw::DrawCtx, f64) + 'static) -> Self {
Self {
base: BaseProps::default(),
draw: std::rc::Rc::new(draw),
width: 100.0,
height: 100.0,
fullscreen: false,
}
}
pub fn size(mut self, width: f64, height: f64) -> Self {
self.width = width;
self.height = height;
self.fullscreen = false;
self
}
pub fn fullscreen(mut self) -> Self {
self.fullscreen = true;
self
}
}
impl NodeBuilder for CustomBuilder {
type Handle = Custom;
base_accessor!();
fn build(self, graph: &SceneGraph, parent: NodeId) -> Custom {
let (width, height) = if self.fullscreen {
graph.frame_size()
} else {
(self.width, self.height)
};
let kind = NodeKind::Custom {
draw: self.draw,
width,
height,
};
Custom::from_id(insert(graph, parent, kind, self.base))
}
}
#[must_use = "a builder does nothing until it is passed to `add`"]
pub struct ParticlesBuilder {
base: BaseProps,
seed: u64,
gravity: f64,
toward_camera: bool,
life: f64,
speed: f64,
radius: f64,
}
impl Default for ParticlesBuilder {
fn default() -> Self {
Self {
base: BaseProps::default(),
seed: 1,
gravity: 980.0,
toward_camera: false,
life: 1.4,
speed: 360.0,
radius: 5.0,
}
}
}
impl ParticlesBuilder {
pub fn seed(mut self, seed: u64) -> Self {
self.seed = seed;
self
}
pub fn gravity(mut self, gravity: f64) -> Self {
self.gravity = gravity;
self
}
pub fn toward_camera(mut self) -> Self {
self.toward_camera = true;
self
}
pub fn life(mut self, seconds: f64) -> Self {
self.life = seconds;
self
}
pub fn speed(mut self, speed: f64) -> Self {
self.speed = speed;
self
}
pub fn radius(mut self, radius: f64) -> Self {
self.radius = radius;
self
}
}
impl NodeBuilder for ParticlesBuilder {
type Handle = Particles;
base_accessor!();
fn build(self, graph: &SceneGraph, parent: NodeId) -> Particles {
let kind = NodeKind::Particles {
seed: self.seed,
gravity: sig(graph, "Particles", "gravity", self.gravity),
toward_camera: self.toward_camera,
life: self.life,
speed: self.speed,
radius: self.radius,
state: crate::sim::ParticlesState::new(),
};
Particles::from_id(insert(graph, parent, kind, self.base))
}
}
enum Seed {
Num(f64),
Vec2(f64, f64),
Color(Color),
Bool(bool),
}
#[must_use = "a builder does nothing until it is passed to `add`"]
pub struct ShaderBuilder {
base: BaseProps,
code: crate::shader::ShaderCode,
waves: bool,
width: Span,
height: Span,
fullscreen: bool,
uniforms: Vec<(String, Seed)>,
}
impl ShaderBuilder {
pub(crate) fn new(code: crate::shader::ShaderCode) -> Self {
Self {
base: BaseProps::default(),
code,
waves: false,
width: Span::Px(100.0),
height: Span::Px(100.0),
fullscreen: false,
uniforms: Vec::new(),
}
}
pub(crate) fn preset() -> Self {
let mut s = Self::new(crate::shader::ShaderCode::Wgsl(
crate::shader::waves_wgsl().into(),
));
s.waves = true;
s.uniform("amp", 0.05)
.uniform("speed", 1.0)
.uniform_color("deep", Color::rgba(0.102_f32, 0.322_f32, 0.549_f32, 1.0))
.uniform_color("foam", Color::rgba(0.910_f32, 0.953_f32, 0.980_f32, 1.0))
}
pub fn width(mut self, v: impl Into<Span>) -> Self {
self.width = v.into();
self.fullscreen = false;
self
}
pub fn height(mut self, v: impl Into<Span>) -> Self {
self.height = v.into();
self.fullscreen = false;
self
}
pub fn fullscreen(mut self) -> Self {
self.fullscreen = true;
self
}
pub fn uniform(mut self, name: impl Into<String>, value: f64) -> Self {
self.set_uniform(name.into(), Seed::Num(value));
self
}
pub fn uniform_vec2(mut self, name: impl Into<String>, value: impl Into<Vec2>) -> Self {
let v = value.into();
self.set_uniform(name.into(), Seed::Vec2(v.x, v.y));
self
}
pub fn uniform_color(mut self, name: impl Into<String>, value: impl IntoPaint) -> Self {
self.set_uniform(name.into(), Seed::Color(value.into_paint()));
self
}
pub fn uniform_bool(mut self, name: impl Into<String>, value: bool) -> Self {
self.set_uniform(name.into(), Seed::Bool(value));
self
}
pub fn amp(self, value: f64) -> Self {
self.uniform("amp", value)
}
pub fn speed(self, value: f64) -> Self {
self.uniform("speed", value)
}
pub fn deep(self, value: impl IntoPaint) -> Self {
self.uniform_color("deep", value)
}
pub fn foam(self, value: impl IntoPaint) -> Self {
self.uniform_color("foam", value)
}
fn set_uniform(&mut self, name: String, value: Seed) {
if let Some(slot) = self.uniforms.iter_mut().find(|(n, _)| *n == name) {
slot.1 = value;
} else {
self.uniforms.push((name, value));
}
}
}
impl NodeBuilder for ShaderBuilder {
type Handle = Shader;
base_accessor!();
fn build(self, graph: &SceneGraph, parent: NodeId) -> Shader {
let (width, height) = if self.fullscreen {
let (w, h) = graph.frame_size();
(Span::Px(w), Span::Px(h))
} else {
(self.width, self.height)
};
let n = graph.next_id();
let uniforms = self
.uniforms
.into_iter()
.map(|(name, seed)| {
let value = match seed {
Seed::Num(v) => crate::shader::UniformValue::Num(
Signal::new(v).with_name(format!("Shader#{n}.{name}")),
),
Seed::Vec2(x, y) => crate::shader::UniformValue::Vec2(
Signal::new(Vec2::new(x, y)).with_name(format!("Shader#{n}.{name}")),
),
Seed::Color(c) => crate::shader::UniformValue::Color(
Signal::new(c).with_name(format!("Shader#{n}.{name}")),
),
Seed::Bool(v) => crate::shader::UniformValue::Bool(
Signal::new(if v { 1.0 } else { 0.0 })
.with_name(format!("Shader#{n}.{name}")),
),
};
crate::shader::Uniform { name, value }
})
.collect();
let kind = NodeKind::Shader {
code: self.code,
waves: self.waves,
width: place::span_signal(graph, parent, Axis::X, width, format!("Shader#{n}.width")),
height: place::span_signal(
graph,
parent,
Axis::Y,
height,
format!("Shader#{n}.height"),
),
uniforms,
};
Shader::from_id(insert(graph, parent, kind, self.base))
}
}
pub fn waves() -> ShaderBuilder {
ShaderBuilder::preset()
}