Writing your own node
A struct that draws itself becomes a node: animatable fields, layout, effects and checks, like the built-in ones.
Loading live preview…
Source of gauge — edit in the playground
//! A node of your own: a gauge that draws an arc with `DrawCtx` and
//! animates its value like any built-in node.
use oxframe::prelude::*;
/// A dial from 0 to 1.
#[derive(Node)]
pub struct Gauge {
/// 0 is empty, 1 is full.
#[signal]
value: f64,
/// Diameter in pixels.
#[signal]
size: f64,
}
impl Draw for Gauge {
fn draw(&self, ctx: &mut DrawCtx) {
let r = self.size / 2.0 - 12.0;
ctx.arc((0.0, 0.0), r, 135.0, 270.0).stroke(Surface, 18.0);
ctx.arc((0.0, 0.0), r, 135.0, 270.0 * self.value.clamp(0.0, 1.0))
.stroke(Accent, 18.0);
}
fn size(&self) -> (f64, f64) {
(self.size, self.size)
}
}
#[oxframe::scene]
pub async fn dials(view: View) {
let row = view.add(Layout::new().gap(80.0));
let gauges: Vec<GaugeHandle> = [0.35, 0.8, 0.55]
.into_iter()
.map(|_| row.add(Gauge::new().size(260.0)))
.collect();
view.add(
Txt::new()
.text("A node of your own")
.h1()
.to_edge(Edge::Top),
);
stagger(0.2, [0.35, 0.8, 0.55].into_iter().enumerate(), |(i, v)| {
gauges[i].value().to(v, 1.0)
})
.await;
wait_for(0.5).await;
}
pub fn project() -> Project {
Project::new("gauge").scene(dials())
}A struct that draws
Turn on the unstable-node-api feature of oxframe. The API may change
until it has outside users.
oxframe = { version = "…", features = ["unstable-node-api"] } #[derive(Node)]
pub struct Gauge {
#[signal] value: f64, // 0..1
#[signal] size: f64,
}
impl Draw for Gauge {
fn draw(&self, ctx: &mut DrawCtx) {
let r = self.size / 2.0 - 12.0;
ctx.arc((0.0, 0.0), r, 135.0, 270.0 * self.value).stroke(Accent, 18.0);
}
fn size(&self) -> (f64, f64) {
(self.size, self.size)
}
} #[derive(Node)] on a struct that implements Draw makes it a node. Each
field marked #[signal] gets a setter on the builder, Gauge::new(), and a
signal on the handle, GaugeHandle, so gauge.value().to(0.8, 1.0) animates it. Fields without the mark are set once on the builder. Every field type needs Default and Clone.
let gauge = view.add(Gauge::new().size(240.0));
gauge.value().to(0.8, 1.0).await; Drawing
Draw::draw paints with the same DrawCtx as Custom: ctx.line, ctx.circle, ctx.rect, ctx.path and DrawCtx::arc for gauges and rings, each followed by .fill(..) or .stroke(.., width) with a color or a theme role. Coordinates are the node's own, with the
origin at the center of its box.
Draw only from the fields and ctx.time(), and take randomness from ctx.rng(seed). The frame is painted again on every seek, and oxframe check reports a node whose two paints of one frame differ.
Its box
Draw::size gives the box that flex layout, Camera::fit and oxframe check use; the default is 100 by 100. Everything else comes with the node:
position, rotation, scale, opacity, relative placement such as to_edge, effects, flex
properties and theme colors. check and inspect name it by its type, so
an issue reads Gauge, not Custom.
A crate of your own
oxframe init --template node my-node oxframe init --template node my-node makes a library crate: the node in src/lib.rs, a demo scene in examples/demo.rs (cargo run --example demo -- render) and a reference test that renders the node on
the CPU and checks its pixels. Publish it like any crate; scenes add it as a dependency.
The playground has the feature on, so a node written in a scene there works too.