rust_widgets 2.0.0

Pure Rust cross-platform native GUI library with hardware-adaptive rendering, 60+ widgets, touch/gesture support, i18n, and SVG-pipeline-accurate output
docs.rs failed to build rust_widgets-2.0.0
Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Visit the last successful build: rust_widgets-0.5.19

rust_widgets — Pure Rust GUI Library

Cross-platform GUI library in pure Rust. Hardware-adaptive rendering, widget library, touch/gesture support, i18n, and SVG output. Supports desktop, tablet, mobile, embedded, and minimal-profile mini targets.

✨ Every control is self-drawn

The library paints 100% of its own controls. It does not create native OS controls — on any platform.

There is no CreateWindowExW/NSButton/gtk_button_new/android.widget.Button anywhere in this crate. Each backend's only job is to hand the renderer a surface to paint into; every button, list, editor, menu and chart below is drawn by the same Rust rasterizer, so a control looks and behaves identically whether it is running on Windows, macOS, Linux, iOS, Android or the web.

        ┌──────────────────────────────────────────┐
        │  rust_widgets  —  paints its own controls │
        └──────────────────────────────────────────┘
             │  rasterizer output (RGBA / SVG / GPU)
             ▼
  ┌──────────────┐   ┌──────────────┐   ┌──────────────┐
  │ Windows HWND │   │ macOS NSView │   │  GTK widget  │   … one surface per backend
  └──────────────┘   └──────────────┘   └──────────────┘

Why this matters

Property Self-drawn (this library) Native controls
Appearance Identical on every OS Differs per OS toolkit and version
Widget count 167 kinds, all platforms Only what the OS toolkit offers
Dependency weight No GUI toolkit linked GTK / AppKit / Win32 / Android SDK
Headless & embedded Runs with no OS at all (mini, SVG) Impossible
Deterministic tests Pixel/serialise snapshots Needs a real display

What each backend does own

Self-drawing is not "one backend". A backend still owns the parts that genuinely belong to the operating system, and only those:

  • Surface + event loop — window creation, the paint callback, resize.
  • Input — keyboard/mouse/touch translated into a unified Event.
  • Platform services — IME, clipboard, accessibility bridge, file dialogs, DPI scaling.

A backend that cannot supply even a surface (for example a bare framebuffer) still works: it paints into an in-memory buffer instead. See docs/ARCHITECTURE.md.

Migrating from 1.x? Native control creation was removed from all ten backends in 2.0.0. See CHANGELOG.md and docs/MIGRATION_GUIDE.md.

All 167 widget kinds are registered in the factory and each publishes its own property contract; the platform capability matrix (docs/plans/platform_capability_matrix.md) is generated from source and gated for drift in CI.

build version tests license


Quick Start

# Desktop (default)
cargo check

# Mini (reduced std profile, minimal widget set)
cargo check --no-default-features --features mini

# Embedded
cargo check --no-default-features --features embedded

# Tests (lib suite; the CI command is `cargo test --all-features -q`)
cargo test --lib

# Cross-compile checks used by CI (no system libraries required)
cargo check --target wasm32-unknown-unknown --no-default-features --features wasm
cargo check --target x86_64-pc-windows-msvc --no-default-features \
  --features "windows desktop-runtime wgpu touch i18n controls-native controls-custom serde serde_json advanced-widgets quality-management"

Android: build the JNI test APK with ./tools/build_android_testapp.sh (ANDROID_SDK_ROOT defaults to ~/Android/Sdk; the NDK is taken from $ANDROID_SDK_ROOT/ndk). See Build Requirements.

Device Profiles

Pick exactly one. The device profiles are mutually exclusive: mini/embedded compile parts of the crate out, so combining one with desktop is not a "lowest common denominator" — it breaks the build.

# ✅ correct
cargo check                                        # desktop (default)
cargo check --no-default-features --features mini
cargo check --no-default-features --features embedded

