Skip to main content

Scene

Struct Scene 

Source
pub struct Scene { /* private fields */ }
Expand description

The data model behind a SceneView: a flat list of entries in a parent-relative scene-graph plus a SpatialIndex for rectangular queries.

The Scene itself does no rendering — it’s a passive container the view reads from at build / place / paint time. Mutations (add_widget, add_item, set_local_pos, set_transform, set_local_bounds, remove) update the spatial index in lockstep, so items_in_rect, item_at, and SceneView’s viewport-cull path are all O(visible) instead of O(N). When a parent’s local_pos or transform changes, every descendant’s scene-AABB shifts; the Scene re-buckets the entire subtree.

In practice most callers operate on a SceneModel handle (Rc<RefCell<Scene>> with &self mutators) rather than a bare Scene. Prefer SceneModel for any widget or handler that needs to share the scene across multiple owners.

Implementations§

Source§

impl Scene

Source

pub fn new() -> Self

An empty scene with the default GridHashIndex.

Source

pub fn with_index(index: Box<dyn SpatialIndex>) -> Self

An empty scene with a custom SpatialIndex.

Source

pub fn add_widget<W: Widget + 'static>( &mut self, widget: W, local_rect: Rect, ) -> ItemId

Place a heavyweight Widget at local_rect’s origin, sized local_rect.size. The rect is interpreted as (local_pos = local_rect.origin, local_bounds = (0, 0, w, h)). Returns the ItemId for later mutation. The widget is consumed at SceneView build time and added to the arena.

Source

pub fn add_item<I: SceneItem + 'static>( &mut self, item: I, local_pos: Point, ) -> ItemId

Place a lightweight SceneItem at local_pos. The item’s local_bounds and initial_flags are read once at insert time. The item is not added to the arena — it’s painted directly from SceneView::paint.

Source

pub fn add_item_dynamic<I: SceneItem + 'static>( &mut self, item: I, local_pos: Point, ) -> ItemId

Like add_item but flags the entry as having signal-driven local_bounds. The Scene re-reads item.local_bounds() each rebuild via refresh_dynamic_bounds — the SceneView calls that at the start of every build pass. The spatial index gets re-bucketed when the read-back differs from the cached value, so items_in_rect / hit-test stay correct without app-side set_local_bounds plumbing.

Use only when the bounds genuinely depend on a Signal<T> the item reads in local_bounds. Static items pay an unnecessary per-rebuild bounds read otherwise; prefer add_item for the common case.

Source

pub fn refresh_dynamic_bounds(&mut self) -> bool

Re-read every dynamic item’s current local_bounds, applying set_local_bounds (and re-bucketing the spatial index) for any entry whose value has changed. No-op for static entries. Called by SceneView at the start of each build() so signal-driven bounds propagate to bucketing without explicit app-side calls.

Returns true if at least one dynamic entry’s bounds changed this call. SceneView uses the true → false transition (an animation settling) as the one moment to walk the final animated bounds into the AccessKit tree, since it otherwise suppresses per-frame AT re-walks during the animation.

Source

pub fn item_change_signal(&self) -> Signal<SceneChange>

Reactive notification stream for every Scene mutation. Apps observe via signal.observe(|change| …) to wire validation, persistence, telemetry, or mirroring into a data layer without polling the Scene each frame. The signal fires after the mutation has been applied — by the time the observer runs, the Scene already reflects the new state.

§Notification timing

Where the fan-out happens depends on which door mutated the scene, and the difference is exactly the RefCell an observer would have to re-enter:

  • Through a SceneModel (the normal path, including SceneWriteGuard) the mutation runs inside a write scope: changes queue in emission order and fan out once the model has released its borrow_mut(). An observer may therefore read the scene, and write it back — a write made from an observer queues in turn and is delivered by the same drain, after the changes already queued ahead of it.

  • Through a bare &mut Scene there is nothing holding a RefCell open to escape from, so the fan-out is synchronous, inside the mutator, exactly as before.

    This is safe for a Scene you own outright — no second handle to it can exist. It is not safe for a &mut Scene reborrowed out of a SceneModel: a RefMut taken from the shared cell leaves defer_depth at zero, so observers still run under the live exclusive borrow and one that re-enters the model panics. Take SceneModel::write_guard instead — it derefs to &mut Scene, so the call sites are identical, and it opens the write scope the observers need.

Either way the notification has been delivered by the time the mutator call returns.

§What is not deferred

The scene’s constraint signals — pan_axes_signal, zoomable_signal, pan_bounds_signal, zoom_range_signal — carry state, not events (current_pan_axes and friends read them back), so queueing their writes would make the scene contradict itself inside an open write scope. They still fan out synchronously under the borrow: an observer on one of those four must not re-enter the SceneModel.

Source

pub fn a11y_change_signal(&self) -> Signal<u64>

Reactive notification for logical-AT-structure mutations (add_a11y_group / remove_a11y_group / set_a11y_parent / add_a11y_relation / set_a11y_live / set_a11y_landmark / set_a11y_categories). A monotonic counter bumped after each such mutation. SceneView observes this to re-walk the AccessKit tree — these changes don’t flow through item_change_signal because they aren’t item geometry, and the AT tree is separate from the visual scene.

Source

pub fn cascade_budget(&self) -> CascadeBudget

The runaway-detection budget for this scene’s change fan-out.

Source

pub fn set_cascade_budget(&self, budget: CascadeBudget)

Replace the runaway-detection budget for this scene’s change fan-out.

The default is tuned for the graph shapes this tier runs; see CascadeBudget for the rule and for when raising it is the right answer rather than a way to silence a real cycle.

A budget of zero is clamped to the smallest usable one rather than stored: it would forbid the reactive write-back this channel exists for, and would trip with no cascade for the diagnostic to describe.

Takes effect on the next drain — a budget raised from inside an observer does not enlarge the drain already running, which is why the budget is read once at its start.

Source

pub fn open_transaction_depth(&self) -> u32

How many transaction / write scopes are open on this scene.

Zero between mutations. A non-zero value outside a mutator means a SceneTransaction is being held, which is only correct within one synchronous scope — see SceneModel::transaction.

Source

pub fn transaction_signal(&self) -> Signal<TxnId>

Fires once per committed transaction, after the edit sink, with the scene unborrowed. See SceneModel::transaction_signal.

Source

pub fn mutation_version(&self) -> u64

Monotonic counter of every model mutation applied so far — item geometry / visibility / structure (each ItemChange) and logical-AT structure (groups, parents, relations, live, landmarks, categories).

Includes the per-frame churn of refresh_dynamic_bounds; a consumer gating an expensive rebuild on “did anything meaningful change” wants structural_version instead. The counter wraps; compare for equality, not ordering.

Source

pub fn structural_version(&self) -> u64

mutation_version with the per-frame dynamic-bounds churn subtracted out: it advances on every mutation except the LocalBoundsChanged events refresh_dynamic_bounds emits.

