DrawnUI for Rust: a Skia-drawn UI engine, the same controls and contract as DrawnUI for .NET and React, on the desktop, mobile and the web.
See the codeDraw your whole UI with Skia, from one Rust source, on every screen. DrawnUI for Rust is a UI engine: layouts, controls you can subclass and paint, gestures, animations, caching, shaders and accessibility, on Windows, macOS, Linux, iOS, Android and in the browser. It shares its controls and rules with DrawnUI for .NET and DrawnUI for React, so one design runs the same in all three.
drawnui · API docs: https://docs.rs/drawnuiCHANGELOG.md · Accessibility: ACCESSIBILITY.md
· What it shares with DrawnUI for .NET: PARITY.mduse drawnui::prelude::*;
#[derive(Default)]
struct App { count: i32 }
fn build(_app: &mut App) -> Build<SkiaLayout> {
SkiaLayout::column().spacing(16).padding(24).children((
SkiaLabel::new("").font_size(24).text_color(Color::WHITE)
.observe(|me, app: &App| me.set_text(format!("Count {}", app.count))),
SkiaButton::new("Tap me").on_tapped(|_me, app: &mut App, _cx| app.count += 1),
))
}
fn main() {
drawnui::run("Counter", || Box::new(Ui::new(App::default(), build).font("Default", "assets/OpenSans-Regular.ttf")));
}
observe your
state, handle a tap in one line.cargo new to the browser and Android builds.| Platform | Graphics | Skia prebuilt for |
|---|---|---|
| Windows | OpenGL | x64, ARM64 |
| macOS | Metal, paced by the display | Apple silicon, Intel |
| Linux | OpenGL, X11 and Wayland | x64, ARM64 |
| iOS | Metal, 120 Hz on ProMotion screens | devices, simulator (Apple silicon and Intel) |
| Android | Vulkan, with OpenGL ES as the fallback | arm64, armv7, x86_64, x86 |
| Browser | WebGL2 (wasm32-unknown-emscripten) | all browsers with WebGL2 |
| Crate | What it is | Version |
|---|---|---|
drawnui | the engine: controls, layouts, caches, gestures, animations, accessibility, the desktop, mobile and browser hosts | 0.1.0-preview.4 |
drawnui-skia-safe | Skia for Rust: rust-skia's skia-safe plus SkMesh; drawnui re-exports it as drawnui::skia | 0.153.6 |
drawnui-skia-bindings | the native Skia under drawnui-skia-safe, downloaded prebuilt from DrawnUi/rust-skia | 0.153.6 |
An app depends on drawnui only; the Skia crates come with it. Use Skia's API through
drawnui::skia (the same as skia_safe, SkMesh included) and do not add skia-safe or skia-bindings
to an app: a program links one Skia.
The fastest way: copy templates/app, rename myapp, build. It is an empty
app with an icon, assets, a web page and build scripts for the desktop, the browser and Android.
Or by hand, in a new project (cargo new myapp), Cargo.toml:
[dependencies]
drawnui = "0.1.0-preview.4"
[profile.release]
lto = "thin"
panic = "abort"
The first build downloads Skia for your platform (11 to 25 MB) from
DrawnUi/rust-skia's releases. SKIA_BINARIES_URL
points the download at a mirror. Do not set FORCE_SKIA_BINARIES_DOWNLOAD: inside a crate it makes
skia-bindings compile Skia from source.
.cargo/config.toml, for the browser's link flags and Android's 16 KB pages:
[target.wasm32-unknown-emscripten]
linker = "em++"
rustflags = [
"-C", "link-arg=-sMAX_WEBGL_VERSION=2",
"-C", "link-arg=-sMODULARIZE=1",
"-C", "link-arg=-sEXPORT_NAME=createDrawnUi",
"-C", "link-arg=-sEXPORTED_RUNTIME_METHODS=GL,HEAPU8,UTF8ToString",
"-C", "link-arg=-sALLOW_MEMORY_GROWTH=1",
# A 1 MB stack with the overflow check: layout and paint walk the tree recursively.
"-C", "link-arg=-sSTACK_SIZE=1048576",
"-C", "link-arg=-sSTACK_OVERFLOW_CHECK=1",
"-C", "link-arg=-sERROR_ON_UNDEFINED_SYMBOLS=0",
]
[target.'cfg(target_os = "android")']
rustflags = ["-C", "link-arg=-Wl,-z,max-page-size=16384"]
The web page loads your build and drawnui_host.js from the same drawnui version (the template's
web.ps1 / web.sh copy it for you):
<canvas id="canvas" style="width:100vw;height:100dvh;display:block;touch-action:none"></canvas>
<script src="myapp.js"></script>
<script src="drawnui_host.js"></script>
<script>DrawnUi.start({ canvas: document.getElementById("canvas"), create: createDrawnUi });</script>
Per platform:
Windows, Linux, macOS: cargo run --release. Assets go next to the executable (the
template's build.rs), icon.ico becomes the window and taskbar icon. Linux needs the X11 /
Wayland and GL development packages.
Browser: rustup target add wasm32-unknown-emscripten, emsdk 6 with its tools on PATH, then
the template's web.ps1 / web.sh; serve dist/ over http.
Android: cargo-ndk and NDK 26; cargo ndk ... rustc --lib --release --crate-type cdylib makes
the activity's library (android_main in the template's src/lib.rs), packed by Gradle with a
GameActivity (templates/fiddle has the Gradle project and build scripts).
Add the winit and android-activity fixes to your Cargo.toml:
[patch.crates-io]
winit = { git = "https://github.com/DrawnUi/winit", rev = "f1603c5c70f39e93e95839136d1d70c37fbd71f6" }
android-activity = { git = "https://github.com/DrawnUi/android-activity", rev = "d45520c015edc340c16770c2266d711327ce01f1" }
iOS: dev/ios builds an .app bundle for the simulator, or a signed one for a
device.
Building with an AI coding agent? Give it the DrawnUI for Rust skill and it writes DrawnUI code the way the engine expects: adding drawnui, fluent builders, controls and layouts, caching, the browser build with its link flags and web page, Android, icons, accessibility.
skills/drawnui-rust/SKILL.md, also at
https://hellorust.drawnui.net/skills/drawnui-rust/SKILL.md~/.claude/skills/drawnui-rust/SKILL.md (or in your
agent's skill or rules folder).The same draw list, N rounded rectangles per frame, 1609x1163 canvas, Chrome on Windows, WebGL2:
| Draw calls | DrawnUI for Rust (Rust + Skia in one wasm module) | CanvasKit 0.42 driven from JS |
|---|---|---|
| 2,000 | 59.9 FPS, 2.8 ms CPU | 60.0 FPS, 3.7 ms CPU |
| 5,000 | 60.0 FPS, 5.1 ms CPU | 59.8 FPS, 7.4 ms CPU |
| 20,000 | 59.7 FPS, 12.5 ms CPU | 47.8 FPS, 20.6 ms CPU |
Windows desktop (OpenGL): 20,000 draw calls at 60 FPS, 10 to 12 ms CPU. MacBook Air M1 (Metal): 20,000 draw calls at a steady 60 FPS, 10.3 ms CPU. SkMesh: 2,000 animated waves (a vertex program each, one draw call each) in 6.1 ms CPU on the M1, and at 60 FPS in 6.4 ms on a Windows desktop.
The web build links only the Skia code it uses:
| Build | raw | brotli |
|---|---|---|
gl | 3.93 MB | 1.15 MB |
gl + text layout (ICU, HarfBuzz) + SVG | 7.39 MB | 2.27 MB |
| CanvasKit 0.42 wasm, for scale | 7.32 MB | 2.28 MB |
A phone with a Mali-G57 MC2, 1080x2408, a 90 Hz panel, the same release build on each API. On heavy scenes Vulkan draws 21 to 35 % more frames; where both keep up with the panel, it needs 26 to 59 % less CPU per frame.
| Scene | Vulkan | OpenGL ES | Vulkan better by |
|---|---|---|---|
| 20,000 draw calls | 40.2 FPS, 24.8 ms a frame | 33.3 FPS, 30.0 ms | +21 % FPS |
| 2,000 SkMesh waves | 57.2 FPS, 17.5 ms | 42.4 FPS, 23.6 ms | +35 % FPS |
| 2,000 animated shapes | 91.6 FPS, CPU 5.4 ms | 91.6 FPS, CPU 7.3 ms | -26 % CPU |
| HelloRust: Recycled cells, flings | 91.6 FPS, CPU 1.45 ms | 91.6 FPS, CPU 2.71 ms | -46 % CPU |
| HelloRust: Uneven cells, flings | 91.6 FPS, CPU 0.94 ms | 91.6 FPS, CPU 2.32 ms | -59 % CPU |
| HelloRust: Lottie & GIF | 91.6 FPS, CPU 1.94 ms | 91.6 FPS, CPU 3.67 ms | -47 % CPU |
| HelloRust: Shaders | 91.6 FPS, CPU 1.60 ms | 91.6 FPS, CPU 2.75 ms | -42 % CPU |
OpenGL ES stays available as the fallback and as an option (Ui::gpu_backend).
cargo test -p drawnui -p hellorust # headless tests, no window
cargo run --release -p hellorust # the demo
cd examples/bench && cargo run --release -- 20000 g0 # 20,000 draw calls
cd examples/bench && cargo run --release -- 2000 mesh # 2,000 animated SkMesh waves
dev/build.ps1 -Example hellorust -WebOnly # the browser build into target/web/hellorust
The workspace builds against three checkouts next to this repository:
../rust-skia: DrawnUi/rust-skia, branch
drawnui-crates (git clone -b drawnui-crates https://github.com/DrawnUi/rust-skia.git). Skia
downloads prebuilt, as it does for apps.../winit and ../android-activity: upstream plus one fix each; the commands are in
dev/winit-android and
dev/android-activity-null-text.To work on Skia itself, dev/skia-binaries.ps1 builds it from source once per target and exports
the archive every later build unpacks. Our notes on each platform, for anyone bringing a Rust + Skia
library to it: solved-win.md, solved-wasm.md,
solved-mac.md, solved-ios.md,
solved-android.md, solved-linux.md.
drawnui/: the engine crate; drawnui/web/drawnui_host.js is the browser host.examples/hello/: a counter. examples/hellorust/: the DrawnUI Hello app.
examples/bench/: the benchmark scene. examples/images/: a photo feed. examples/dungeon/:
Dungeon Run.templates/app/: the starter app.dev/: build, release and platform scripts.MIT, see LICENSE.
DrawnUI for Rust: a Skia-drawn UI engine, the same controls and contract as DrawnUI for .NET and React, on the desktop, mobile and the web.
See the codeDraw your whole UI with Skia, from one Rust source, on every screen. DrawnUI for Rust is a UI engine: layouts, controls you can subclass and paint, gestures, animations, caching, shaders and accessibility, on Windows, macOS, Linux, iOS, Android and in the browser. It shares its controls and rules with DrawnUI for .NET and DrawnUI for React, so one design runs the same in all three.
drawnui · API docs: https://docs.rs/drawnuiCHANGELOG.md · Accessibility: ACCESSIBILITY.md
· What it shares with DrawnUI for .NET: PARITY.mduse drawnui::prelude::*;
#[derive(Default)]
struct App { count: i32 }
fn build(_app: &mut App) -> Build<SkiaLayout> {
SkiaLayout::column().spacing(16).padding(24).children((
SkiaLabel::new("").font_size(24).text_color(Color::WHITE)
.observe(|me, app: &App| me.set_text(format!("Count {}", app.count))),
SkiaButton::new("Tap me").on_tapped(|_me, app: &mut App, _cx| app.count += 1),
))
}
fn main() {
drawnui::run("Counter", || Box::new(Ui::new(App::default(), build).font("Default", "assets/OpenSans-Regular.ttf")));
}
observe your
state, handle a tap in one line.cargo new to the browser and Android builds.| Platform | Graphics | Skia prebuilt for |
|---|---|---|
| Windows | OpenGL | x64, ARM64 |
| macOS | Metal, paced by the display | Apple silicon, Intel |
| Linux | OpenGL, X11 and Wayland | x64, ARM64 |
| iOS | Metal, 120 Hz on ProMotion screens | devices, simulator (Apple silicon and Intel) |
| Android | Vulkan, with OpenGL ES as the fallback | arm64, armv7, x86_64, x86 |
| Browser | WebGL2 (wasm32-unknown-emscripten) | all browsers with WebGL2 |
| Crate | What it is | Version |
|---|---|---|
drawnui | the engine: controls, layouts, caches, gestures, animations, accessibility, the desktop, mobile and browser hosts | 0.1.0-preview.4 |
drawnui-skia-safe | Skia for Rust: rust-skia's skia-safe plus SkMesh; drawnui re-exports it as drawnui::skia | 0.153.6 |
drawnui-skia-bindings | the native Skia under drawnui-skia-safe, downloaded prebuilt from DrawnUi/rust-skia | 0.153.6 |
An app depends on drawnui only; the Skia crates come with it. Use Skia's API through
drawnui::skia (the same as skia_safe, SkMesh included) and do not add skia-safe or skia-bindings
to an app: a program links one Skia.
The fastest way: copy templates/app, rename myapp, build. It is an empty
app with an icon, assets, a web page and build scripts for the desktop, the browser and Android.
Or by hand, in a new project (cargo new myapp), Cargo.toml:
[dependencies]
drawnui = "0.1.0-preview.4"
[profile.release]
lto = "thin"
panic = "abort"
The first build downloads Skia for your platform (11 to 25 MB) from
DrawnUi/rust-skia's releases. SKIA_BINARIES_URL
points the download at a mirror. Do not set FORCE_SKIA_BINARIES_DOWNLOAD: inside a crate it makes
skia-bindings compile Skia from source.
.cargo/config.toml, for the browser's link flags and Android's 16 KB pages:
[target.wasm32-unknown-emscripten]
linker = "em++"
rustflags = [
"-C", "link-arg=-sMAX_WEBGL_VERSION=2",
"-C", "link-arg=-sMODULARIZE=1",
"-C", "link-arg=-sEXPORT_NAME=createDrawnUi",
"-C", "link-arg=-sEXPORTED_RUNTIME_METHODS=GL,HEAPU8,UTF8ToString",
"-C", "link-arg=-sALLOW_MEMORY_GROWTH=1",
# A 1 MB stack with the overflow check: layout and paint walk the tree recursively.
"-C", "link-arg=-sSTACK_SIZE=1048576",
"-C", "link-arg=-sSTACK_OVERFLOW_CHECK=1",
"-C", "link-arg=-sERROR_ON_UNDEFINED_SYMBOLS=0",
]
[target.'cfg(target_os = "android")']
rustflags = ["-C", "link-arg=-Wl,-z,max-page-size=16384"]
The web page loads your build and drawnui_host.js from the same drawnui version (the template's
web.ps1 / web.sh copy it for you):
<canvas id="canvas" style="width:100vw;height:100dvh;display:block;touch-action:none"></canvas>
<script src="myapp.js"></script>
<script src="drawnui_host.js"></script>
<script>DrawnUi.start({ canvas: document.getElementById("canvas"), create: createDrawnUi });</script>
Per platform:
Windows, Linux, macOS: cargo run --release. Assets go next to the executable (the
template's build.rs), icon.ico becomes the window and taskbar icon. Linux needs the X11 /
Wayland and GL development packages.
Browser: rustup target add wasm32-unknown-emscripten, emsdk 6 with its tools on PATH, then
the template's web.ps1 / web.sh; serve dist/ over http.
Android: cargo-ndk and NDK 26; cargo ndk ... rustc --lib --release --crate-type cdylib makes
the activity's library (android_main in the template's src/lib.rs), packed by Gradle with a
GameActivity (templates/fiddle has the Gradle project and build scripts).
Add the winit and android-activity fixes to your Cargo.toml:
[patch.crates-io]
winit = { git = "https://github.com/DrawnUi/winit", rev = "f1603c5c70f39e93e95839136d1d70c37fbd71f6" }
android-activity = { git = "https://github.com/DrawnUi/android-activity", rev = "d45520c015edc340c16770c2266d711327ce01f1" }
iOS: dev/ios builds an .app bundle for the simulator, or a signed one for a
device.
Building with an AI coding agent? Give it the DrawnUI for Rust skill and it writes DrawnUI code the way the engine expects: adding drawnui, fluent builders, controls and layouts, caching, the browser build with its link flags and web page, Android, icons, accessibility.
skills/drawnui-rust/SKILL.md, also at
https://hellorust.drawnui.net/skills/drawnui-rust/SKILL.md~/.claude/skills/drawnui-rust/SKILL.md (or in your
agent's skill or rules folder).The same draw list, N rounded rectangles per frame, 1609x1163 canvas, Chrome on Windows, WebGL2:
| Draw calls | DrawnUI for Rust (Rust + Skia in one wasm module) | CanvasKit 0.42 driven from JS |
|---|---|---|
| 2,000 | 59.9 FPS, 2.8 ms CPU | 60.0 FPS, 3.7 ms CPU |
| 5,000 | 60.0 FPS, 5.1 ms CPU | 59.8 FPS, 7.4 ms CPU |
| 20,000 | 59.7 FPS, 12.5 ms CPU | 47.8 FPS, 20.6 ms CPU |
Windows desktop (OpenGL): 20,000 draw calls at 60 FPS, 10 to 12 ms CPU. MacBook Air M1 (Metal): 20,000 draw calls at a steady 60 FPS, 10.3 ms CPU. SkMesh: 2,000 animated waves (a vertex program each, one draw call each) in 6.1 ms CPU on the M1, and at 60 FPS in 6.4 ms on a Windows desktop.
The web build links only the Skia code it uses:
| Build | raw | brotli |
|---|---|---|
gl | 3.93 MB | 1.15 MB |
gl + text layout (ICU, HarfBuzz) + SVG | 7.39 MB | 2.27 MB |
| CanvasKit 0.42 wasm, for scale | 7.32 MB | 2.28 MB |
A phone with a Mali-G57 MC2, 1080x2408, a 90 Hz panel, the same release build on each API. On heavy scenes Vulkan draws 21 to 35 % more frames; where both keep up with the panel, it needs 26 to 59 % less CPU per frame.
| Scene | Vulkan | OpenGL ES | Vulkan better by |
|---|---|---|---|
| 20,000 draw calls | 40.2 FPS, 24.8 ms a frame | 33.3 FPS, 30.0 ms | +21 % FPS |
| 2,000 SkMesh waves | 57.2 FPS, 17.5 ms | 42.4 FPS, 23.6 ms | +35 % FPS |
| 2,000 animated shapes | 91.6 FPS, CPU 5.4 ms | 91.6 FPS, CPU 7.3 ms | -26 % CPU |
| HelloRust: Recycled cells, flings | 91.6 FPS, CPU 1.45 ms | 91.6 FPS, CPU 2.71 ms | -46 % CPU |
| HelloRust: Uneven cells, flings | 91.6 FPS, CPU 0.94 ms | 91.6 FPS, CPU 2.32 ms | -59 % CPU |
| HelloRust: Lottie & GIF | 91.6 FPS, CPU 1.94 ms | 91.6 FPS, CPU 3.67 ms | -47 % CPU |
| HelloRust: Shaders | 91.6 FPS, CPU 1.60 ms | 91.6 FPS, CPU 2.75 ms | -42 % CPU |
OpenGL ES stays available as the fallback and as an option (Ui::gpu_backend).
cargo test -p drawnui -p hellorust # headless tests, no window
cargo run --release -p hellorust # the demo
cd examples/bench && cargo run --release -- 20000 g0 # 20,000 draw calls
cd examples/bench && cargo run --release -- 2000 mesh # 2,000 animated SkMesh waves
dev/build.ps1 -Example hellorust -WebOnly # the browser build into target/web/hellorust
The workspace builds against three checkouts next to this repository:
../rust-skia: DrawnUi/rust-skia, branch
drawnui-crates (git clone -b drawnui-crates https://github.com/DrawnUi/rust-skia.git). Skia
downloads prebuilt, as it does for apps.../winit and ../android-activity: upstream plus one fix each; the commands are in
dev/winit-android and
dev/android-activity-null-text.To work on Skia itself, dev/skia-binaries.ps1 builds it from source once per target and exports
the archive every later build unpacks. Our notes on each platform, for anyone bringing a Rust + Skia
library to it: solved-win.md, solved-wasm.md,
solved-mac.md, solved-ios.md,
solved-android.md, solved-linux.md.
drawnui/: the engine crate; drawnui/web/drawnui_host.js is the browser host.examples/hello/: a counter. examples/hellorust/: the DrawnUI Hello app.
examples/bench/: the benchmark scene. examples/images/: a photo feed. examples/dungeon/:
Dungeon Run.templates/app/: the starter app.dev/: build, release and platform scripts.MIT, see LICENSE.