Repository navigation
SUI 0.6.0
This release makes the JavaScript package async and runs it in browsers on a
WebAssembly build, removes the APIs deprecated in 0.4, and makes small-change
frames much cheaper: retained layers scroll on the compositor, draws batch
across packets with clips applied in shaders, and windows on every platform
redraw only what changed.
Upgrading from 0.5
- Update the SUI crates together from
0.5to0.6, andsinomo-ui-lucideto
1.47.3, which bundles the same Lucide 1.47.0 icons for the0.6family. - Clear 0.5's deprecation warnings before updating: each names its
replacement, and 0.6 removes the deprecated APIs.
Breaking changes
- Removed the APIs deprecated in 0.4, as the 0.4 upgrade notes announced.
Code that compiled without deprecation warnings is unaffected. Use the
replacements the warnings named:- Types:
Modal→Dialog,Drawer→SideSheet,BusyIndicator→
Spinner,ComboBox→Select,SpinBox→NumberInput,
MultilineTextInput→TextArea,Divider→Separator,DataGrid→
Table,PathBar→Breadcrumb,ResizablePane→SplitView, and the
CanvasAppearance,CanvasRulerAppearance,PixelCanvasAppearance, and
ColorPickerAppearancealiases → their*Colorstypes. The unused
Stylestruct is gone. - Builder methods:
spacing→gap,line_spacing→cross_gap,
radius→corner_radius,disabled()→enabled(false),
appearance→colors,shown→open,collapsed()→
expanded(false),without_border()→show_border(false),
content/with_content/with_child→childoritem,
activate_with_content→activate_with_child,subtitle→detail,
TreeItem::children→items,Select::expanded→open,
Icon::labelandImage::label→semantic_name, and the checked-state
nameson,on_when,selected,on_toggle, and
on_select→checked,checked_when, andon_change(and
on_select_with_ctx→on_change_with_ctx).FloatingStack::theme,
which had no effect, is gone. - Accessors and setters:
child()→child_pod(),items/items_mut/
item/set_itemonFlex→ theirflex_*names,is_on/is_selected
→is_checked,set_on/set_selected→set_checked,is_shown/
set_shown→is_open/set_open,label()→current_label(),
color()→current_color(),TextInput::name()→accessible_name(),
andstate()on workspaces →shared_state(). dynamic(fallback, reader)constructors →new(fallback)with
text_when,label_when, ordocument_when;Label::styleand
style_when→text_styleandtext_style_when;
RebuildOnChange::newandnew_observable→key_whenandkey_from.register_builtin_icon_resourcesis gone; built-in icons need no
registration.RadioButton::on_select, which fired on every activation, has no
replacement;on_changereports only actual changes.
- Types:
- The JavaScript package
@sui/uiis now ESM and async-first, and needs
Node.js 20.19 or newer (CommonJS code can stillrequireit):App.run()returns a promise and runs the app on Node's event loop
instead of blocking; pass{ onReady(ui) { … } }instead of calling
App.runWithHandle.UiHandle.post,callLater, andcallEvery,Timer, andUiTaskQueue
are gone; Node's own timers and promises work while the app runs.UiHandle.showFileDialog(options)returns a promise of the paths instead
of taking a callback.- Widget factories are
lowerCamelCaseonly; thePascalCasepositional
functions such asButtonare no longer exported. RunningApp.windowId(index)is nowwindowHandle(index).
JavaScript
- Added a WebAssembly build that runs the same API in browsers: build it with
cargo xtask web build, and bundlers and browsers load it through the
package'sbrowserexport condition.App.run({ canvas })draws on a
canvas and keeps it the size of its container. - Callbacks may be
async, and errors from callbacks, including rejected
promises, go tosetErrorHandler(by defaultconsole.error) without
stopping the app. - Added promise helpers:
app.once(name),state.changed(), the
state.changes()async iterator, andrunning.idle()to wait for async
callbacks in tests.App.runand listeners take anAbortSignal. app.onreturns aSubscription; it andStateSubscriptionare
disposable withusing.- Builders such as
Window.root()andApp.window()return their object for
chaining;Point,Size,Rect, andColorhaveequalsandtoString;
addedColor.rgba,Color.black,Color.white,version, and
RunningApp.saveScreenshot. - Fixed
rebuildOnChange, which rendered nothing, and custom widgetevent
callbacks that return nothing, whose context requests were dropped. - A virtual table's
selectedstate that holds a string now receives the
selected key as a string, matching the key type of its callbacks. - Added
app.fontUrl(url)to fetch and register a font, and
FontHandle.family(also in Python) to read a registered font's family
name. Invalid font data now fails at registration. - Added a
node:testsuite that runs on both the addon and the WebAssembly
build.
Python
- The sdist places the license file at its root, where its metadata declares
it. The release workflow checks each sdist's contents and metadata, and
runstwine check, before uploading it. - The examples open interactive desktop windows with
App.run()instead of
printing snapshots, and paint the theme's window background with a root
surface, as the README's examples now do.
Rust
- Added
DesktopPlatform::start_pumpedandPumpedDesktop, and
App::start_pumped_with_handle, to drive the desktop event loop one turn at
a time from another framework's event loop. - Added
DesktopPlatform::start_webandwith_web_canvas, and
App::start_web_with_handle, to run in the browser on a chosen canvas and
learn when the app exits. - Fonts can be WOFF or WOFF2 as well as TrueType or OpenType, in Rust and in
the bindings. AddedRegisteredFont::try_from_bytes, which checks the data,
andRegisteredFont::family_name;font_bytesandregister_font_bytes
now fail for data that is not a font. - Offscreen renderers share compiled pipelines, and the shared cache did not
tell single-source and dual-source text pipelines apart. A renderer without
dual-source blending could leave its text pipeline for later renderers,
which then drew LCD text in grayscale. Text pipelines are now cached per
variant. - On Windows, closing the last window no longer returns from
runbefore the
window is destroyed, which left it on screen until the process exited. ContextMenukeeps its panel positions, and with them its paint,
semantics, and hit testing, when a parent such as a toolbar measures it
again after arranging it.VirtualTablepublishes its viewport only when arranged, not when a parent
measures provisional sizes, which made layout observers alternate between
sizes forever.- Touch input no longer opens tooltips, which could cover the menu or action
a finger just activated.
Performance
- On Android, a focused window no longer presents every display frame while
its UI is unchanged. Desktop windows keep presenting so that variable
refresh rate does not lower the monitor's refresh rate. - In the browser, a focused canvas no longer wakes on every display frame
while nothing changes, matching Android. - Bundled fallback fonts on the web and Android, and fonts registered by the
app, are shared with the text system instead of copied into the heap. - Release web builds run Binaryen's
wasm-opt -Os(Trunk for the demo,
cargo xtask web build --releasefor the JavaScript package when
wasm-optis installed orWASM_OPTis set) and enable WebAssembly SIMD.
The demo module drops from 16.6 MB to 11.7 MB. - Per-widget layout, paint, and semantics phases only read the clock when
SUI_PROFILE_WIDGET_TIMINGSis set, and the layout graph uses a faster
hasher for widget IDs. - JavaScript widget callbacks and class-instance checks cross the
WebAssembly boundary fewer times per call. - Scrolling and translating a retained layer is now compositor-only: the
renderer reuses the layer's composed geometry and vertex buffers and moves
them with a GPU offset, and rectangular clips are applied as scissors. - Layout change detection no longer rebuilds a node index for every moved
widget, which made each scroll step quadratic in the number of widgets. - Translated layout bounds keep their exact size, and descendants that move
with their paint boundary within f32 rounding stay composition-only, so
retained content far from the origin is no longer repainted on scroll. - Text packets only compare the backdrop under themselves, so text scrolling
over a uniform background keeps its rasterized glyphs. - Added
scroll-flatandscroll-retainedfixtures tosui-bench. - Small-change frames do less whole-tree and whole-scene work:
- The renderer reorders each fragment's draws so draws that share a
pipeline and clip are adjacent, without moving any draw past one it
overlaps, and drops clip rectangles that contain the whole draw. The
controls grid goes from 1,201 to 647 draw calls per frame. - The runtime keeps the graph from before layout by swapping buffers
instead of copying a snapshot, and diffs the two graphs directly. - Stack metadata is only rewritten when it changed, so unchanged frames
keep sharing scene command lists with the renderer. - The frame image registry is reused while its images are unchanged, and
unused text layouts are pruned in batches rather than every frame. - Appending a scene to an empty one shares its commands instead of copying
them, and semantics assembly uses a faster hasher.
- The renderer reorders each fragment's draws so draws that share a
- Rectangular clips are applied in the scene shaders instead of with
scissors: every vertex and glyph instance carries its clip in framebuffer
pixels, snapped to the same edges a scissor uses, and moves with its
fragment. Draws with different clips can then share a draw call, and runs
of up to 16 consecutive packets are merged into cached groups that are
rebuilt only when a member changes. The 1,200-widget controls grid goes
from 647 to 22 draw calls per frame, which matters most in browsers, where
each GPU call goes through JavaScript. Vertex and instance formats grow by
16 bytes each. - Partial redraw: when a window renders through its persistent intermediate
target (always in browsers, and for HDR and wide-gamut output), a frame
clears and redraws only the region where its packets changed, and redraws
nothing when none did. Reordered packets, a resized target, a different
image registry, external images, frames drawn straight to the swapchain,
and damage over half the target fall back to a full redraw. - Native SDR windows (Windows, macOS, Linux, Android, and iOS) redraw
partially too: they draw into a persistent frame in the swapchain's format
and copy it to the swapchain, when the swapchain accepts copies. Frames
that change everything pay for the extra copy. The persistent frame holds
one extra window-sized texture, whichtrim_memoryfrees while the window
has no surface. - Path clips use a stencil-only
Stencil8attachment whose contents are
discarded after each pass instead of stored, which saves tile-based mobile
GPUs a write of the whole attachment per clipped pass. - Suspending a window (backgrounding an Android app) releases only its
surface and keeps the renderer's retained scene. Resuming with the same
size and scale presents the existing frame instead of relaying out and
rebuilding everything. - Added
WgpuRenderer::trim_memoryandMemoryTrimLevel. Desktop and mobile
hosts call it withBackgroundon suspend, freeing image textures,
staging memory, and hidden frames, and withCriticalon the system's
memory warning, also freeing text atlases and the retained scene, which
the next frame rebuilds. - The image texture cache expires textures unused for 2 seconds instead of
120 frames, so 120 Hz displays no longer evict twice as fast, and keeps
idle textures within a budget of 64 MB on mobile and 256 MB elsewhere.