Skip to content

scenix v1.10.0 — Editor And UI Tooling

Latest

Choose a tag to compare

@github-actions github-actions released this 22 Oct 12:26
1686d87

Scenix v1.10.0 — Editor And UI Tooling

Scenix 1.10.0 ships the two new optional tooling crates: scenix-editor
(owning editor sessions with exact inverse-command undo/redo, gizmo drag
math, scene-tree rows, the node inspector surface, material field editing,
asset-browser rows, timeline previews, and a versioned project metadata
exchange) and scenix-ui (a renderer-agnostic layer of docked panel stacks
solved by a space allocator, rolling renderer stats with budget policies,
tool widgets with hit testing, and bounded per-frame overlay composition).
Both build on the v1.5.0 interaction backbone instead of duplicating it —
selection stays in SceneGraph, editor metadata stays in
NodeEditorMetadata, and the inspector vocabulary stays in the core
inspector snapshot types — and neither renders a single pixel: host faces
(egui, HTML tools, native windows) draw the solved output.

Highlights

scenix-editor (new, std-only tooling crate)

  • Owning EditorSession: the scene graph plus the standard five-panel
    stack (scene tree, inspector, assets, timeline, material), the edit
    history, gizmo state, and a session tick in one struct. Panels live in
    scenix-ui's PanelStack; the session layout reuses the same space
    solver hosts already call.
  • Exact history: every mutation is a concrete EditCommand
    (RenameNode, SetVisibility, SetLayer, SetTransform,
    ReparentNode) whose inverse is computed from the pre-apply graph, so
    undo restores byte-exact state and redo replays the stored forward
    command. The history is capped (HISTORY_DEFAULT_CAP = 128) and new
    work clears redo, like every editor users know.
  • Coalesced gizmo drags: continuous SetTransform updates coalesce by
    kind key + tick window (COALESCE_WINDOW_TICKS = 30) while keeping the
    first inverse — one drag is one undo step that restores the pre-drag
    pose. Drag amounts re-solve absolutely from the captured start
    transform, so repeated inputs replay identically, and rotation gizmos
    compose about world axes (delta * current) as on-screen gizmos promise.
  • Gizmo vocabulary: GizmoMode/GizmoAxis/GizmoSettings::SNAPPED
    with half-unit translates, 15° rotations, and 0.1 scale steps
    (apply_axis_delta, snap_translation), plus line-closest-approach
    axis_pick (GIZMO_HALF_LENGTH = 1.0,
    GIZMO_PICK_THRESHOLD = 0.08) for pointer rays.
  • Panel models: ScenePanelRow + scene_rows flatten the graph
    depth-first (capped at SCENE_PANEL_ROW_LIMIT = 256) with depth,
    payload tags, and selected/hidden/locked flags; InspectorEdit routes
    rename/visible/layer/position/rotation/scale edits and refuses locked
    nodes; inspect_selected builds scalar rows over the v1.5
    Inspectable snapshot implementation.
  • Material editing: PbrField/PhysicalField writes with editor
    clamps (unit channels 0..=1, IOR 1.0..=2.5), assign_texture over
    TextureSlot (Option<TextureId> sets or clears), and a bounded
    MaterialEditorLog. Materials are host-owned resources by design, so
    material edits are optimistic and log-based rather than history-undone.
  • Asset browser (assets feature): AssetBrowserModel over loader
    AssetLoadStatus — capped at BROWSER_ROW_LIMIT = 256 rows with a
    64-char label cap, case-insensitive substring filter, optional status
    filter, and per-status counts.
  • Timeline previews (timeline feature): TimelinePreview delegates
    to animato's own LoopMode::advance — the same solver runtime actions
    run — with rate clamping 0.05..=8 and 60 FPS snapping for scrubs.
  • Project metadata: ProjectMetadata/SceneRoute schema v1 with
    validation, upgrade_to_current, and serde_json round-trip
    persistence under the serde feature.