The version to gate an expensive rebuild on. SceneView snapshots it each build() and only re-walks the (separate, expensive) AccessKit tree when it has advanced since the previous walk — so an actively-animating add_item_dynamic item, which rebuilds every frame, does not issue an AT re-walk per frame for sub-pixel bounds drift a screen reader cannot use.

Naming the exclusion is the point. The alternative — snapshotting mutation_version before refresh_dynamic_bounds and again after, and treating the difference as churn — is wrong now that the refresh fans its changes out: an observer may legally mutate the scene from that fan-out, and its mutation lands inside the bracket where it is indistinguishable from churn. Folded into the baseline, an AT-structural change made there would never un-gate a re-walk, in that build or any later one. The counter wraps; compare for equality, not ordering.

Source

pub fn local_pos(&self, id: ItemId) -> Option<Point>

Read an item’s local_pos (its anchor in parent coords).

Source

pub fn set_local_pos(&mut self, id: ItemId, local_pos: Point)

Move an item to a new local_pos in its parent’s coordinate frame. Re-buckets the item and every descendant in the spatial index since the descendants’ scene-AABBs shift along. No-op if the id is unknown.

Source

pub fn local_bounds(&self, id: ItemId) -> Option<Rect>

Read an item’s local_bounds (its AABB in local coords).

Source

pub fn set_local_bounds(&mut self, id: ItemId, local_bounds: Rect)

Update an item’s local_bounds. For lightweight items this also calls SceneItem::set_local_bounds on the item so its next paint reflects the new geometry. The spatial index is re-bucketed; only this item moves (descendants’ local frames are unchanged). No-op if the id is unknown.

§The item has the last word, and says it once

An item whose box is derived from its own geometry — a PathItem — treats this as “fit yourself to this rectangle” rather than “adopt this rectangle”, and the box stored here is what it settled on, read back from the item. It lands on the request on every axis the geometry has extent on; on an axis it has none (a perfectly horizontal stroke has no height) the box stays the stroke’s own thickness, because there is nothing to stretch.

Either way the call is idempotent: asking twice for the same rectangle emits one ItemChange::LocalBoundsChanged and re-buckets the index once. An app driving this per frame — a resize handle, a layout pass — therefore goes quiet as soon as it stops moving, rather than emitting an endless series of nearly-identical changes.

Source

pub fn size_policy(&self, id: ItemId) -> SizePolicy

Which axes of this entry’s local_bounds its widget decides. SizePolicy::Fixed for an unknown id, and for every lightweight item.

Source

pub fn set_size_policy(&mut self, id: ItemId, policy: SizePolicy) -> bool

Hand one or both axes of id’s box to its widget. See SizePolicy.

Returns false — and changes nothing — for an unknown id or a lightweight entry, which has no layout_response to ask. That is a refusal rather than a silent no-op because the two tiers already have different answers to “who decides my box” (add_item_dynamic is the lightweight one), and a policy that quietly did nothing on one of them would read as a bug in the view rather than a misuse of the model.

Emits ItemChange::SizePolicyChanged when the value actually changes — an edit, carrying no geometry. The geometry it enables arrives separately as ItemChange::MeasuredSizeChanged, which is not an edit.

Source

pub fn set_measured_size(&mut self, id: ItemId, size: Size) -> bool

Write a derived size into the model: the same geometry update and index re-bucketing set_local_bounds does, reported as ItemChange::MeasuredSizeChanged instead — not an edit, not structural, answered by a relayout rather than a rebuild.

Returns whether anything moved. The origin of local_bounds is kept; only the size is written, because that is the whole of what a measurement can know.

§When this is the right door, and when it is not

Use it for a size that is a pure function of content the app already owns: the height a paragraph wraps to at a given width, the bounding box of an ink stroke after a segment dried, the extent of a generated diagram. Recording such a write as an edit is what makes a text reflow an undo step; bumping the structural version for it is what buys an AccessKit re-walk for a two-pixel line-height change. This door does neither, and the view that observes it relayouts instead of rebuilding.

Use set_local_bounds for anything the user did: a resize handle, an import, a paste. Those are edits, and a history above the scene must see them.

The SizePolicy machinery drives this from inside the view; an app measuring something the framework cannot measure calls it directly.

Source

pub fn transform(&self, id: ItemId) -> Option<Transform2D>

Read an item’s local→parent transform (rotation/scale around the local origin). Identity by default.

Source

pub fn set_transform(&mut self, id: ItemId, transform: Transform2D)

Set an item’s local→parent transform. Re-buckets the item’s subtree in the spatial index. No-op if the id is unknown.

Source

pub fn scene_transform(&self, id: ItemId) -> Transform2D

The composed local→scene transform for this item, walking up the parent chain. Identity for an item that doesn’t exist.

Source

pub fn scene_pos(&self, id: ItemId) -> Option<Point>

The item’s anchor in scene coords (its local origin transformed through the parent chain).

Source

pub fn scene_rect(&self, id: ItemId) -> Option<Rect>

The AABB enclosing the item’s local_bounds after composing through the parent chain — i.e. the rectangle the spatial index buckets on. None if the id is unknown.

Source

pub fn map_to_scene(&self, id: ItemId, local_pt: Point) -> Option<Point>

Map a point in the item’s local frame to scene coords.

Source

pub fn map_from_scene(&self, id: ItemId, scene_pt: Point) -> Option<Point>

Map a point in scene coords to the item’s local frame. Returns None if the item is unknown or its scene transform is degenerate (zero scale).

Source

pub fn flags(&self, id: ItemId) -> Option<ItemFlags>

Read an item’s ItemFlags bitset.

Source

pub fn set_flags(&mut self, id: ItemId, flags: ItemFlags)

Replace an item’s flags wholesale. No-op if unknown.

Announces exactly what set_flag announces for the same net change: the derived ItemChange::VisibilityChanged when IS_VISIBLE flipped, then the ItemChange::FlagsChanged that describes the mutation.

Source

pub fn set_flag(&mut self, id: ItemId, flag: ItemFlags, on: bool)

Set or clear a single flag on an item. No-op if unknown.

Source

pub fn set_visible(&mut self, id: ItemId, visible: bool)

Toggle the ItemFlags::IS_VISIBLE bit. Convenience for the common “hide this item” operation.

Source

pub fn is_effectively_visible(&self, id: ItemId) -> bool

Whether the item is visible AND every ancestor in its chain is visible. Returns true when nothing in the chain has IS_VISIBLE cleared. false for unknown ids.

Source

pub fn opacity(&self, id: ItemId) -> Option<f32>

Read an item’s local opacity multiplier (1.0 by default).

Source

pub fn set_opacity(&mut self, id: ItemId, opacity: f32)

Set an item’s local opacity, clamped to [0.0, 1.0].

Source

pub fn set_item_fill(&mut self, id: ItemId, fill: impl Into<ColorProp>)

Replace a lightweight item’s fill colour live, emitting ItemChange::AppearanceChanged — always repaint-only, never a relayout, rebuild, or AccessKit re-walk. The colour is a [ColorProp], so it accepts a plain Color, a theme role, a Signal<Color>, or a Signal<Role>. No-op for item kinds without a fill (e.g. ImageItem).

