Shared Rust UI descriptions for Dioxus and immediate GPU rendering, alongside the existing Dioxus component library.
| Crate | Responsibility |
|---|---|
ui-kit-core |
Typed component tree, IDs, theme, layout and action dispatch |
ui-kit-dioxus |
Native HTML controls and Dioxus event adapter |
ui-kit-immediate |
Pointer/focus state, semantic actions, clipped drawing commands |
ui-kit-wgpu |
Shared device/queue, texture targets, materials and rendering extensions |
ui-kit-window |
Custom window chrome, optional immediate/Dioxus rendering, and reusable winit integration |
ui-kit-sensorial |
Sensorial's look for immediate/wgpu apps: the living backdrop, glass, palette, type, springs and the look's own controls |
ui-kit-code-editor |
A code editor for immediate apps: syntax colours (syntect), line numbers and a language picker over the text area, with its look as data |
ui-kit |
Existing Dioxus components, plus shared and SharedView exports |
The new core and hosts have no dependency on Prism. The Prism implementation is
still present in its repository; adoption there can happen separately. Existing
ui-kit::Button, Slider, TextInput, etc. signatures remain unchanged.
The application owns its state and actions. The view contains descriptions and action mappings, not Dioxus signals or GPU handles.
use ui_kit_core::*; // also available as ui_kit::shared::*
#[derive(Clone, PartialEq)]
enum Action { Rename(String), Volume(f64), Enabled(bool), Save }
fn settings(name: &str, volume: f64, enabled: bool) -> View<Action> {
panel([
text("Settings"),
text_input("name", "Workspace name", name).on_input(Action::Rename),
slider("volume", "Volume", volume)
.range(0.0, 1.0, 0.05)
.on_change(Action::Volume),
row([
checkbox("enabled", "Enable saving", enabled).on_toggle(Action::Enabled),
button("save", "Save").disabled(!enabled).on_activate(Action::Save),
]),
]).padding(24.0).gap(16.0)
}IDs must be unique across the tree and stable across updates. Compose views with
functions, column, row, panel, and dynamically generated child iterators.
Handlers accept action constructors or Send + Sync + 'static closures. This
in-process view is not a serialized wire protocol; action handlers are closures.
use dioxus::prelude::*;
use ui_kit::SharedView;
#[component]
fn SettingsScreen() -> Element {
let mut state = use_signal(Settings::default);
rsx! {
SharedView {
view: settings(&state.read().name, state.read().volume, state.read().enabled),
on_action: move |action| state.write().update(action),
}
}
}Settings and its reducer belong to your application; the runnable gallery has
a complete implementation. SharedView uses native buttons and inputs, preserving
browser keyboard behavior, text editing, selection, clipboard, IME and semantics.
It maps events through View::dispatch, including disabled/loading guards and
slider range/step normalization. DOM text input has an accessible label.
use ui_kit_core::{Rect, Theme};
use ui_kit_immediate::{ImmediateHost, composition::UiRenderer};
use ui_kit_wgpu::{GpuContext, WgpuRenderer, WgpuFrame, TextureTarget};
// Initialize once using your application's Arc<Device> and Arc<Queue>.
let gpu = GpuContext::new(device.clone(), queue.clone());
let mut renderer = WgpuRenderer::new(&gpu, wgpu::TextureFormat::Rgba8Unorm)?;
let target = renderer.create_texture(800, 600)?;
let mut host = ImmediateHost::default();
// Each frame, from the same application view:
let view = settings(&state.name, state.volume, state.enabled);
let frame = host.frame(
&view,
&Theme::default(),
input,
Rect { x: 0.0, y: 0.0, width: 800.0, height: 600.0 },
)?;
for action in frame.actions { state.update(action); }
let mut encoder = device.create_command_encoder(&Default::default());
renderer.render(&mut WgpuFrame {
encoder: &mut encoder,
target: TextureTarget::from_texture2d(&target)?,
clear: Some(wgpu::Color::TRANSPARENT),
}, &frame.commands)?;
// Add other passes, including sampling target.view on a world-space panel.
queue.submit([encoder.finish()]);ImmediateHost persists focus and capture. Input supplies an optional panel-pixel
pointer, button-down state, committed text, backspace, forward Tab, activation, and
signed keyboard step for sliders. Submit press/release frames in order. The host
emits actions; it never owns or mutates application values. Drawing can be repeated
for other targets without advancing interaction. Changed application values are
reflected by rebuilding the next view.
spatial::WorldPanel maps a controller ray to panel pixels. Use the resulting
point as Input.pointer; the rendering layer does not own a headset session.
The immediate text field keeps a caret and a selection: the pointer places and
drags them, a double click selects all, arrows/Home/End move them (Shift extends),
and typing, Backspace, Delete, paste, copy and cut act at them. Text an input
method is composing (Input.preedit) is shown at the caret until it commits.
Input also carries the wheel (scroll), the secondary button, the keys pressed
(keys, with modifiers), time for double clicks, delays and animation, the
viewport popups keep inside, clipboard requests (copy, cut, paste) and
access_actions from screen readers. After Ui::end, Ui::output() says what
the frame asks of the host: the cursor, text for the clipboard, whether it
wants_keyboard, the ime_area, and repaint_after while something animates.
With winit, ui_kit_window::InputCollector (feature winit + immediate) does
both directions: feed it every WindowEvent, take the input each frame, and
apply the output to the window. Feature clipboard connects the system
clipboard; feature accesskit adds AccessibilityBridge, which publishes
Ui::accessibility() to screen readers and turns their requests into input.
ui.scope("left", |ui| ..) prefixes every id used inside, so a component can be
drawn twice. A duplicate id stops debug builds and is listed by
Ui::duplicate_ids in release. ui.with_style, ui.scoped and ui.with_clip
restore what they change. Ui::theme holds the named styles widgets use --
Theme::button(Variant), rows, popups, tooltips, fields -- built from a
Palette with Theme::from_palette. ui.animate(id, target, seconds) eases a
value; ui.remember/ui.remembered keep a size across frames, and
ui.painted_since(ui.mark()) measures what was just drawn.
Beyond buttons, checkboxes, sliders, fields, comboboxes and number pickers:
Toggle, RadioGroup, SegmentedControl, Tabs, TreeView, Table (sortable,
only visible rows drawn), Menu::button, Menu::context (right click),
MenuBar, ui.popup (nested, flips and slides to stay on screen), ui.tooltip,
ui.modal, ui.toast/ui.show_toasts, ui.image (contain, cover or fill; the
renderer draws textures registered with register_image), ScrollArea (both
axes), VirtualList, drag and drop (drag_source, drop_target) and keyboard
shortcuts (ui.shortcut). Built-in widgets describe themselves for
accessibility.
Harness drives a view by control id: click("save", view),
type_text, key, scroll, drag, right_click, double_click, and reads
back rect(id), find_text and the accessibility nodes.
let mut theme = Theme::default();
theme.panel.material = Material::Glass { blur: 12.0 };
theme.control.accent = [0.4, 0.9, 0.8, 1.0];Both adapters use typed colors, corner radius, foreground, font size and materials.
Nodes can override a style with .styled(style). Browser glass uses CSS backdrop
blur; wgpu uses a nine-tap snapshot blur. These approximate the same appearance,
not identical pixels. wgpu renders system fonts by default; call load_font(bytes)
on platforms without system fonts or to supply application fonts.
WgpuRenderer accepts a caller-owned encoder and a full base-mip 2D texture view.
Supported formats are RGBA8/BGRA8 unorm, linear/sRGB, and RGBA16 float, with one
sample and one layer. UI records commands without submitting, polling, acquiring
a surface, or creating a device. clear: None overlays previous scene rendering.
Glass requires COPY_SRC. Transparent textures contain premultiplied RGB; use
premultiplied blending when compositing them. All resources must share the device.
Implement UiRenderer<Target> to replace the command backend, or compose partial
wgpu overrides with WgpuRenderer::with_custom(&gpu, format, (effect_a, effect_b)).
Each CommandRenderer receives context, frame and command; return true when
handled or false to delegate. Override CommandRenderer::wants to name the
commands a renderer handles: the rest are then drawn in batches -- one pass for a
run's shapes and one for its text -- instead of a pass apiece.
Style::overflow chooses whether text wraps, clips or ends in an ellipsis. Install
renderer.text_measure() on a Ui with set_shared_text_measure so
Ui::text_width, fit_text and elide use the fonts the renderer draws with;
load_font adds a font to both. Honor the command clip and record into the provided
encoder. Unknown kinds/materials are errors; the Dioxus host displays an alert for
unsupported custom materials. Native GPU shaders are not translated into CSS.
ui-kit-sensorial is the look of the Sensorial pages, for any immediate/wgpu
application and every component: black, lit by light split as through a
prism, with everything on it made of glass that frosts what is behind it,
bends it at the rim and splits it into colours there.
use ui_kit_sensorial::{Atmosphere, SensorialRenderer};
// Once: the world's state, shared with the renderer, and the renderer
// before the vector one (the look draws its hairlines and icons as meshes).
let atmosphere = ui_kit_sensorial::shared(Atmosphere::new(0.55)); // the brand's hue
let renderers = (SensorialRenderer::new(&gpu, atmosphere.clone()),
VectorRenderer::new(&gpu, format));
fonts.load_font(ui_kit_sensorial::FONT.to_vec());
fonts.prefer_family(FontFamily::SansSerif, &ui_kit_sensorial::FAMILIES);
// Each frame:
atmosphere.borrow_mut().advance(dt, [width, height], scale, input.pointer);
atmosphere.borrow_mut().scrolled(scroll, velocity);
ui.theme = ui_kit_sensorial::theme();
ui.begin(input);
ui_kit_sensorial::paint_backdrop(&mut ui, screen);
// ... any components ...
let commands = ui_kit_sensorial::lower(ui.end().to_vec(), &ui.theme.palette);paint_backdroppaints the world: a fan of faint spectral rays from above, pools of coloured light drifting far away, a haze, and dust at three depths that streaks and splits into colours as the page scrolls fast. It drifts with time, rises with the scroll, leans to the pointer and flashes onAtmosphere::flash;litfades it from black.SensorialRenderer::with_lightadds a layer of light the application renders into it (the pages' 3D logo).theme()is the palette -- black, white at a few strengths, a white accent with near-black on it -- and glass for the built-in widgets' panes, cards, popups, fields and buttons.lowermakes every component glass: a surface filled in the palette'ssurfaceorraisedcolour, or inMaterial::Glass, becomes a pane of glass with the rest of the command (label, value, focus ring) over it, buttons become pills, and popups and dialogs dark frosted glass. The primary button stays the one solid white thing; disabled, square or tiny surfaces stay flat.GlassLookis glass of any kind (control,card,pane,bar,chip,popup), painted withGlassLook::paint;AT_POINTERmakes its highlight follow the pointer.Typeandscaleset type in Prism Sans (FONT): display, headings, lead, body, eyebrow capitals;spectral_lineis the hairline of spectrum things are underlined with.Springswith themotionfeels (HOVER,PRESS,ENTRANCE,REVEAL) are how things move;Pill,Link,GlassCardandeyebroware the look's own controls, moving by them.
The target must allow COPY_SRC and be viewed in its own format, for glass to
read what is behind it. Glass costs a copy and a few blur passes per surface.
The shared model includes Text, Button, Checkbox, Slider, TextInput, Toggle, Select, Progress, Separator, Panel, Row and Column, rendered by both hosts. Legacy tables, graphs, calendars and other existing components remain on the original Dioxus API for incremental migration.
The supported layout subset is fixed or fill widths, fixed or intrinsic heights, rows/columns, padding and gaps. Rows divide remaining width equally between fill children. Overflow clips. Wrapping, scrolling, grids, responsive breakpoints, reverse Tab, and overlapping interactive widgets are not implemented yet. The DOM adapter uses the corresponding CSS rules; glyph metrics can differ by platform.
Immediate widgets take a Rect, but nothing should compute one by adding offsets.
ui_kit_immediate::layout says how big things are and works out where they go;
the shared layout above is solved by the same Flex.
use ui_kit_immediate::{Flex, Insets, Size, Strip, Stack, Grid};
// A bar: fixed controls at both ends, the seek bar in whatever is left.
let mut bar = Strip::row(bounds.inset_by(Insets::symmetric(8.0, 0.0)), 4.0);
let play = bar.leading(32.0);
let volume = bar.trailing(84.0);
let open = bar.trailing_if_room(68.0, 120.0); // gives way on a narrow bar
let seek = bar.rest();
// A run solved in one pass: fixed items, gaps, then fills by weight.
let [dec, value, inc] = Flex::row(0.0).split(field, [Size::Fixed(28.0), Size::FILL, Size::Fixed(28.0)]);
// Rows down a scrolling card, and tiles wrapped into as many columns as fit.
let mut rows = Stack::new(content, 6.0);
let heading = rows.row(20.0);
let tiles = Grid::fit(area, 120.0, 8.0);
let third = tiles.cell(2);Rect adds align, centered, center_vertically, inset_by, split_top,
split_bottom and overlaps. Ui::text_width / Ui::fit_text size a control
to its label; install a shaping measure with Ui::set_text_measure, otherwise
the width is estimated from the character count.
ui_kit_immediate::components has the immediate counterparts of the Dioxus
components, drawn in the colours of ui.theme.palette (components::theme::theme(dark)
builds the kit's light and dark looks). Stateless ones are builders with a
show method; stateful ones take a state value the application owns:
use ui_kit_immediate::components::{Button, ButtonVariant, Dropdown, Menu, MenuItem, MenuState};
use ui_kit_immediate::icon::Icon;
ui.theme = components::theme::theme(dark);
if Button::new("Save").icon(Icon::Check).show(&mut ui, "save", rect).clicked { /* ... */ }
Dropdown::new(&["Rust", "Go"]).show(&mut ui, "language", field, &mut language);
if let Some(id) = Menu::horizontal(&items).show(&mut ui, "nav", bar, &mut menu_state) { /* ... */ }Inputs: Button, TextInput, NumberField (one number, typed and held to
a range, for what a slider cannot say exactly), Select, Dropdown,
Checkbox, Switch, Slider, OtpInput, DateTimePicker, ColorPicker,
ColorField, EditableText. Feedback: Label (with ellipsis() for one-line text),
Badge, Notification, ProgressBar, Spinner, StatusDot (a dot with a
breathing ring while something is alive). Containers: Card, Modal,
Browser, Table, PipelineBoard, Timeline, Bill, MetricCard,
FormFlow, JobList (work something is running or has queued, grouped, with
pause, resume and stop on each job and each group; the application maps its
own work onto Job and JobGroup and acts on the JobAction it returns), plus
card_header, stat_row and empty_state for the parts of a card that are
the same everywhere. Navigation: Menu (horizontal or
vertical; children cascade into submenus) and Sidebar (a column of sections
with icons, badges and a highlight that slides to the chosen one; icons only
when narrow). Charts and graphs: Sparkline, AreaChart (a series over a
filled area, with guide lines, a pinnable range, a reference line and a
window it scrolls through), TimeSeries (the last seconds of a live value,
sampled at a steady rate for an AreaChart, with readouts held long enough
to read), Gauge (a ring filled
to a fraction, coloured by how full), BarChart (bars on a shared scale with
a caption each), SegmentedBar (parts of a whole laid end to end, the rest
left as track, with tooltips and a legend), SlotLane (capacity as slots in
use and a count waiting), PieChart, StackedBarChart, FlowGraph,
NetworkGraph, HierarchyGraph, GraphNode, GraphCamera. Time: CurveEditor
(see below). Menus, dropdowns, pickers and dialogs draw over the
page on their own layers and take the pointer from what is under them, so they
can be shown from anywhere in a frame; set Input::viewport so they stay on screen.
ui_kit_core::animation is the model a curve editor edits and a renderer
reads, with no UI in it: a Track is an id, a name, a range, a unit and a
sorted run of Keyframes, and Track::value_at(time) is the one question
worth asking of it. A key says how the segment leaving it is shaped --
Hold, Linear, Smooth (tangents worked out from the neighbours, held so
the curve never swings past the keys it joins) or Bezier (tangents dragged
by hand) -- and an Animation is the tracks of one thing, looked up by id.
Time is whatever the caller counts in.
components::CurveEditor is the editor: a track list down the left, time
across the top and the curves in the middle. Keys are dragged (shift holds
the time, alt holds the value, and they snap to whole frames), double
clicking a curve adds one, Delete takes one away, the toolbar reshapes the
segment leaving the selected key, the wheel zooms about the pointer and a
drag on empty space pans. The selected key's time and value are also fields
in the toolbar, for the numbers a pointer cannot land on exactly; what is
typed there is taken as written, rather than snapped. Tracks are drawn against their own ranges,
normalised, so degrees and multiples share one box.
What can be animated comes from the application as TrackOptions -- an id, a
name, what the number is worth now, its range and its unit -- and the "Add
parameter" menu turns the chosen one into a track keyed at the playhead. The
editor never learns what any of them mean, so making one more number
animatable is one more TrackOption.
use ui_kit_core::animation::Animation;
use ui_kit_immediate::components::{CurveEditor, CurveEditorState, TrackOption};
let options = [TrackOption::new("camera.fov", "Field of view", 50.0).range(10.0, 120.0)];
let response = CurveEditor::new(duration)
.fps(fps)
.options(&options)
.show(&mut ui, "curves", rect, &mut animation, &mut state, &mut playhead);
if response.scrubbed { /* the playhead moved */ }
if response.changed { /* a curve changed */ }ui_kit_core::motion has springs solved in closed form. A Spring is a
response (roughly how long it takes, in seconds) and a damping ratio; the
house springs overshoot by well under one percent. A SpringTrack is a list
of target changes known up front, and its value at any time is the sum of one
spring step per change, so it can be seeked, rendered out of order and looped
(looped(period) lets the springs still settling at the end of a lap carry
into the next, so the seam matches in position and speed). jump and kick
let a dragged value go from wherever it was, Blend crossfades between
states with weights that always sum to one, BeatGrid places things on a
song's beats and rubber_band resists a drag past its limit.
ui_kit_core::morph builds on it. A Scene is a stack of layers (opacity,
blur) of rounded rects, one-line text, stroked paths and filled outlines on a
square stage. Reel is a choreography: one shape through twelve UI states in
seven bars (button, loader, check, dynamic island, music player with a
play/pause morph and a scrub, a volume slider that stretches past max, a
toggle, a liquid tab indicator, a chart that draws itself with a tooltip,
⌘K, a filtered command palette and a toast), driven by a cursor, where
Reel::seek(t) is a pure function of time. Reel::cues says where each UI
sound belongs. One scene, two renderers:
// Immediate: draw commands (vector meshes need `VectorRenderer`).
ui_kit_immediate::components::MorphReel::new(&reel).show(&mut ui, rect, time);
// Dioxus: SVG, with real Gaussian blur on the fading content.
rsx! { ui_kit::MorphReel { time, accent: "#3b82f6".to_string() } }The immediate renderer has no blur for text, so a blurred layer is drawn as faint copies around a disc that add up to its opacity. Both galleries have a "Morph Reel" section with play, scrub, beat stepping, an accent switch and a jump to each state.
examples/morph-reel renders it without a window:
cargo run --release -p morph-reel -- beats target/beats # one PNG per beat + sheet.png
cargo run --release -p morph-reel -- frame 7.25 target/frame.png
python examples/morph-reel/render.py --song path/to/song.mp3 --laps 2render.py finds the song's tempo and first downbeat with numpy (spectral
flux autocorrelation, then attacks at millisecond resolution), measures each
UI sound's peak, mixes the song from that downbeat with every sound's peak on
its cue, renders the beat sheet for review, then renders 1440 × 1440 at
60 fps from four subframes per frame blended by ffmpeg tmix. Without
--song it synthesises a placeholder 120 BPM track and UI sounds; give it a
royalty-free song (and --sounds <dir> of press, release, click,
toggle, success, type, enter, pop WAVs) for a real cut. It needs
ffmpeg on the path and Python with numpy.
ui_kit_immediate::vector paints antialiased meshes (fill_triangles, line,
polyline, circle). svg::Svg::parse reads an SVG once and tessellates its
fills and strokes; paint and paint_tinted then place it at any size.
icon::Icon is a set of line icons (chevrons, arrows, close, plus, check,
calendar, loader, play, pause, stop, trash, refresh, folder, file, save,
settings, cpu, download, upload, flame, snowflake, zap, search, copy, external
link, layers, activity, list, terminal, gauge, clock, undo, info, alert,
database, hard drive, power, ...) drawn from SVG, so no control depends on a
font having a glyph. Meshes are drawn by ui_kit_wgpu::VectorRenderer:
WgpuRenderer::with_custom(&gpu, format, VectorRenderer::new(&gpu, format)).
EditableText places its caret with ui_kit_wgpu::ShapedRichText.
Standalone immediate-mode gallery (native winit window, wgpu rendering):
It uses ui-kit-window for the custom title bar and native window interactions.
See the winit composition guide to reuse the
same setup in another application.
cd D:\dev\sensorial\systems\ui-kit
cargo run -p immediate-galleryThe window has light/dark themes, glass strength, scrolling, menus, browser tabs,
forms, loading states, dialogs, metrics, tables, planning views, invoices and
graphs. Mouse wheel and Page Up/Down scroll; Ctrl+Home/End jump to the ends;
Escape closes overlays. Shift-click selects pipeline cards for group dragging.
The application supplies the wgpu device/queue and renders into a reusable
texture before presenting. Gallery::frame builds commands separately from
the window host from ui_kit_immediate::components; VectorRenderer composes
vector drawing with the standard renderer through CommandRenderer. Snapshot a
state with cargo run -p immediate-gallery -- --snapshot out.png, adding
--click LABEL, --drag FROM TO, --section NAME, --dark or --sensorial.
The theme dropdown's "Sensorial" shows every component in Sensorial's look,
over its living backdrop; --sensorial opens the window in it, in its face.
The existing Dioxus gallery is still its own DOM/CSS renderer:
cd examples/gallery
dx serve --web --port 8096The native gallery follows that gallery's sections and theme, but is not yet
pixel-identical or feature-equivalent for every advanced component.
Date/time editing now supports month navigation, leap years, validated time,
and Apply/Cancel. Color editing supports Canvas, Presets, Hex, RGB, HSL, and
alpha, both inline and in a popover. Ctrl/Cmd+A replaces a whole text field.
Hierarchy editing adds, renames, and deletes subtrees with stable IDs. The
survey follows the reference role-based branch and exposes its submitted
answers through Gallery::survey_result.
The native page is currently built from immediate primitives; it does not imply that all legacy components have been added to the shared semantic tree. The smaller shared gallery below demonstrates the single-definition API supported by both adapters.
Capture the native renderer without opening a window:
cargo run -p immediate-gallery -- --snapshot target/immediate-gallery.png
cargo run -p immediate-gallery -- --snapshot target/immediate-dark.png --dark --section "7. Planning Components"
cargo run -p immediate-gallery -- --snapshot target/calendar.png --click "2026-07-16 18:00"
cargo run -p immediate-gallery -- --snapshot target/color.png --click "#3b82f6" --click HSLSnapshots use the same frame builder and renderer as the live window.
Repeat --click to drive a sequence of controls (exact labels take precedence
over prefixes). The picker, hierarchy, and survey implementations are separated
into pickers.rs, hierarchy.rs, and survey.rs in the native example.
Live shared gallery:
cd examples/shared-gallery
dx serve --web --features web --port 8098From the repository root, generate a GPU PNG and static side-by-side HTML from the
same settings_view:
cargo run -p shared-gallery --example texture --features gpuOutputs: target/shared-gallery.png and target/shared-gallery.html. The HTML
comparison is static; use the live gallery for application interactions. GPU
readback and waiting occur only in the example/tests.
cargo test -p ui-kit-core -p ui-kit-immediate -p ui-kit-dioxus -p ui-kit-wgpu -p shared-gallery --features shared-gallery/gpu
cargo test -p ui-kitTests cover action parity, range rules, disabled/loading states, native DOM semantics, layout, input capture, Unicode editing, VR ray mapping, shared GPU handles, texture pixels, clipping and glass transparency. GPU tests need an adapter.
The original comprehensive Dioxus gallery remains in examples/gallery.
Rich text uses ui_kit_core::RichText (formatted runs) and RichTextEditor
(selection, grapheme-aware editing, formatting, undo/redo). The native gallery
adds a toolbar, hit testing, keyboard navigation, and Save/Cancel. Lists currently
insert text markers; the editor does not yet implement clipboard integration or
scrolling inside long documents. Flow and network graphs support selection,
zoom buttons, Fit, and background dragging. Both network renderers use the same
deterministic ui_kit_core::graph::network_layout function.
A formatted document can be displayed by either renderer:
let document = ui_kit_core::RichText::plain("Hello");
// Native GPU command (application controls placement and frame submission):
ui.rich_text(bounds, ui_kit_immediate::RichTextPaint {
document: document.clone(), cursor: None, anchor: 0,
});
// Dioxus DOM/CSS component:
rsx! { ui_kit::RichTextView { document } }RichTextView displays shared formatted runs; the existing Dioxus editable-text
component remains its own DOM editor. Sharing document/layout logic does not
replace either renderer.
Native interaction/rendering details: drag the right-hand scrollbar thumb (or
click its track), and drag graph backgrounds to pan. Pointer movement requests
an immediate redraw; animations use a 60 Hz timer. Graph and chart meshes have
one-physical-pixel antialiasing at their outer edges. Control labels are centered
using glyph ink bounds, and RGB channel edits preserve untouched channels.
Run the opt-in rendering diagnostic with:
cargo test -p immediate-gallery --test render profile_flow_frames -- --ignored --nocaptureIt reports CPU and GPU time for warmed flow frames, including a synchronous GPU wait; it is a diagnostic, not a timing assertion or a live-window latency measure.