# ❌ wrong: desktop stays on, so mobile-profile modules are still compiled
cargo check --features mini
Profile Command Backend Widgets i18n GPU
Desktop cargo check OS surface + event loop Full widget set ✅ (wgpu enabled by desktop)
Tablet --no-default-features --features tablet OS surface + event loop Full widget set ✅ (wgpu enabled by tablet)
Mobile --no-default-features --features mobile Mobile API Full widget set ✅ (wgpu enabled by mobile)
Embedded --no-default-features --features embedded Software Core widget set
Mini --no-default-features --features mini reduced std + alloc Core widget set

What each profile turns off

The API is the same across profiles; what differs is which capabilities exist. Only profiles that include a platform backend and keep widget::runtime can host custom-painted widgets:

Capability Desktop Embedded Mini
widget::runtime (widget registry)
Custom-painted widgets (mount_custom_widget)
supports_custom_widgets() true false false
Menus / tool bars / status bars
Menu shortcuts (displayed)
Menu shortcuts (actually fire)

Where the table shows the capability is absent, not degraded: the module is compiled out, so supports_custom_widgets() reports false and callers are expected to refuse the operation rather than mount into a blank window (see demo/code_editor's startup check).

Menus and shortcuts are deliberately not affected: their code carries no mini gate, so a mini build is best described as "no custom-painted widget surface, but fully working menus".

The cargo test --all-features CI command deliberately turns every feature on, which includes desktop and mini at once. That combination is the regression tripwire for this constraint; see docs/plans/platform_differences.md for the full rationale and the verification matrix.

tablet / mobile need an explicit OS backend

Unlike desktop, the tablet and mobile profiles do not pull in an OS backend by themselves — their only backend entry is os-auto, which is currently an empty feature. Build them with a backend named explicitly:

# ⚠️ resolves to a stub backend on every OS: no real widgets at all
cargo check --no-default-features --features tablet

# ✅ real backend
cargo check --no-default-features --features "tablet,macos"

Two consequences worth knowing before you rely on these profiles:

  • Without a backend feature you silently get macos-fallback-stub (or the per-OS equivalent) rather than an error. Check rust_widgets::backend_name() if you are unsure which one you built.
  • On macOS, tablet/mobile select the objc2 preview backend, which does not implement custom-painted widget hosting yet. On macOS that currently requires the desktop profile. Query supports_custom_widgets() rather than assuming.

OS Backends

OS Feature Auto-detect
Windows (Win32) windows
macOS (Cocoa/objc2) macos
iOS (UIKit) ios
Linux (GTK) linux-gtk
Linux (Wayland) linux-wayland
Android (JNI) android
Web (WASM) wasm
HarmonyOS harmony

OS Support Matrix

1. Platform services per OS

These are the capabilities a backend reports about the operating system. Every one is queried at runtime through PlatformCapabilities (rust_widgets::PlatformCapabilities) — read it rather than assume, because a backend running on an OS it was not compiled for reports false.

OS Backend Family DPI scaling IME Accessibility Native menu Configurable
Windows WindowsPlatform Desktop
macOS cocoa Desktop
macOS (objc2 preview) macos-objc2-preview Desktop
Linux / GTK GTK backend Desktop
Linux / Wayland wayland Desktop
iOS ios-state-backend Mobile
Android android-state-backend Mobile
HarmonyOS harmony-desktop Desktop
Web (WASM) wasm-state-backend Embedded
Portable / no-OS portable Embedded

Legend. Native menu means the OS exposes a menu-bar protocol. Wayland has none, so its backend keeps the menu tree in-process and the host renders it — advertising a native menu would be false. Configurable means the backend exposes OS-level settings (theme, accent colour, notifier) beyond the capability flags.

How to read the native_menu column. A backend that does not override Platform::capabilities inherits the trait default, which is "true if the backend reports the Desktop family". Wayland, iOS, Android and HarmonyOS override it to false because they genuinely have no menu protocol; Windows, macOS and GTK keep the default. The values above are pinned by a test (published_os_capability_matrix_matches_the_trait_default), so they cannot drift.

The control set is not in this table, on purpose. Because every control is self-drawn, widget availability does not vary by OS — it varies by profile. That is the next table.

2. Widget availability per profile

What differs across targets is how much of the widget set is compiled in, not what the OS can draw.

Profile Widget set Registry Custom-painted controls GPU i18n
desktop 167 kinds (full) ✅ wgpu
tablet 167 kinds (full) ✅ wgpu
mobile 167 kinds (full) ✅ wgpu
embedded reduced core set — software
mini reduced core set — software

A is absent, not degraded: the module is compiled out, so supports_custom_widgets() returns false and callers are expected to refuse the operation rather than mount into a blank surface.

The reduced embedded/mini set is: Window, Button, CheckBox, RadioButton, Label, LineEdit, ComboBox, SpinBox, ListBox, ProgressBar, Slider, ScrollBar, ScrollArea, Panel, Frame, GroupBox, TileView, Line, Meter, MiniChart, ImageView, MiniCanvas, Arc, Spinner, Roller, Dropdown, TextArea, Keyboard, Switch.

3. What "support" means per OS

Reading the two tables together:

Concern Varies by OS? Varies by profile?
Control appearance ❌ (self-drawn)
Which controls exist
DPI scaling / IME / a11y
Native menu bar
File/colour/font dialogs ✅ (host-provided)
Rendering backend ✅ (GPU vs software)

So an app that avoids OS-specific APIs is portable by construction: build it once per profile, and it renders the same everywhere.


Architecture

┌────────────────────────────────────────────────────────────┐
│  API Layer — lib.rs + compat.rs (core/alloc bridge)     │
├────────────────────────────────────────────────────────────┤
│  Widgets  │  Event System  │  Layout Engine                │
│  (30-80)  │  (EventLoop,   │  (Box, Grid, Flow,           │
│           │   Gesture)     │   Stack, Absolute)            │
├───────────┴────────────────┴──────────────────────────────┤
│  i18n  │  Theme  │  Signal System  │  Control Backend       │
├────────────────────────────────────────────────────────────┤
│  Rendering: SoftwarePaintBackend / SvgPaintBackend / GPU   │
├────────────────────────────────────────────────────────────┤
│  Platform: Windows │ macOS │ Linux │ iOS │ Android │ WASM  │
└────────────────────────────────────────────────────────────┘

Features

Rust-Native Design

  • no_std-ready architecture: all files import shared types via compat.rs (core/alloc) so enabling #![cfg_attr(feature = "mini", no_std)] is a tracked step — the mini profile currently compiles on std.
  • compat.rs bridge: HashMap→BTreeMap, lightweight-profile lock compatibility, MiniVec<T,64>, MiniString<256>, MiniArena (bumpalo)
  • enum WidgetKind + trait Widget + trait Draw + trait EventHandler — zero-cost abstractions
  • Builder pattern: Style::new().bg_color(RED).pad_all(8).build() — compile-time checking

Rendering Backends

  • SoftwarePaintBackend: CPU rasterizer (RGBA framebuffer), used by mini/embedded
  • SvgPaintBackend: SVG pipeline output for testing and documentation
  • GPU (wgpu): Hardware-accelerated for desktop/tablet/mobile

Touch & Gesture

  • 11 gesture recognizers: Tap, DoubleTap, LongPress, Swipe, Pan, Fling, TwoFingerTap, TwoFingerSwipe, LongPressDrag, Pinch, Rotate
  • Touch-target expansion for small widgets on touch devices

Layout

  • Box, HBox, VBox, Grid, Form, Stack, Flow, Absolute, Anchor, Masonry
  • Device-adaptive layout scale, font scale, and minimum touch size

CSS Styling

  • CSS parser + selector engine (CssParser, CssSelector)
  • Widget::apply_css(css, class) — per-widget CSS application
  • StyleSheetManager — global stylesheet registration
  • CssWatcher — polling-based CSS hot-reload

Partial Refresh

  • DirtyRegionTracker with rectangle merging
  • render_dirty_regions() — clip-based partial redraw via push_clip/pop_clip

Internationalization

  • tr!() macro for compile-time key-based translation
  • en / zh-cn / zh-tw translations (30+ strings per language)
  • Context-based and plural variants
  • audit_keys() for coverage validation

Widget Library

Desktop/Tablet/Mobile (167 widget kinds)

Core: Window, Dialog, MessageBox, FileDialog, ColorDialog, FontDialog, InputDialog, ProgressDialog, PopupWindow, Button, CheckBox, RadioButton, Label, LineEdit, TextEdit, RichEdit, ComboBox, SpinBox, ListBox, ListView, TreeView, ProgressBar, Slider, ScrollBar, ScrollArea, TabWidget, Splitter, GroupBox, MenuBar, Menu, MenuItem, ContextMenu, ToolBar, StatusBar, Canvas, Table, Grid, Chart, ToggleButton

Date & Time: Calendar, DateEdit, TimeEdit, DateTimeEdit, DatePicker, TimePicker, DateTimePicker, CupertinoDatePicker, DateRangePicker, MobileDatePicker

Containers: CollapsiblePane, DockWidget, MdiArea, StackedWidget, ToolBox, TabBar, NavigationStack, PagerPageView, Carousel, BottomSheet, ModalBottomSheet

Mobile: BottomNavigationBar, NavigationDrawer, AppBar, SafeArea, PullToRefresh, RefreshControl, SearchBar, CupertinoSwitch, CupertinoSlider, CupertinoNavigationBar, CupertinoSegmentedControl, AdaptiveScaffold

Input: CommandLink, FontComboBox, KeySequenceEdit, MaskedEdit, AutoCompleteEdit, MultiSelectComboBox, EditableComboBox, RangeSlider, FloatingLabel, TagInput, InplaceEditor, SearchBox, ShortcutEditor

Display: LCDNumber, Dial, ProgressCircle, Rating, Icon, Sparkline, Tooltip, Badge, Chip, Avatar, SkeletonLoader, EmptyState

Charts: LineChart, BarChart, PieChart, Sparkline

Web: WebView, WebEngineView, WebEnginePage, WebEngineSettings, WebEngineDownloadItem, WebEngineCookieStore, WebEngineWebChannel, WebEngineFindTextResult, WebEngineNotification, WebEngineScriptDialog, WebEngineContextMenuRequest

Menus: PieMenu, RibbonBar, MenuButton, DropdownMenu, Popover, SegmentedButton

Special: FreeformShape, QRCode, ColorHistory, ColorWell, MasonryLayout, Stepper, Divider, SwipeToDismiss, Toolbox, PropertiesPanel, PropertyGrid, WizardDialog, Wizard, AnimatedImage, HeroAnimation, BezierCurveEditor, LottieWidget, RiveWidget, VideoPlayer, ImageGallery, AudioVisualizer, CameraPreview, BarcodeScanner, Breakcrumb, CodeEditor, ColorPicker, CommandEntry, CommandPalette, DiffViewer, MapView, MediaPlayer, NotificationCenter, Snackbar, SplitButton, TerminalView, ToastStack

Mini / Embedded (reduced core widget set)

Window, Button, CheckBox, RadioButton, Label, LineEdit, ComboBox, SpinBox, ListBox, ProgressBar, Slider, ScrollBar, ScrollArea, Panel, Frame, GroupBox, TileView, Line, Meter, MiniChart, ImageView, MiniCanvas, Arc, Spinner, Roller, Dropdown, TextArea, Keyboard, Switch


Widget Properties

Every control publishes its own property contract, so you can read, write and enumerate a control's state without knowing its concrete type. The same code works for a button, a chart and a code editor, on every platform.

use rust_widgets::core::Rect;
use rust_widgets::widget::{
    widget_property_get, widget_property_names, widget_property_set, WidgetFactory,
};
use rust_widgets::CapabilityValue;

let factory = WidgetFactory::new_with_defaults();
let mut button = factory.create("button", Rect::new(10, 10, 100, 30), "OK").unwrap();

// Read and write by name
factory.write_property(button.as_mut(), "text", CapabilityValue::String("Save".into())).unwrap();
let text = factory.read_property(button.as_ref(), "text").unwrap();
assert_eq!(text, CapabilityValue::String("Save".into()));

// Or enumerate the whole contract — the API for a property editor or a serialiser.
// `enabled`, `visible`, `tooltip` and `geometry` appear here for every control.
for name in widget_property_names(button.as_ref()).unwrap() {
    println!("{name} = {:?}", widget_property_get(button.as_ref(), name).unwrap());
}

Because the list comes from the control itself, it cannot go stale — and a test fails by name if a control advertises a property it will not answer.

Error semantics

Error Meaning
UnknownProperty This control has no property by that name — a caller bug.
ReadOnlyProperty The property exists but is not writable (e.g. geometry, row_count). Render a disabled field.
TypeMismatch Wrong value type, or a value out of range.
UnsupportedOnWidget The control has no contract at all. Should not occur in 2.0.0.

Reading by id (rust_widgets::widget::read_widget_property_by_id) resolves through the widget runtime, so the control must be registered first; use the id runtime::register returns. See docs/MIGRATION_GUIDE.md.


C ABI & Language Bindings

cargo build --release
clang -Iexamples examples/c_abi_poll_demo.c -Ltarget/release -lrust_widgets -o target/release/c_abi_poll_demo
python examples/python/demo_basic.py
Language Status
C
C++
Python
Java (JNI)

Core Modules

Module Description Availability
core Point, Rect, Size, Color, Font, ObjectId All profiles
widget Widget implementations All profiles
event Event types, EventLoop, GestureEngine All profiles
compat core/alloc bridge, MiniVec, MiniString, MiniArena All profiles
render SoftwarePaintBackend, SvgPaintBackend, GPU (wgpu) All profiles
layout Box, Grid, Flow, Stack, Absolute, Anchor, Masonry All profiles
signal GenericSignal, Signal1, ConnectionScope All profiles
style WidgetStyle, CSS parser, animations, theme states All profiles
object Object/class-name system All profiles
platform Windows, macOS, Linux, iOS, Android, WASM, Harmony Desktop+
gesture 11 gesture recognizers Desktop+ (touch)
i18n tr!() macro, I18nManager, en/zh-cn/zh-tw Desktop+
theme Theme manager, dark/light mode Desktop+
gpu GPU adapter detection, buffer pools Desktop+
chart Line, Bar, Pie, Scatter, Area charts Desktop+
web WebEngine, WebView, JS engine Desktop+
pdf PDF document creation Desktop+
print Print support Desktop+
performance Profiler, frame rate monitor Desktop+
memory ObjectPool, ArenaAllocator, BufferPool Desktop+

Build Requirements

Profile Rust Version Dependencies
Desktop 1.87+ wgpu, GTK/Wayland (Linux), objc2 (macOS)
Mini 1.87+ heapless, hashbrown, bumpalo (no_std-ready; profile compiles on std)
Embedded 1.87+ None (software-only)

Image codecs and cross-compilation

AVIF support uses the pure-Rust avif codec (ravif), not avif-native, so building mobile/tablet/desktop for a foreign target does not require a dav1d sysroot or cross-configured pkg-config. Earlier releases pulled in dav1d-sys, which failed to cross-compile for Android/iOS/wasm unless a pkg-config sysroot was set up by hand.

The trade-off is decode speed: the pure-Rust codec is slower than the C dav1d backend, and it adds ~15 build-time crates (rav1e et al.).


Performance

Metric Desktop Mini (target)
Binary size ~5MB < 100KB
RAM (typical) < 100MB < 32KB
Frame rate 60 FPS 30 FPS
Widget creation < 1ms < 0.1ms

License

MIT License — see LICENSE.

Support