All nodes
A reference of every node and its properties. If you followed the guide, you already know the pattern; this page is for looking things up.
Builders set initial values; view.add(builder) (or node.add for
children) returns a Copy handle whose methods of the same names return signals.
| Node | Builder | Signals |
|---|---|---|
Rect | .width .height .size .radius | width height radius |
Circle | .size .start_angle .end_angle .pie | size start_angle end_angle — arcs, pie slices |
Polygon | .sides .size | sides size — sides animate |
Star | .points .size .inner | points size inner |
Path | .data(BezPath) .svg("M0 0 …") | data — morphs |
Line | .points .smooth .closed .start_arrow .end_arrow | points arrow_size |
Txt | .text .rich([span(..)]) .font_size .weight .family .italic .align .max_width | text spans font_size weight, morph_to |
Code | .code .language .theme .focus(a, b) .dim | code font_size focus dim, focus_lines |
Img | .src(include_bytes!(..)) .file(path) .width .height | width height |
Layout | .row .column .wrap .grid(n) .gap .padding .align .justify .width .height | gap padding width height radius |
Grid | .width .height .spacing | width height spacing |
Group | — | — |
Every node has x y rotation scale opacity fill stroke line_width start end, the paint
and compositing properties described in Effects, and the flex item
properties grow shrink basis margin align_self. start/end trim outlines — animate end from 0 to 1 to draw a shape on (on text it reveals glyph by
glyph).
Shapes and morphing
Tweening Path::data between any two paths aligns them first (segment counts, start points,
winding), so shapes morph without twisting.
Loading live preview…
Source of morph — edit in the playground
//! Morphing between arbitrary paths and drawing outlines on with `end`.
use oxframe::prelude::*;
const STAR: &str =
"M0 -150 L44 -46 L150 -46 L64 18 L96 130 L0 64 L-96 130 L-64 18 L-150 -46 L-44 -46 Z";
const HEART: &str = "M0 140 C-60 80 -160 30 -160 -50 C-160 -110 -110 -150 -60 -150 C-30 -150 -10 -130 0 -110 \
C10 -130 30 -150 60 -150 C110 -150 160 -110 160 -50 C160 30 60 80 0 140 Z";
fn circle(r: f64) -> BezPath {
use oxframe::core::kurbo::Shape;
oxframe::core::kurbo::Circle::new((0.0, 0.0), r).to_path(0.1)
}
fn svg(d: &str) -> BezPath {
BezPath::from_svg(d).expect("valid path")
}
#[oxframe::scene]
pub async fn morph(view: View) {
// Draw the outline on, then fill it.
let shape = view.add(
Path::new()
.svg(STAR)
.fill(Color::TRANSPARENT)
.stroke(color!("#f5c242"))
.line_width(8.0)
.end(0.0),
);
shape.end().to(1.0, 1.2).await;
shape.fill().to(color!("#f5c242"), 0.5).await;
// Star → circle → heart → star: alignment picks start points and
// directions so the shapes do not twist.
shape.data().to(circle(140.0), 1.0).await;
all((
shape.data().to(svg(HEART), 1.0),
shape.fill().to(color!("#e13238"), 1.0),
shape.stroke().to(color!("#e13238"), 1.0),
))
.await;
wait_for(0.3).await;
all((
shape.data().to(svg(STAR), 1.0).ease(presets::OUT_BACK),
shape.rotation().to(144.0, 1.0),
shape.fill().to(color!("#68abdf"), 1.0),
shape.stroke().to(color!("#68abdf"), 1.0),
))
.await;
// Erase from the start.
shape.fill().to(Color::TRANSPARENT, 0.4).await;
shape.start().to(1.0, 0.8).await;
}
pub fn project() -> Project {
Project::new("morph").scene(morph())
}
Loading live preview…
Source of shapes — edit in the playground
//! Shapes and layout: polygons, stars, arcs, smooth curves and a flexbox
//! row whose items grow and wrap.
use oxframe::prelude::*;
#[oxframe::scene]
pub async fn shapes(view: View) {
let poly = view.add(
Polygon::new()
.sides(3.0)
.size(220.0)
.position((-600.0, -250.0))
.fill(color!("#68abdf")),
);
let star = view.add(
Star::new()
.points(5.0)
.size(240.0)
.position((-250.0, -250.0))
.fill(color!("#f5c242")),
);
let ring = view.add(
Circle::new()
.size(220.0)
.position((100.0, -250.0))
.end_angle(0.0)
.fill(Color::TRANSPARENT)
.stroke(color!("#e13238"))
.line_width(18.0)
.line_cap(LineCap::Round),
);
let pie = view.add(
Circle::new()
.size(220.0)
.position((450.0, -250.0))
.pie(true)
.end_angle(0.0)
.fill(color!("#2ecc71")),
);
let wave = view.add(
Line::new()
.points([
(-700.0, 60.0),
(-450.0, -40.0),
(-200.0, 120.0),
(50.0, 0.0),
(300.0, 110.0),
(600.0, 20.0),
])
.smooth(true)
.stroke(color!("#68abdf"))
.line_width(8.0)
.end(0.0),
);
all((
poly.sides().to(8.0, 2.0),
star.points().to(9.0, 2.0),
star.inner().to(0.7, 2.0),
ring.end_angle().to(300.0, 2.0),
pie.end_angle().to(290.0, 2.0),
wave.end().to(1.0, 2.0),
))
.await;
// Flexbox: the middle card grows, the row wraps when it gets narrow.
let row = view.add(
Layout::new()
.width(1200.0)
.gap(20.0)
.padding(20.0)
.radius(24.0)
.fill(color!("#26282e"))
.wrap(true)
.y(300.0),
);
let colors = ["#e13238", "#f5c242", "#68abdf", "#2ecc71", "#9b59b6"];
let mut cards = Vec::new();
for (i, c) in colors.iter().enumerate() {
let card = row.add(
Rect::new()
.width(160.0)
.height(100.0)
.radius(16.0)
.fill(Color::from_hex(c).unwrap()),
);
if i == 1 {
card.grow().set(1.0);
}
cards.push(card);
}
wait_for(0.6).await;
row.width().to(Some(600.0), 1.5).await;
wait_for(0.6).await;
row.width().to(Some(1200.0), 1.5).await;
wait_for(0.6).await;
}
pub fn project() -> Project {
Project::new("shapes").scene(shapes())
}
Layout
Layout is real flexbox (and CSS grid with .grid(columns)), computed by
taffy. Children take .grow, .shrink, .basis, .margin and .align_self; LayoutAlign::Stretch resizes rects, images and nested layouts. Layout properties are signals:
animate the width or the gap and everything re-flows.
Loading live preview…
Source of layout — edit in the playground
//! Layout: rows and columns size themselves to content; animating a
//! child's size re-flows its siblings.
use oxframe::prelude::*;
#[oxframe::scene]
pub async fn layout(view: View) {
let colors = [
color!("#e13238"),
color!("#f5c242"),
color!("#68abdf"),
color!("#2ecc71"),
];
let row = view.add(
Layout::new()
.gap(24.0)
.padding(24.0)
.radius(24.0)
.fill(color!("#262626")),
);
let mut cards = Vec::new();
for (i, c) in colors.into_iter().enumerate() {
let card = row.add(
Layout::new()
.column()
.gap(12.0)
.padding(20.0)
.radius(16.0)
.fill(c)
.opacity(0.0)
.child(
Txt::new()
.text(format!("{}", i + 1))
.font_size(64.0)
.weight(800.0),
)
.child(Txt::new().text("card").font_size(28.0)),
);
cards.push(card);
}
// Staggered fade-in.
sequence(
0.15,
cards
.iter()
.map(|c| -> oxframe::BoxFuture { Box::pin(c.opacity().to(1.0, 0.5)) })
.collect(),
)
.await;
// Growing the gap and one card's padding pushes the others aside.
all((row.gap().to(60.0, 0.8), cards[1].padding().to(60.0, 0.8))).await;
wait_for(0.4).await;
all((row.gap().to(24.0, 0.8), cards[1].padding().to(20.0, 0.8))).await;
// Removing a card re-flows the row.
cards[2].opacity().to(0.0, 0.4).await;
cards[2].remove();
wait_for(0.6).await;
}
pub fn project() -> Project {
Project::new("layout").scene(layout())
}
Text
Inter and JetBrains Mono are embedded (OFL), so text renders identically everywhere. Strings tween like a typewriter.
Loading live preview…
Source of text — edit in the playground
//! Text: typewriter effect, animated weight and size, glyph-by-glyph reveal.
use oxframe::prelude::*;
#[oxframe::scene]
pub async fn text(view: View) {
let title = view.add(Txt::new().text("").font_size(96.0).weight(300.0).y(-120.0));
// Strings tween like a typewriter.
title
.text()
.to("Hello, oxframe".into(), 1.2)
.ease(presets::LINEAR)
.await;
all((
title.weight().to(800.0, 0.8),
title.font_size().to(120.0, 0.8),
))
.await;
// Retype: deletes back to the common prefix, then types the rest.
title
.text()
.to("Hello, Rust".into(), 1.0)
.ease(presets::LINEAR)
.await;
let code = view.add(
Txt::new()
.text("async fn intro(view: View) { .. }")
.family(MONO)
.font_size(44.0)
.fill(color!("#68abdf"))
.y(60.0)
.end(0.0),
);
// `end` reveals glyph by glyph.
code.end().to(1.0, 1.5).ease(presets::LINEAR).await;
let note = view.add(
Txt::new()
.text("Кириллица тоже работает — встроенный Inter")
.font_size(40.0)
.fill(color!("#f5c242"))
.y(180.0)
.opacity(0.0),
);
note.opacity().to(1.0, 0.6).await;
wait_for(1.0).await;
}
pub fn project() -> Project {
Project::new("text").scene(text())
}
Rich text, code and morphing
Txt::new().rich([span("Signals are "), span("lazy").bold().color(color!("#f5c242"))])
Code::new().language("rust").code(src).focus(3, 4) // code.focus_lines(5, 5, 0.4).await moves it
word.morph_to("frame", 1.0).await // shared letters travel, the others fade Highlighting covers Rust, JS/TS, Python, JSON, TOML and shell, with One Dark and One Light themes.
Loading live preview…
Source of typography — edit in the playground
//! Text beyond plain strings: glyph morphing between words, rich spans and
//! code with syntax highlighting and a moving focus.
use oxframe::prelude::*;
#[oxframe::scene]
pub async fn typography(view: View) {
// Letters shared by both words travel; the rest fade.
let word = view.add(
Txt::new()
.text("oxframe")
.font_size(140.0)
.weight(800.0)
.y(-300.0),
);
wait_for(0.4).await;
word.morph_to("frame", 1.0).await;
word.morph_to("flame", 1.0).await;
let line = view.add(
Txt::new()
.y(-150.0)
.font_size(44.0)
.rich([
span("Signals are "),
span("lazy").bold().color(color!("#f5c242")),
span(", tweens are "),
span("futures").italic().color(color!("#68abdf")),
])
.end(0.0),
);
line.end().to(1.0, 1.0).ease(presets::LINEAR).await;
let code = view.add(
Code::new()
.y(150.0)
.font_size(30.0)
.focus(1, 1)
.code(
"use oxframe::prelude::*;\n\
\n\
#[oxframe::scene]\n\
async fn intro(view: View) {\n\
\x20 let r = view.add(Rect::new().size(200.0));\n\
\x20 r.x().to(300.0, 1.0).await; // 1 s\n\
}",
)
.opacity(0.0),
);
code.opacity().to(1.0, 0.5).await;
// Walk the focus through the code.
for (a, b) in [(3, 4), (5, 5), (6, 6), (0, 0)] {
wait_for(0.6).await;
code.focus_lines(a, b, 0.4).await;
}
wait_for(0.8).await;
}
pub fn project() -> Project {
Project::new("typography").scene(typography())
}