§Reactivity contract

A colour becomes continuously reactive by being registered at build time (SceneItem::register_bindings). So:

  • Construct the item with a Signal/role colour (.fill(my_signal)) for a colour that tracks its signal forever. This is the recommended path and needs no mutator at all.
  • This mutator installs a snapshot: it repaints immediately, which is all a static colour ever needs. If you pass a Signal/dynamic role here, it paints the signal’s current value now and starts tracking it continuously from the owning view’s next rebuild (whenever some other structural change re-runs register_bindings). Deliberately not forced: a colour change must never cost a rebuild + AT re-walk.
Source

pub fn clear_item_fill(&mut self, id: ItemId)

Clear a lightweight item’s fill (Rect/Path/Group become fill-less), emitting ItemChange::AppearanceChanged (repaint-only). No-op for items whose fill can’t be cleared (e.g. TextItem, which always has a foreground colour).

Source

pub fn set_item_stroke( &mut self, id: ItemId, color: impl Into<ColorProp>, style: StrokeStyle, )

Replace a lightweight item’s stroke (colour + [StrokeStyle]) live, emitting ItemChange::AppearanceChanged (repaint-only). No-op for item kinds without a stroke slot (TextItem / ImageItem). See set_item_fill for the reactivity contract.

Source

pub fn clear_item_stroke(&mut self, id: ItemId)

Clear a lightweight item’s stroke, emitting ItemChange::AppearanceChanged (repaint-only). No-op for item kinds without a stroke.

Source

pub fn add_boxed_item( &mut self, item: Box<dyn SceneItem>, local_pos: Point, ) -> ItemId

Insert an already-boxed lightweight item at local_pos, returning its id. The boxed-dyn counterpart of add_item — used by SceneListAdapter whose delegate yields Box<dyn SceneItem>.

Source

pub fn set_item_handlers( &mut self, id: ItemId, handlers: Option<SceneItemHandlerSet>, )

Replace an item’s handler set. Pass None to clear.

Fires ItemChange::HandlersChanged carrying both sides (HandlerReplacement), like every other mutator on this type: a consumer that caches handlers has no other way to learn of it, and a data layer inverting a transaction has no other way to put them back. This is the handler door that produces a reversible edit; see handlers_mut for the one that cannot.

Source

pub fn handlers_mut(&mut self, id: ItemId) -> Option<&mut SceneItemHandlerSet>

Mutably borrow an item’s handler set, lazily creating an empty one if none exists. Returns None for unknown ids. Allows fluent chains: scene.handlers_mut(id).unwrap().on_tap(…).cursor(…);.

Fires ItemChange::HandlersChanged before handing the set out, with no HandlerReplacement — &mut cannot report back what the caller does with it, so the notification means “no longer what you last read” and nothing finer. A caller that takes the borrow and changes nothing therefore costs one spurious invalidation, which is the right way round: the alternative is a consumer serving stale handlers.

Because it describes nothing, it is not recorded as an edit (ItemChange::is_edit) — a transaction record whose every entry carries both sides of what it replaced must not carry one that carries neither. Make a handler change the history should be able to reverse through set_item_handlers, which knows both sides.

Source

pub fn handlers(&self, id: ItemId) -> Option<&SceneItemHandlerSet>

Read-only access to an item’s handler set, if one is set.

Source

pub fn effective_opacity(&self, id: ItemId) -> f32

Effective opacity composed up the parent chain — the product of every ancestor’s opacity and this item’s. 1.0 for an unknown id (so callers don’t end up multiplying by a stale value).

Source

pub fn set_scene_rect(&mut self, rect: Option<Rect>)

Declare the scene’s logical extent. None (the default) means “auto-compute from items each query”; Some(rect) fixes the extent regardless of item placement. Used by SceneView for pan clamping and fit_to_content.

Source

pub fn scene_rect_extent(&self) -> Option<Rect>

The resolved scene extent — user-declared via Scene::set_scene_rect if set, otherwise the AABB enclosing every item’s scene rect. None when neither is available (the user didn’t declare and the scene is empty).

Source

pub fn pan_axes(&mut self, axes: PanAxes)

Set the axes the view may pan along. Default PanAxes::Both. Writes to the reactive signal; gesture closures pick the change up on the next event.

Source

pub fn current_pan_axes(&self) -> PanAxes

The currently-declared pan axes. Live read of the signal.

Source

pub fn zoomable(&mut self, on: bool)

Set whether the view honors zoom gestures. Default true. Writes to the reactive signal.

Source

pub fn is_zoomable(&self) -> bool

Whether the scene currently allows zoom. Live read.

Source

pub fn set_pan_bounds(&mut self, bounds: Option<Rect>)

Clamp the visible viewport to this scene-coord rect. None (default) leaves pan unconstrained. When Some(r), the SceneView’s pan is clamped so the visible scene region overlaps r. When r is smaller than the visible viewport, the rect is centered.

Distinct from set_scene_rect: scene_rect declares the scene’s logical extent (used by adopt_scene_size); pan_bounds controls what region the user can scroll to. A doc-style app typically sets both to the same rect.

Source

pub fn current_pan_bounds(&self) -> Option<Rect>

The currently-declared pan-bounds rect. Live read.

Source

pub fn set_zoom_range(&mut self, range: Option<RangeInclusive<f32>>)

Inclusive [min, max] zoom-factor clamp. None (default) is unconstrained from the Scene side — the SceneView may still impose its own override.

The effective range applied by the SceneView is the intersection of Scene + view-level override, so apps cannot loosen a Scene-declared range by setting a wider override on the view.

Source

pub fn current_zoom_range(&self) -> Option<RangeInclusive<f32>>

The currently-declared zoom range. Live read.

Source

pub fn pan_axes_signal(&self) -> Signal<PanAxes>

Reactive accessors for live observation.

Source

pub fn pan_bounds_signal(&self) -> Signal<Option<Rect>>

Reactive pan-bounds signal.

Source

pub fn zoom_range_signal(&self) -> Signal<Option<RangeInclusive<f32>>>

Reactive zoom-range signal.

Source

pub fn zoomable_signal(&self) -> Signal<bool>

Reactive zoomable on/off signal.

Source

pub fn constraints(&self) -> &SceneConstraints

Read-only view of the full constraint bundle. Useful when passing all four signals to a custom view implementation.

Source

pub fn set_z(&mut self, id: ItemId, z: f32)

Set paint z-order for an entry. Higher z paints later (on top); equal-z falls back to insertion order. Default 0.0.

Works for both tiers: lightweight items re-sort within their band on the next paint, and heavyweight widget entries restack the arena children on the next rebuild (the SceneView reorders node.children by z without recreating the widgets, so focus / text-edit / animation state survives the restack). No-op for unknown ids.

§The no-op test is exact

