Skip to content

SUI 0.6.0

Choose a tag to compare

@github-actions github-actions released this 30 Oct 11:17
· 14 commits to main since this release

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.5 to 0.6, and sinomo-ui-lucide to
    1.47.3, which bundles the same Lucide 1.47.0 icons for the 0.6 family.
  • 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
      ColorPickerAppearance aliases → their *Colors types. The unused
      Style struct 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 → child or item,
      activate_with_content → activate_with_child, subtitle → detail,
      TreeItem::children → items, Select::expanded → open,
      Icon::label and Image::label → semantic_name, and the checked-state
      names on, on_when, selected, on_toggle, and
      on_select → checked, checked_when, and on_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_item on Flex → their flex_* 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(),
      and state() on workspaces → shared_state().
    • dynamic(fallback, reader) constructors → new(fallback) with
      text_when, label_when, or document_when; Label::style and
      style_when → text_style and text_style_when;
      RebuildOnChange::new and new_observable → key_when and key_from.
    • register_builtin_icon_resources is gone; built-in icons need no
      registration.
    • RadioButton::on_select, which fired on every activation, has no
      replacement; on_change reports only actual changes.
  • The JavaScript package @sui/ui is now ESM and async-first, and needs
    Node.js 20.19 or newer (CommonJS code can still require it):
    • 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, and callEvery, Timer, and UiTaskQueue
      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 lowerCamelCase only; the PascalCase positional
      functions such as Button are no longer exported.
    • RunningApp.windowId(index) is now windowHandle(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's browser export 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 to setErrorHandler (by default console.error) without
    stopping the app.
  • Added promise helpers: app.once(name), state.changed(), the
    state.changes() async iterator, and running.idle() to wait for async
    callbacks in tests. App.run and listeners take an AbortSignal.
  • app.on returns a Subscription; it and StateSubscription are
    disposable with using.
  • Builders such as Window.root() and App.window() return their object for
    chaining; Point, Size, Rect, and Color have equals and toString;
    added Color.rgba, Color.black, Color.white, version, and
    RunningApp.saveScreenshot.
  • Fixed rebuildOnChange, which rendered nothing, and custom widget event
    callbacks that return nothing, whose context requests were dropped.
  • A virtual table's selected state 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:test suite 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
    runs twine 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_pumped and PumpedDesktop, 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_web and with_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. Added RegisteredFont::try_from_bytes, which checks the data,
    and RegisteredFont::family_name; font_bytes and register_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 run before the
    window is destroyed, which left it on screen until the process exited.
  • ContextMenu keeps 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.
  • VirtualTable publishes 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 --release for the JavaScript package when
    wasm-opt is installed or WASM_OPT is 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_TIMINGS is 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-flat and scroll-retained fixtures to sui-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.
  • 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, which trim_memory frees while the window
    has no surface.
  • Path clips use a stencil-only Stencil8 attachment 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_memory and MemoryTrimLevel. Desktop and mobile
    hosts call it with Background on suspend, freeing image textures,
    staging memory, and hidden frames, and with Critical on 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.