scenix-ui (new, no_std-capable crate)

  • Layout: Rect/Edges/Anchor/UiScale with safe_rect for
    device insets.
  • Docked panels: PanelStack/PanelSpec/PanelEvent/DockKind +
    the first-fit tab-order panel_placement space solver
    (DOCK_MAX_FRACTION, PANEL_MIN_THICKNESS,
    PANEL_MAX_THICKNESS). A panel is skipped, never silently shrunk away,
    when remaining space falls under the floor.
  • Renderer stats: RendererStats rolling window (STATS_HISTORY_FRAMES
    = 120) with FrameSample::sanitized, BudgetPolicy::{THIRTY_HZ, SIXTY_HZ}, and BudgetWarnings; serializes oldest-first under
    serde so hosts replay identical windows.
  • Tool widgets: Button with edge-only press, Toggle, Slider
    with truncating snap (SLIDER_DEFAULT_STEP = 0.01), SmallTextBuffer
    (TEXT_BUFFER_CAP = 64), FocusChain (FOCUS_CHAIN_MAX = 8),
    ColorSwatch, and widget_hit point testing with exclusive right/bottom
    edges.
  • Bounded overlays: OverlayFrame::push_rect/push_text stop at
    MAX_OVERLAY_RECTS = 256 / MAX_OVERLAY_TEXTS = 64, and
    compose_panel_overlay is deterministic — the same panel stack, stats,
    and viewport always produce the same draw list, which makes overlay
    tests exact instead of approximate. Compiles for
    wasm32-unknown-unknown with --no-default-features.

Facade and integrations

  • The facade ui feature exposes curated re-exports of the whole UI
    surface; the editor feature drives scenix-editor (with its
    inspector + material features) and implies ui, because
    sessions own the standard panel stack returned by standard_panels.
  • Cross-crate surfaces stay feature-gated: asset-browser rows re-export
    under editor,loader, timeline previews under editor,animato, and the
    project JSON exchange under editor,serde.
  • loader forwards scenix-editor/assets, animato forwards
    scenix-editor/timeline, and serde forwards both tooling crates'
    serialization surfaces.

Try It

use scenix::{DockKind, EditorSession, GizmoAxis, GizmoMode, InspectorEdit,
    PanelId, PanelSpec, SceneNode, SelectionMode, Vec3, scene_rows,
    standard_panels};

let mut session = EditorSession::new("Shell");
let root = session.graph_mut().add(SceneNode::group("Hero"));
*session.panels_mut() = standard_panels();
session.panels_mut().upsert(PanelSpec::docked(
    PanelId(5), "Assets".into(), DockKind::Bottom, 240.0,
));

session.select(root, SelectionMode::Replace).unwrap();
session
    .apply_inspector_edit(root, InspectorEdit::SetPosition(Vec3::new(2.0, 0.0, 1.0)))
    .unwrap();

// One drag, one undo step: the second update re-solves from the start.
session.begin_drag(root, GizmoMode::Translate, GizmoAxis::X).unwrap();
session.drag_gizmo(0.4).unwrap();
session.drag_gizmo(0.9).unwrap();
session.end_drag();
session.undo().unwrap(); // back to the pre-drag transform, exactly

for row in scene_rows(session.graph()) {
    println!("{}{}", "  ".repeat(row.depth), row.label);
}

println!("layout: {:?}", session.layout((1600.0, 900.0).into()));

Run the headless demos without any window or GPU:
cargo run -p scenix --features editor --example editor_shell and
cargo run -p scenix --features ui --example ui_overlay.

Upgrade

Nothing breaks. Add the features you need:

[dependencies]
scenix = { version = "1.10", features = ["editor"] } # implies ui
# or UI tooling only:
scenix = { version = "1.10", features = ["ui"] }

The existing editor meaning did not change for scenix-core's inspector
snapshot types — they re-export exactly as in v1.9.x. Code that enabled
editor without wanting panel types can keep compiling: the added
re-exports are additive names.