-
-
Notifications
You must be signed in to change notification settings - Fork 14
Writing Examples
Every example under examples/blinc_app_examples/examples/
runs on every platform Blinc supports — desktop via
WindowedApp::run, web via WebApp::run_with_setup, and (where
the widgets allow) iOS and Android via the mobile runners — with
no per-target forks. A single source file is the source of truth
for all targets.
The Example Gallery is assembled from
this same set, auto-discovered by tools/build-web-examples and
published to GitHub Pages via CI. Adding a new example requires
writing one file that follows the convention below. Nothing else.
No manifest entry. No wrapper crate. No CI change.
Every cross-target example must define exactly one function with
this signature, as a top-level pub fn:
pub fn build_ui(ctx: &mut WindowedContext) -> impl ElementBuilder {
// The actual demo UI.
}Edition 2024 note: when calling
WindowedApp::run(config, build_ui)directly, edition 2024 RPIT capture rules causebuild_uito only implementFnMutfor one specific lifetime — the higher-ranked bound onrunfails. The fix is to pass the builder via a closure wrapper:WindowedApp::run(config, |ctx| build_ui(ctx)). The alternative is to annotate the named fn with+ use<>to opt the return type out of lifetime capture, but the closure form is more portable across editions and is what every example uses.
And its fn main must be cfg-gated to non-wasm targets:
#[cfg(not(target_arch = "wasm32"))]
fn main() -> blinc_app::Result<()> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.init();
let config = WindowConfig {
title: "My Example".to_string(),
width: 800,
height: 600,
..Default::default()
};
// Closure wrapper: on edition 2024 a free fn `-> impl Element`
// captures its input lifetime in the return type, which breaks the
// higher-ranked `FnMut` bound. The closure form is portable across
// editions. See `WindowedApp::run` docs for the `+ use<>` alternative.
blinc_app::windowed::WindowedApp::run(config, |ctx| build_ui(ctx))
}That's it. Run the codegen tool:
cargo run -p blinc-build-web-examplesYour example is now auto-discovered, wrapped as a wasm32 crate under
examples/_generated/<name>/, built by CI, and appears in the
Example Gallery with the title and
description pulled from your //! doc comment.
A complete minimal example looks like this:
//! My New Example
//!
//! One-paragraph description of what the demo shows. This text
//! becomes the gallery page description verbatim — keep it short.
//! Bullet points render fine:
//! - First thing the example demonstrates
//! - Second thing
//!
//! Run with: cargo run -p blinc_app_examples --example my_new --features windowed
use blinc_app::prelude::*;
use blinc_app::windowed::WindowedContext;
#[cfg(not(target_arch = "wasm32"))]
fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.init();
let config = WindowConfig {
title: "My New Example".to_string(),
width: 800,
height: 600,
..Default::default()
};
// Closure wrapper: on edition 2024 a free fn `-> impl Element`
// captures its input lifetime in the return type, which breaks the
// higher-ranked `FnMut` bound. The closure form is portable across
// editions. See `WindowedApp::run` docs for the `+ use<>` alternative.
blinc_app::windowed::WindowedApp::run(config, |ctx| build_ui(ctx))
}
pub fn build_ui(ctx: &mut WindowedContext) -> impl ElementBuilder {
div()
.w(ctx.width)
.h(ctx.height)
.bg(Color::rgba(0.08, 0.08, 0.12, 1.0))
.items_center()
.justify_center()
.child(
text("Hello, Blinc!")
.size(32.0)
.color(Color::WHITE),
)
}Save that as examples/blinc_app_examples/examples/my_new.rs and run
cargo run -p blinc-build-web-examples. The gallery picks it up
on the next book build.
-
Title — the first non-empty line of the
//!doc block. The " Example" / " Demo" suffix is stripped for display, so//! Scroll Container Examplebecomes Scroll Container. -
Description — everything from the second
//!paragraph up to (but not including) the firstRun with:line. Rendered verbatim as markdown on the gallery page. -
Dependencies — the tool greps your source for
blinc_cn::/blinc_icons::/blinc_tabler_icons::/blinc_canvas_kit::/blinc_theme::/ etc. and adds matchingpath = "..."dependencies to the generated wrapper'sCargo.toml. If you use a workspace crate the tool doesn't know about yet, add it to theINFERABLE_DEPStable intools/build-web-examples/src/main.rs.
impl ElementBuilder lets you return anything Blinc considers a
valid root element: Div, Scroll, Stateful<T>, Canvas,
MotionContainer, etc. Returning Div specifically would force
you to wrap non-Div roots like scroll().child(...) in an
extra div().child(...) just to satisfy the type system, which
adds a pointless layout node.
The web runner (WebApp::run_with_setup) accepts any
ElementBuilder via the internal UiBuilderFn trait — see
crates/blinc_app/src/web.rs
for the type-erasure machinery. You should never need to think
about it; just return whatever feels natural.
The web runner's frame loop holds a mutable borrow of the context
for reactive state bookkeeping. Taking &mut makes your build_ui
compatible with both WindowedApp::run (desktop) and
WebApp::run_with_setup (web); taking & only works on desktop.
Without the cfg gate, cargo check --target wasm32-unknown-unknown
would compile your WindowedApp::run call into a wasm binary, and
that method isn't available on the web target. The gate also means
the auto-generated wrapper crate can include! your example source
without colliding with its own #[wasm_bindgen(start)] entry
point.
Historically a lot of the framework's examples initialized an
Arc<Mutex<...>> or a timeline in fn main and captured it into
the closure passed to WindowedApp::run. That pattern doesn't
translate to the web target, because the wasm wrapper only has
access to build_ui — it never sees whatever state fn main
set up. Put the state setup inside build_ui and use
ctx.use_state_keyed / ctx.use_animated_timeline to persist it
across rebuilds:
pub fn build_ui(ctx: &mut WindowedContext) -> impl ElementBuilder {
// Persistent state keyed by string — survives rebuilds.
let count = ctx.use_state_keyed("counter", || 0i32);
// Persistent animation timeline — also survives rebuilds.
let timeline = ctx.use_animated_timeline();
div().child(/* ... use count and timeline ... */)
}Some examples can't run on the web target — multi-window demos,
CLI diagnostics, OS-specific runners. For these, add a //! no-web:
line with a short reason to the top of the doc block:
//! Multi-Window Demo
//!
//! no-web: the web target has no multi-window concept — a browser
//! tab is a single `<canvas>`. `open_window_with()` doesn't
//! translate to the browser. Kept desktop-only on purpose.
//!
//! Demonstrates: ...
The codegen tool skips any file with no-web: in its doc block
(no wrapper crate, no gallery entry) without erroring out. The
desktop build is untouched, and the example continues to work as
cargo run -p blinc_app_examples --example <name> --features windowed.
Currently opted out:
-
css_parser_demo— CLI diagnostic, no event loop -
fuchsia_hello— Fuchsia OS target -
multi_window_demo— multi-window not supported on web
Desktop:
cargo run -p blinc_app_examples --example my_new --features windowedUnchanged from before the cross-target convention.
Web:
# 1. Generate (or regenerate) the wasm wrapper crate
cargo run -p blinc-build-web-examples
# 2. Build it with wasm-pack
cd examples/_generated/my_new
wasm-pack build --target web --release
# 3. Serve it
./serve.sh 8000
# Open http://localhost:8000 in Chrome 113+For iterating on a single example, once the wrapper exists you can
skip step 1 on subsequent runs — cargo's rerun-if-changed in the
wrapper's build.rs catches edits to your upstream example
automatically. Only add / remove / rename operations require a
fresh codegen pass.
Running cargo run -p blinc-build-web-examples (no flags) writes:
-
examples/_generated/<name>/— one wrapper crate per discovered example. Contents:Cargo.toml,build.rs,src/lib.rs,index.html,serve.sh,.gitignore. -
docs/book/src/web/example-gallery.md— the gallery index. -
docs/book/src/web/example-gallery/<name>.md— one page per example with an iframe of the wasm build. -
docs/book/src/SUMMARY.md— patched between<!-- begin:web-examples -->/<!-- end:web-examples -->markers to include the new gallery pages in the book's TOC.
Everything under examples/_generated/ is gitignored (except
.gitignore + .gitkeep markers) so the generated tree is
rebuilt on every CI run and never ends up in a commit.
Flags:
-
--build— after codegen, runwasm-pack build --target web --releasein each wrapper. Used by CI. -
--stage-to <dir>— after--build, copy each wrapper'sindex.html+pkg/into<dir>/<name>/. Used by CI to drop artifacts intotarget/book/examples/for mdBook iframe resolution. Implies--build. -
--no-gallery— skip the markdown + SUMMARY patch. Useful for lint-only CI steps that don't need to touch the book source.
The earlier version of the repo had hand-written wrapper crates
for every web example (examples/web_hello, web_drag,
web_assets, web_mobile_demo). That worked for a handful but
didn't scale: every new example meant a new directory, new
Cargo.toml, new index.html, new serve.sh, plus duplicated
code between the desktop and web entry points.
The convention-driven approach collapses all that to one .rs
file that compiles for both targets. The wrapper crate generation
is purely mechanical: the codegen tool parses your example with
syn, checks for the convention, and emits the wrapper from a
template. There's no magic, no AST rewriting — just file I/O.
Getting Started
Mobile Development
Example Gallery
- Example Gallery
- Canvas Element
- Canvas Kit Interactive
- Carousel Demo - Selector API Showcase
- Chrome-Style Tabs
- blinc_cn Components
- Code Element
- Complex SVG
- CSS Debug
- CSS Visual Features
- Layer Effects
- Emoji and HTML Entities
- @flow Shader
- Fluid Surface
- Skeleton animation with glTF +
blinc_canvas_kit. DrawContext::run_gpu_passend-to-end demo.- Image CSS Styling
- Image Layer Test
- Keyframe Animation Canvas
- Markdown Editor
- 3D Mesh Demo — renders the Khronos glTF
DamagedHelmetsample model - Motion Demo
- Music Player Glass Card
- Node-editor demo — pre-wired graph with three node types, typed
- Notch Menu Bar
- Overflow Fade
- Overlay System
- Pointer Query
- Rich Text Element
- Rich Text Editor
- Scroll Container
- Semantic @flow
- Sortable
- Stateful API + Signal-bound modifiers demo.
- End-to-end 3D demo wiring Blinc's SceneKit3D renderer up to
- Unified Styling API
- SVG Animation
- Table Builder
- Tabler Icons
- Minimal text positioning test
- Text Input Widgets
- KHR_texture_transform
- Theme System
- Timeline Animation
- Typography
- Video Player
- Wet Glass
- Windowed Application
Web Development
Core Concepts
Animation
Components
Component Library (blinc_cn)
Widgets
- Buttons & Inputs
- Text & Rich Text
- Code Editor
- Scroll Containers
- Virtualized List
- Canvas Drawing
- Images & SVG
- Audio & Video
- Markdown Rendering
Canvas Kit
Advanced
- Routing & Navigation
- Multi-Window
- System Integration
- Element Query API
- Overlay System
- Custom State Machines
- Pointer Query
- Performance Tips
- Flow Shaders
- 3D Rendering
- Custom GPU Passes
- Hot-reload (experimental)
Architecture
Contributing