A write is ignored only when the entry already holds that value. The guard used to be an absolute |old - z| < f32::EPSILON, which is the wrong metric in both directions: f32::EPSILON is the spacing of the representable numbers at 1.0, so out at z = 1e6 (where the real spacing is ~0.06) no two distinct floats are ever within it and the guard never fires, while near zero (where the spacing is ~1e-45) it swallows millions of distinct values.

That second half was not theoretical: z_between bisects relatively and existed precisely so a caller is never told “there is room” and then gets a silent no-op — and near zero the two disagreed, so z_between returned Some(6.25e-8) and set_z dropped it, emitting no ItemChange::ZChanged and leaving the order wrong with nothing to observe. One metric now, and it is the one z_between already used: two zs are the same iff they are the same float.

Source

pub fn bring_to_front(&mut self, id: ItemId)

Raise an entry above all current entries by giving it a z one greater than the current maximum. The drag-to-front primitive — call it on drag-start so the grabbed card (and its text) renders over the others. Works for both tiers (see set_z).

Source

pub fn send_to_back(&mut self, id: ItemId)

Lower an entry below all current entries by giving it a z one less than the current minimum. Works for both tiers (see set_z).

Source

pub fn z(&self, id: ItemId) -> Option<f32>

Read an entry’s z-order.

Source

pub fn set_layer(&mut self, id: ItemId, layer: SceneLayer)

Set the paint band for a lightweight entry — see SceneLayer for what each one means and what Interleaved costs. Within a band, set_z still orders items among themselves. No-op for unknown ids.

Source

pub fn layer(&self, id: ItemId) -> Option<SceneLayer>

Read an entry’s paint band. None for unknown ids.

Source

pub fn set_item_parent(&mut self, child: ItemId, parent: Option<ItemId>)

Declare a parent/child relationship. child’s local_pos and transform are reinterpreted as relative to the new parent’s local frame — the visual position changes unless the caller compensates. Re-buckets child’s subtree.

Pass parent = None to detach (child’s local frame becomes scene-rooted again).

Cycle guard: if the proposed parent is child itself or a descendant of child, the call is a no-op (no parent change, no rebucket, no signal fire). Without this guard the downstream rebucket_subtree walk loops indefinitely.

Source

pub fn parent_of(&self, id: ItemId) -> Option<ItemId>

Parent of id, if any.

Source

pub fn is_descendant_of(&self, id: ItemId, ancestor: ItemId) -> bool

Whether id’s ancestor chain contains ancestor.

Source

pub fn collect_descendants(&self, id: ItemId, out: &mut Vec<ItemId>)

Append every direct + transitive descendant of id into out, each parent before its own children. The id itself is not included.

Costs the subtree, not the scene: the walk steps through the kept SceneEntry::children adjacency rather than rescanning every entry per visited node. A cycle in the parent graph is bounded by the visited set rather than looping forever.

Source

pub fn set_geometry_constraint( &mut self, f: impl Fn(&ProposedChange<'_>) -> ChangeVerdict + 'static, )

Install the document’s standing geometry rule — snap-to-grid, axis lock, page-bounds clamp — consulted before a user-driven gesture applies anything.

Replaces any previous constraint; there is one per scene, because a document has one geometry. See ProposedChange for what the closure is handed, and SceneModel::set_geometry_constraint for the door an app normally uses.

use teksilo_canvas::{Point, Rect};
use teksilo_scene::{ChangeVerdict, RectItem, Scene};

let mut scene = Scene::new();
scene.add_item(RectItem::new(Rect::new(0.0, 0.0, 40.0, 40.0)), Point::ZERO);
// A 25-unit grid, snapping the moved box's own top-leading corner.
scene.set_geometry_constraint(|c| {
    let mut f = c.proposed;
    f.rect.x = (f.rect.x / 25.0).round() * 25.0;
    f.rect.y = (f.rect.y / 25.0).round() * 25.0;
    ChangeVerdict::Adjust(f)
});
assert!(scene.has_geometry_constraint());
Source

pub fn clear_geometry_constraint(&mut self)

Remove the geometry constraint. Gestures then apply their raw proposal.

Source

pub fn has_geometry_constraint(&self) -> bool

Whether a geometry constraint is installed.

Source

pub fn in_geometry_constraint(&self) -> bool

Whether a geometry constraint is running right now on this thread.

The probe SceneModel::write_guard uses to turn “a constraint tried to write the scene” into a diagnostic that names the hook.

Source

pub fn selection_roots(&self, ids: &[ItemId]) -> Vec<ItemId>

ids pruned to its roots: any item whose ancestor is also in ids is dropped.

Moving an ancestor already moves its descendants — their local_pos is parent-relative and is not touched — so transforming both would apply the change twice. This is the mutation-side twin of the filter the paint preview already runs over the same set.

Order is preserved. O(n·depth): the set is hashed once and each id then walks its own ancestor chain, asking the set rather than asking every other id whether it is an ancestor. The hop cap is the one is_descendant_of uses, so a malformed parent cycle bounds rather than hangs, and an id that is its own ancestor is kept (a cycle has no root to prefer).

The complexity is load-bearing, not incidental: every selection-transform recompute runs this once (plus one transformable_roots per operation), and so does the item-drag group and the keyboard nudge — so a quadratic form here is a frozen window on a large selection rather than a slow one. selection_roots_is_linear_in_the_selection, in tests/selection_roots_scaling_probe.rs, pins it: 7.03 ms against 36.3 µs for a 1 000-item selection, measured.

Source

pub fn transformable_roots( &self, ids: &[ItemId], op: TransformOp, ) -> Vec<ItemId>

The roots of ids that may take part in op.

Three filters, in this order. The item must carry the operation’s flag (TransformOp::required_flag). For TransformOp::Rotate it must be a lightweight entry, because a heavyweight card is sized from the AABB of its transformed bounds and a rotation there inflates its layout box without turning anything. For TransformOp::Resize it must own at least one axis of its own box: an entry on SizePolicy::Intrinsic has handed both to its widget, so every number a resize could write is one the next layout pass measures back over — a handle that produces a reversible step and no visible change is worse than no handle. SizePolicy::HeightForWidth still owns its width and is offered the full set; the height half of a corner drag is neutralised at the model door, in apply_transform_delta.

The flag is checked before the descendant pruning, so a selected child of a selected-but-locked parent still takes part on its own.

Source

pub fn scene_rotation(&self, id: ItemId) -> Option<f32>

The item’s own rotation in scene space, in radians — the angle of its composed local → scene basis. None for an unknown id.

Source

pub fn transform_frame(&self, roots: &[ItemId]) -> Option<TransformFrame>

The selection frame enclosing roots — the box a transform controller draws its handles on. None when roots is empty or none of them resolve.

A single root’s frame takes that item’s own rotation, so resizing a rotated item happens along its own axes and is exact. A multi-item frame is axis-aligned, because the union of differently-rotated boxes has no well-defined angle — Konva does the same.

Source

pub fn apply_transform_delta( &mut self, roots: &[ItemId], delta: &TransformDelta, ) -> usize

Apply delta to roots as a single operation, and the only place a transform controller writes the model.

Returns how many entry fields actually changed.

Three writes per root at most, and each is the existing setter, so the change stream, the spatial index and every observer see nothing new:

  • position — the item’s scene anchor is mapped through the delta and restated in its parent’s frame, so a scale about a pivot moves it and a rotation orbits it.
  • extent — local_bounds is scaled, which makes the item reflow into the new box rather than be drawn at a stretched scale. The scale is resolved along the item’s own axes, so a rotated item stays a rotated rectangle instead of shearing into a parallelogram; where the scale is non-uniform and the item is rotated relative to the frame, that is an approximation of the true (unrepresentable) result, and it is exact whenever the item is aligned with the frame — which includes every single-item selection. An axis the entry’s SizePolicy hands to its widget is not scaled, and is not scaled for the anchor either, so a card whose height its content owns neither resizes nor moves when someone drags its top edge; see the comment at the loop head.
  • orientation — the item’s own Transform2D is post-rotated. Skipped for a heavyweight entry, whose layout box is the AABB of its transformed bounds: a rotation there would inflate the box and turn nothing.

The transaction boundary is this call. One gesture is one call, so an app-level reversible-edit layer has exactly one thing to record — and this crate ships no history of its own.

Source

pub fn item(&self, id: ItemId) -> Option<&dyn SceneItem>

Borrow a lightweight SceneItem by id. None for unknown ids and for heavyweight widget entries.

Source

pub fn paint_key(&self, id: ItemId) -> Option<PaintKey>

Where id sits in this scene’s single paint order — the value every picker in the crate compares. None for unknown ids.

Defined for both tiers: a lightweight entry’s rank is its SceneLayer band, and a heavyweight widget entry’s is RANK_WIDGET — which is exactly where the arena’s child walk paints it, between the Under and Over bands. An Interleaved item shares that rank, so it sorts against the cards by z. See PaintKey for the ordering and for the equal-z tie-break.

Source

pub fn remove(&mut self, id: ItemId)

Remove an item by id, recursively dropping every descendant.

Mirrors Qt’s QGraphicsScene::removeItem semantics: deleting a parent deletes its children too. No-op if id is unknown. Fires one ItemChange::Removed per id, descendants first then the named parent — observers see a consistent “leaves-then-root” order.

To remove id without deleting its children, call Scene::orphan first to promote them to root-level, then remove(id).

§What happens to what it destroyed

This is Scene::take with the salvage routed to the edit sink instead of to the caller — identical work, identical events. With a sink installed (SceneModel::set_edit_sink), each removed entry arrives as a SceneEdit::Removed carrying its RemovedItem, which Scene::restore puts back. With none, the salvage is dropped, which is what a removal has always done.

Source

pub fn take(&mut self, id: ItemId) -> Vec<RemovedItem>

Remove id and every descendant, handing the salvage back instead of dropping it — item box, handlers, magnets and logical-AT decorations intact, each still carrying its original ItemId.

The same work and the same events as Scene::remove; the difference is who ends up owning what was removed. Feed a result to Scene::restore_all to put the subtree back.

§Order

Deepest descendants first, the named root last — the order remove announces in. Scene::restore_all reverses it; restoring by hand means roots first.

use teksilo_canvas::{Point, Rect};
use teksilo_scene::{RectItem, Scene};

let mut scene = Scene::new();
let card = scene.add_item(RectItem::new(Rect::new(0.0, 0.0, 40.0, 40.0)), Point::ZERO);
let salvage = scene.take(card);
assert_eq!(scene.len(), 0);

// Back at the same id, so selection, magnets and AT parenting still resolve.
let restored = scene.restore_all(salvage).unwrap();
assert_eq!(restored, vec![card]);
assert_eq!(scene.len(), 1);
Source

pub fn restore(&mut self, salvage: RemovedItem) -> Result<ItemId, RestoreError>

Re-insert a salvaged entry at its original ItemId, with its magnets and its logical-AT decorations.

§Identity is the point

SceneSelection is keyed by ItemId, MagnetId → ItemId is keyed by it, the whole logical AT tree is keyed by A11yNode::Item(ItemId), and — because a SceneItem has no downcast — an app’s own side map is the only way to reach item-specific state, so it is keyed by it too. A restore that minted a fresh id would restore the pixels and lose every one of them.

§Order

Roots before children: a salvage naming a parent that is not in the scene fails with RestoreError::MissingParent. Scene::restore_all orders a whole Scene::take result for you.

§What comes back, and what does not

Geometry, transform, z, layer, parent link, flags, opacity, handlers, magnets (ids included) and every logical-AT decoration whose other endpoint is still alive. The entry returns to its recorded position in declaration order when that index still exists, so the accessibility reading order is preserved rather than the item being appended last.

An edge to a node that has since been removed is not re-attached: re-inserting it would name something the AT walker cannot resolve. Edges are therefore attached after the entry is in, and — under restore_all — after the whole batch is in, so an edge between two items of one removed subtree is not dropped merely because its far end had not arrived yet.

A single-view heavyweight widget (Scene::add_widget) whose one Box<dyn Widget> a view already drained restores as an entry with no instance to materialise — unless the take and the restore happen inside one build cycle, in which case the view’s orphan reap never runs and the arena instance survives. The restore is accepted either way, because refusing it would refuse the restores that work; see RemovedItem::widget_instance_present. Content that must survive an undo in every view goes in through SceneModel::add_widget_item.

§Restoring into a different scene is supported

A RemovedItem taken from one scene may be restored into another, and that is the move-between-documents door: cut a card out of one board with take, restore it into a second, and it arrives whole — item box, geometry, flags, handlers and magnets, at the same ItemId.

Nothing special makes it work and nothing needs to refuse it. ItemId is minted from a process-global counter, so an id from another scene can never collide with one here; a salvage carries its whole entry rather than an index into the scene it came from; and the three consequences of crossing the boundary are exactly the rules that already apply within one scene:

  • A parented salvage is refused with RestoreError::MissingParent, because the parent it names is still in the other scene. Move the parent first and the child follows — the same roots-before-children rule, doing the same job — or call RemovedItem::detach to bring the item over as a root.
  • Logical-AT edges whose far end is not here are dropped, by the same test that drops an edge to a since-removed node.
  • Declaration order is clamped: the recorded index is where the entry sat in the source scene’s reading order, and a shorter target takes it at the end.

The salvage is consumed either way, so a refused move does not leave a second copy behind — but it does drop the item, so check the Result.

Source

pub fn restore_all( &mut self, salvage: Vec<RemovedItem>, ) -> Result<Vec<ItemId>, RestoreError>

Restore a whole Scene::take result, roots first.

take hands back leaves-then-root; restoring in that order would fail the first child on RestoreError::MissingParent, so this reverses it for you — which also puts each parent’s children list back in its original order.

Logical-AT edges are attached once the whole batch is in, so an edge between two items of the removed subtree survives whatever order the two ends land in.

Stops at the first failure and returns it; everything restored before it stays restored, because a rollback here would be the framework applying an inverse, which is the data layer’s job.

Source

pub fn replace_item( &mut self, id: ItemId, item: Box<dyn SceneItem>, ) -> Result<Box<dyn SceneItem>, ReplaceRejected>

Swap the lightweight item box at id, keeping the entry — and the ItemId. Returns the box that was there.

Position, transform, z, layer, parent, flags, opacity, handlers, magnets and logical-AT decorations all survive, so a data row whose content changed does not lose its selection membership, its connections or its place in the accessibility tree. Emits one ItemChange::ItemReplaced, not Removed + Added.

§Bounds and the spatial index

The entry’s AABB is derived from the item, so the new item’s local_bounds is read and the subtree re-bucketed. Skipping that would leave the old rectangle in the index and quietly break items_in_rect, item_at, the cull path and every cached hit snapshot.

§Flags

The entry’s flags are kept; the replacement’s initial_flags are not consulted. They are initial flags — they apply where an item is inserted — and an app that has since called set_visible or set_flag would otherwise have that silently undone by a content refresh. Call set_flags afterwards to adopt the new item’s instead.

§Errors

ReplaceItemError::UnknownItem for an id that is not in the scene, ReplaceItemError::NotLightweight for a heavyweight widget entry — swap a Delegated entry’s data with SceneModel::set_payload instead.

Either way the ReplaceRejected hands the item back: a refused write must not eat a value the caller cannot clone and may have paid to build.

Source

pub fn placement(&self, id: ItemId) -> Option<Placement>

Where id sits, as one value. None for an unknown id.

Source

pub fn set_placement(&mut self, id: ItemId, placement: Placement)

Write parent, z, position and transform together, emitting one ItemChange::PlacementChanged.

The atomic alternative to four mutators and four events. No-op when the placement is unchanged, when id is unknown, and — as with set_item_parent — when the proposed parent is id itself or one of its own descendants, which would make a cycle. The whole write is refused in that last case rather than applied with the old parent, because a partly-applied atomic write is the thing this door exists to prevent.

Source

pub fn reparent_keeping_scene_pos(&mut self, id: ItemId, parent: Option<ItemId>)

Reparent id while holding it visually still — “drag this card into that group”, correctly.

set_item_parent reinterprets local_pos and transform in the new parent’s frame, so the item jumps unless the caller compensates. This derives the local frame that leaves the item’s scene transform where it is, and applies parent and frame as one set_placement write.

The derived frame is normalised: the rotation/scale go in transform (around the local origin, which is what that field means) and all of the translation in local_pos. An item whose transform carried a translation of its own comes back with the same visual result and that translation folded into its position.

No-op for an unknown id, for a parent that is already the current one, for a cycle, and for a new parent whose scene transform is degenerate (a zero scale somewhere in its chain) — there is no frame under it to land in.

Source

pub fn z_between(&self, below: ItemId, above: ItemId) -> Option<f32>

A z strictly between two items’ — the fractional insert that puts one item between two others without renumbering a single sibling.

None when there is no such value: the two are at the same z, either id is unknown, or f32 precision is exhausted at that locus. An f32 carries ~24 mantissa bits, so about two dozen bisections at one point in the order run out — reachable in any card-shuffling UI.

Asking first is what makes that visible. A caller that computed an exhausted midpoint itself would get mid == lo, hand it to set_z, and be given a silent no-op: the card simply would not move, with no error and no event. The answer to None is to renumber the band (a pass of evenly-spaced z values) and bisect again.

The two agree on one metric, and it is this one. set_z ignores a write only when the entry already holds that exact float, so every Some this returns is a value set_z will apply — which is the whole contract. (An absolute epsilon there used to break it near zero: with lo = 0.0 and hi = 1.25e-7 this answers Some(6.25e-8) and the write was dropped.)

use teksilo_canvas::{Point, Rect};
use teksilo_scene::{RectItem, Scene};

let mut scene = Scene::new();
let r = || RectItem::new(Rect::new(0.0, 0.0, 10.0, 10.0));
let lower = scene.add_item(r(), Point::ZERO);
let upper = scene.add_item(r(), Point::ZERO);
scene.set_z(lower, 1.0);
scene.set_z(upper, 2.0);

let middle = scene.add_item(r(), Point::ZERO);
let z = scene.z_between(lower, upper).expect("room between 1.0 and 2.0");
scene.set_z(middle, z);
assert!(scene.z(middle).unwrap() > 1.0 && scene.z(middle).unwrap() < 2.0);

// No room between an item and itself.
assert_eq!(scene.z_between(lower, lower), None);
Source

pub fn orphan(&mut self, id: ItemId)

Promote id’s direct children to root-level (clear their parent field). Used when an app wants to remove id without dropping its children — call orphan(id) then remove(id). No-op when id is unknown or has no children.

Fires one ItemChange::ParentChanged per detached child and re-buckets every detached subtree in the spatial index — the children’s scene_transform shifts (no longer composes id’s) so their scene-space AABBs change. Without re-bucketing the index, items_in_rect and item_at would return stale results.

Apps wanting visual stability across the orphan call should first bake id’s scene_transform into each child’s local_pos + transform; otherwise children visibly jump.

Source

pub fn items_in_rect(&self, scene_rect: Rect) -> Vec<ItemId>

All items whose scene-AABB intersects scene_rect.

Broad phase: the spatial index returns every id bucketed in any cell touched by scene_rect. Narrow phase: each candidate goes through scene_rect, which itself dispatches via entry_index (an HashMap<ItemId, usize>), so the per-candidate cost is O(parent-chain-depth) — not O(N). Total query is O(visible × chain) instead of O(N).

Source

pub fn item_thumbnails(&self) -> Vec<(Rect, Color)>

Snapshot every visible item — both tiers — as a (scene_rect, color) pair suitable for a minimap thumbnail. Filters out items with HAS_NO_CONTENTS (logical-only) and items hidden by IS_VISIBLE / a hidden ancestor — the visible-effective set matches what the SceneView’s paint walk renders.

Ordered by insertion (low z first). A lightweight item’s color comes from SceneItem::thumbnail_color (its fill / stroke / a neutral grey); a heavyweight widget entry has no SceneItem, so it’s shown in a neutral tint — a minimap that omitted the heavyweight tier would misrepresent a widget-heavy scene (cards, nodes), so both tiers are included.

Source

pub fn item_shape(&self, id: ItemId) -> Option<ItemShape>

The shape of any entry, in its local coordinates.

For a lightweight item this is SceneItem::shape. A heavyweight widget entry has no SceneItem at all — Scene::item returns None for it — so its shape is defined to be ItemShape::bounds of its local_bounds. That is not a placeholder: a widget’s silhouette is its layout box, the arena hit-tests it as one, and the marquee has always selected heavyweight entries through the same index query as lightweight ones. Stating it here is what keeps a …ItemShape selection mode meaningful for a scene whose primary objects are cards.

None for an unknown id.

Source

pub fn item_contains( &self, id: ItemId, scene_pt: Point, view_scale: f32, ) -> bool

Whether id’s shape contains scene_pt.

Works for both tiers (see Scene::item_shape). view_scale is the live view zoom, consulted only by a cosmetic stroke band; pass 1.0 when there is no view.

Source

pub fn item_region(&self, id: ItemId) -> Option<SceneRegion>

The item’s own shape re-published as a scene-space region — what a collision query asks the rest of the scene about. None for an unknown id, a shape of ItemShape::none, or a screen-anchored (IGNORES_TRANSFORMATIONS) item, whose silhouette is not in scene space at all.

Source

pub fn items_in_region( &self, region: &SceneRegion, mode: ItemSelectionMode, view_scale: f32, ) -> Vec<ItemId>

Items matching region under mode — both tiers, exactly like Scene::items_in_rect, which this generalises.

Broad-phased by the spatial index on region.bounding_rect(), then narrow-phased per mode: the …ItemBoundingRect modes compare the item’s scene AABB against the region in scene space; the …ItemShape modes map the region into the item’s local frame and compare it against Scene::item_shape. For an item with a non-identity transform those are different tests — see ItemSelectionMode.

view_scale is the live view zoom, consulted only by a cosmetic stroke band; pass 1.0 when there is no view. Visibility and selectability are not filtered here — this is a pure geometry query, and the caller (e.g. SceneSelection::commit_marquee) applies its own flag policy.

Items flagged IGNORES_TRANSFORMATIONS are skipped, for the reason Scene::item_at skips them: they are anchored in screen space, so their local_bounds is a screen rectangle and the scene AABB the index holds for them is a fiction. Comparing a scene-space region against that fiction is a guess, and the two query families used to disagree about whether to make it — the point queries declined and the marquee did not. Reaching screen-pinned chrome needs a region that arrives in screen space, which is what Scene::item_at_in_view does for a point; there is no region twin of it yet, so a marquee cannot select pinned chrome at all.

Source

pub fn item_at(&self, scene_pt: Point) -> Option<ItemId>

Topmost lightweight item whose shape contains scene_pt, at unit view scale. See Scene::item_at_scaled for the zoom-aware form and Scene::item_at_in_view for the one that also places screen-anchored items.

Heavyweight widget entries are skipped: their hit-testing is the arena’s job, and a scene-space answer would contradict it.

Items flagged IGNORES_TRANSFORMATIONS are also skipped, deterministically. They are anchored in screen space, so a scene-space point cannot place them at all; answering with a coin-flip (which is what comparing them against a scene AABB amounts to) is worse than not answering. Use Scene::item_at_in_view, or SceneView dispatch, when the query needs them.

“Topmost” is Scene::paint_key order, so an Over-band item beats a higher-z Under one, and two equal-z items resolve to the later-inserted one — the same answer SceneView dispatch gives.

Hidden and disabled entries are excluded: this is a hit test, and ItemFlags::IS_VISIBLE and ItemFlags::IS_ENABLED both say so. See Scene::is_hit_testable. For a pure geometry query that ignores flags, use Scene::items_in_region or Scene::item_contains.

Source

pub fn item_at_scaled(&self, scene_pt: Point, view_scale: f32) -> Option<ItemId>

Scene::item_at at an explicit view zoom.

The zoom reaches exactly one thing: a cosmetic stroke band, whose width is in device pixels and therefore covers fewer scene units the further you zoom in. Passing the live scale is what makes this agree with SceneView’s own dispatch, which has always had it.

Source

pub fn entry_at(&self, scene_pt: Point, view_scale: f32) -> Option<ItemId>

Topmost entry of either tier whose geometry contains scene_pt, in this scene’s one paint order.

The deliberate counterpart to Scene::item_at_scaled, which skips heavyweight entries because the arena owns their hit-testing. This one answers the different question a selection asks — “which entry is on top here?” — and answers it for a card with the card’s scene rectangle, which is exactly the rectangle SceneView laid the card out at. It is not a substitute for arena dispatch and must not be used to route an event to a widget; it is for deciding whether a press that already reached the view landed on something the view is holding.

Screen-anchored entries are skipped for the same reason item_at skips them: a scene-space point cannot place them.

Source

pub fn items_at(&self, scene_pt: Point) -> Vec<ItemId>

All lightweight items whose shape contains scene_pt, topmost-first by z. Same tier and IGNORES_TRANSFORMATIONS rules as Scene::item_at.

Source

pub fn items_at_scaled(&self, scene_pt: Point, view_scale: f32) -> Vec<ItemId>

Scene::items_at at an explicit view zoom.

Source

pub fn item_at_in_view( &self, screen_pt: Point, view_transform: Transform2D, ) -> Option<ItemId>

Topmost lightweight item under a screen point, resolving both hit spaces the way SceneView dispatch does.

A normal item is tested in scene space, at the view transform’s zoom; a screen-anchored (IGNORES_TRANSFORMATIONS) item is tested against its local_bounds rooted at its projected anchor, at unit scale — its local coordinates are screen coordinates, so it has no zoom to convert. This is the query to use when a scene may contain screen-pinned chrome; Scene::item_at deliberately declines to guess.

Source

pub fn is_hit_testable(&self, id: ItemId) -> bool

Whether id takes part in pointer hit-testing — visible along its whole ancestor chain AND enabled.

This is crate::pick::hit_testable resolved against the scene, and it is the one place the two flag contracts in ItemFlags are honoured: IS_VISIBLE (“neither painted nor hit-tested”) and IS_ENABLED (“pass clicks through to items beneath”). false for unknown ids.

Source

pub fn colliding_items(&self, id: ItemId) -> Vec<ItemId>

Items overlapping id’s shape, excluding id itself.

Apps use this for “which other items overlap this card?” — graph editors checking node-on-node overlap, CAD canvases finding adjacent geometry. Backed by the spatial index, so the cost is O(visible) not O(N).

Defaults to ItemSelectionMode::IntersectsItemShape, so a stroke-only connector collides along its line rather than across its bounding box. Pass IntersectsItemBoundingRect to Scene::colliding_items_with for the cheaper box test.

Screen-anchored items neither collide nor are collided with — see Scene::items_in_region, which this is built on, and Scene::item_region, which declines to publish one for them.

Source

pub fn colliding_items_with( &self, id: ItemId, mode: ItemSelectionMode, ) -> Vec<ItemId>

Source

pub fn items_along_path(&self, path: &Path) -> Vec<ItemId>

Items lying along path — a real region query, not the AABB-of-the-path approximation this used to be.

The path is treated as a zero-width closed region: an item is picked when the path crosses it or encloses it. For a connector — a line with a width — pass that width to Scene::items_along_path_with, so the query asks about the band the user can see.

Source

pub fn items_along_path_with( &self, path: &Path, stroke_width: f32, mode: ItemSelectionMode, ) -> Vec<ItemId>

Scene::items_along_path with an explicit stroke width and ItemSelectionMode.

stroke_width greater than zero makes the region the path’s band rather than its interior: “what does this 4 dp connector touch?”.

Source

pub fn len(&self) -> usize

Number of entries in the scene.

Source

pub fn is_empty(&self) -> bool

Whether the scene is empty.

Source

pub fn ids(&self) -> Vec<ItemId>

All ids in insertion order.

Source

pub fn index(&self) -> &dyn SpatialIndex

Borrow the spatial index (diagnostics / tests).

Source

pub fn add_magnet(&mut self, item: ItemId, magnet: Magnet) -> MagnetId

Attach a Magnet to item and return its MagnetId.

Magnets are local to their item (their local_pos is in the item’s frame), so they follow the item under any move / rotate / scale via the same scene_transform the item uses. No-op returning a fresh-but-unowned id if item is unknown — callers add magnets to items they just created.

Bumps the AT-structure change counter (magnets are AT structure) so a SceneView with magnetism enabled re-walks its synthetic magnet nodes.

Source

pub fn remove_magnet(&mut self, magnet: MagnetId)

Remove a magnet by id. No-op if the id is unknown.

Source

pub fn clear_magnets(&mut self, item: ItemId)

Remove every magnet attached to item. No-op if none.

Source

pub fn set_magnet_local_pos(&mut self, magnet: MagnetId, local_pos: Point)

Move a magnet to a new position in its owning item’s local frame. No-op if the id is unknown.

Source

pub fn set_magnet_enabled(&mut self, magnet: MagnetId, enabled: bool)

Enable or disable a magnet. Disabled magnets are skipped by broad-phase, feedback, the keyboard cycle, and AT emission. No-op if the id is unknown.

Source

pub fn magnet_ids_of(&self, item: ItemId) -> Vec<MagnetId>

The ids of every magnet attached to item, in insertion order (enabled and disabled alike). Empty if item is unknown or has no magnets.

Source

pub fn magnet_owner(&self, magnet: MagnetId) -> Option<ItemId>

The owning item of a magnet, or None if the id is unknown.

Source

pub fn magnet_enabled(&self, magnet: MagnetId) -> bool

Whether a magnet is enabled. false for an unknown id.

Source

pub fn magnet_scene_pos(&self, magnet: MagnetId) -> Option<Point>

A magnet’s position in scene coordinates (its local position projected through its owning item’s scene_transform). None for an unknown id or a degenerate item transform.

Source

pub fn magnet(&self, magnet: MagnetId) -> Option<MagnetRef>

Resolve a magnet to a borrow-free MagnetRef snapshot (id, owning item, role, payload clone, current scene position). None for an unknown id or a degenerate item transform.

Source

pub fn compute_item_snap( &self, dragged: ItemId, drag_delta: Vec2, capture_radius: f32, predicate: &dyn Fn(&MagnetRef, &MagnetRef) -> MagnetVerdict, ) -> Option<MagnetSnap>

Compute the best item-drag snap: the dragged item is visually offset by drag_delta, and each of its enabled magnets seeks the nearest accepting magnet on another item within capture_radius (in scene units). Returns the globally closest accepting pair, or None if nothing accepts within range.

Pure mechanism: it collects candidates under a brief read, then runs the consumer predicate with no scene borrow held, so the predicate may inspect payloads freely. snap_vector added to drag_delta aligns the dragged magnet onto its target.

Source

pub fn compute_port_snap( &self, source: MagnetId, cursor_scene: Point, capture_radius: f32, predicate: &dyn Fn(&MagnetRef, &MagnetRef) -> MagnetVerdict, ) -> Option<(MagnetRef, Option<Rc<dyn Any>>)>

Compute the best port-drag snap: a single source magnet is dragging a transient wire whose free end is at cursor_scene. Finds the nearest accepting target magnet within capture_radius (scene units), excluding the source’s own magnet. Returns the target MagnetRef and the accepting verdict’s payload, or None.

Source

pub fn nearest_magnet(&self, scene_pt: Point, radius: f32) -> Option<MagnetId>

The nearest enabled magnet to scene_pt within radius (scene units), or None. Used by the view to start a port-drag from a grabbed magnet handle (the handle’s grab area is a screen-pixel disc, converted to scene units by the caller).

Source

pub fn add_a11y_group(&mut self, builder: A11yGroupBuilder) -> A11yGroupId

Declare a virtual AT group. The group has no visual counterpart — it exists so the AT walker can emit an AT node under which items / other groups / widgets can be reparented.

Source

pub fn remove_a11y_group(&mut self, id: A11yGroupId)

Remove a logical group; orphaned references fall back to SceneView root. Relations / live / landmarks / categories targeting this group are cleaned up too.

Source

pub fn a11y_group(&self, id: A11yGroupId) -> Option<&A11yGroup>

Borrow a logical group by id.

Source

pub fn set_a11y_parent(&mut self, child: A11yNode, parent: Option<A11yNode>)

Declare a logical-parent relationship for AT (independent of visual placement).

Source

pub fn a11y_parent_of(&self, child: A11yNode) -> Option<A11yNode>

The currently-declared logical parent of a node.

Source

pub fn add_a11y_relation( &mut self, from: A11yNode, kind: A11yRelation, to: A11yNode, )

Declare an AT relationship between two nodes.

Source

pub fn a11y_relations(&self) -> &[(A11yNode, A11yRelation, A11yNode)]

All declared AT relations.

Source

pub fn set_a11y_live(&mut self, node: A11yNode, live: Live)

Mark a node as a live region. Pass Live::Off to clear.

Source

pub fn set_a11y_landmark(&mut self, node: A11yNode, role: Role)

Mark a node as a landmark by overriding its role. Pass Role::Unknown to clear.

Source

pub fn set_a11y_categories( &mut self, node: A11yNode, categories: &[A11yCategory], )

Tag a node with rotor / quick-nav categories.

Source

pub fn a11y_categories_of(&self, node: A11yNode) -> Option<&[A11yCategory]>

Read declared categories for a node.

Source

pub fn a11y_live_of(&self, node: A11yNode) -> Option<Live>

Read a node’s declared live-region politeness. None when it is not a live region.

The read half of set_a11y_live, added because a consumer restoring a RemovedItem has to be able to check that the semantics came back — and because the other four decorations already had one.

Source

pub fn a11y_landmark_of(&self, node: A11yNode) -> Option<Role>

Read a node’s declared landmark role. None when it is not a landmark.

Trait Implementations§

Source§

impl Debug for Scene

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Scene

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl !Freeze for Scene

§

impl !RefUnwindSafe for Scene

§

impl !Send for Scene

§

impl !Sync for Scene

§

impl !UnwindSafe for Scene

§

impl Unpin for Scene

§

impl UnsafeUnpin for Scene

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<T> Downcast for T
where T: Any,

§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.
§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Convert Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
§

fn as_any(&self) -> &(dyn Any + 'static)

Convert &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
§

impl<T> Downcast<T> for T

§

fn downcast(&self) -> &T

§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
§

impl<T> NoneValue for T
where T: Default,

§

type NoneType = T

§

fn null_value() -> T

The none-equivalent value.
§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<T> Upcast<T> for T

§

fn upcast(&self) -> Option<&T>

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more