Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Magnet

Magnetism: typed snap-and-connect between anchor points on scene items.

Public types

KindName
structMagnetId — Opaque identifier for a Magnet inside a Scene
enumMagnetRole — The direction a magnet faces in a connection
structMagnet — A magnetism anchor attached to a scene item
structMagnetRef — An owned, borrow-free snapshot of one magnet, handed to the accept/reject predicate and carried in a MagnetConnection
enumMagnetVerdict — The result of running the accept/reject predicate on a candidate magnet pair
structMagnetConnection — A formed connection between two magnets, delivered to the consumer's on_connect handler on release (mouse) or confirm (keyboard)
structMagnetSnap — The chosen snap when a dragged item's magnet aligns onto another item's magnet
enumMarkerVisibility — When the SceneView paints magnet markers
enumMagnetVisualState — The visual state of a magnet as the feedback renderer sees it
structMagnetMarker — One magnet's render data, handed to the feedback renderer
structMagnetFeedback — Everything the magnetism feedback renderer needs for one frame, in scene coordinates (the canvas is already in the view-transform scope)
structMagnetismConfig — Per-view magnetism configuration, installed via SceneView::magnetism

Public functions

Magnet

MagnetId

ReturnsFunction
Methods
u64as_u64()

MagnetRef

MagnetVerdict

ReturnsFunction
Constructors
Selfaccept()
Selfaccept_with<P: 'static>(payload: P)
Methods
boolis_accept()

MagnetConnection

MagnetSnap

MagnetMarker

MagnetFeedback

MagnetismConfig

Detailed description

A magnet is a local point on an item (relative to the item's anchor, like a child point), carrying a type-erased payload ('static, downcastable) and a directional MagnetRole. An item can carry several. During an interaction the scene broad-phases nearby magnets, runs an accept/reject predicate per candidate pair, snaps so the closest accepting pair aligns, and on release a connection event carries the payloads to the consumer.

Mechanism in scene, policy in the consumer

This module and Scene own the mechanism: magnet geometry, broad-phase, snap math, and the connection result. They do not own policy — which magnet types are compatible, what a connection means, or whether a connection persists. Compatibility is decided entirely by the predicate the consumer supplies to Scene::compute_item_snap / Scene::compute_port_snap; the meaning of a formed connection is decided by the consumer's on_connect handler. No widget-tree or designer concept (slot, category, insertion index) leaks into this API; those live in the payloads and the predicate.

MagnetRole is generic node-graph / diagram vocabulary used by the scene only for default feedback (which end is the source) and for ordering the keyboard connect flow. It is advisory: the predicate is always authoritative on whether two magnets may connect.

Example — two items connected by a typed magnet pair

#![allow(unused)]
fn main() {
use teksilo_scene::{Scene, RectItem, Magnet, MagnetRole, MagnetRef, MagnetVerdict};
use teksilo_canvas::{Point, Rect, Vec2};

// A predicate that accepts Source → Target pairs on different items.
fn source_to_target(a: &MagnetRef, b: &MagnetRef) -> MagnetVerdict {
    if a.item == b.item { return MagnetVerdict::Reject; }
    match (a.role, b.role) {
        (MagnetRole::Source, MagnetRole::Target)
        | (MagnetRole::Target, MagnetRole::Source) => MagnetVerdict::accept(),
        _ => MagnetVerdict::Reject,
    }
}

let mut scene = Scene::new();

// Dragged item with a Source magnet at its local origin.
let dragged = scene.add_item(
    RectItem::new(Rect::new(0.0, 0.0, 10.0, 10.0)),
    Point::ZERO,
);
scene.add_magnet(dragged, Magnet::new(Point::ZERO).role(MagnetRole::Source));

// Target item 100 px to the right with a Target magnet at its local origin.
let target = scene.add_item(
    RectItem::new(Rect::new(0.0, 0.0, 10.0, 10.0)),
    Point::new(100.0, 0.0),
);
scene.add_magnet(target, Magnet::new(Point::ZERO).role(MagnetRole::Target));

// The dragged item is 5 px away from snapping; capture radius 20 px.
if let Some(snap) = scene.compute_item_snap(dragged, Vec2::new(95.0, 0.0), 20.0, &source_to_target) {
    // snap_vector carries the dragged item exactly onto the target magnet.
    assert!((snap.snap_vector.x - 5.0).abs() < 1e-3);
}
}

API reference

📖 Full rustdoc API for this module

pub struct MagnetId

Import: use teksilo_scene::MagnetId; — add teksilo-scene to [dependencies]: teksilo does not re-export it.

Opaque identifier for a Magnet inside a Scene.

Globally unique within a process, minted by MagnetId::next. Stable across the magnet's lifetime; removing a magnet (or its owning item) retires its id permanently — ids are never reused.

#![allow(unused)]
fn main() {
pub struct MagnetId(pub(crate) u64);
}

Methods

pub fn as_u64(self) -> u64

Raw numeric value, used by AccessKit's synthetic-NodeId derivation.

pub enum MagnetRole

Import: use teksilo_scene::MagnetRole; — add teksilo-scene to [dependencies]: teksilo does not re-export it.

The direction a magnet faces in a connection.

Advisory only — the scene uses it for default feedback (arrow direction, which end starts the keyboard flow), but the accept/reject predicate is always the authority on compatibility. A node-graph output port is a Source, an input port is a Target; a snap point that can be either end is Bidirectional.

#![allow(unused)]
fn main() {
pub enum MagnetRole { /* variants */ }
}

Variants

  • Source — Originates a connection (e.g. a node-graph output port).
  • Target — Receives a connection (e.g. a node-graph input port).
  • Bidirectional — Can be either end of a connection.

pub struct Magnet

Import: use teksilo_scene::Magnet; — add teksilo-scene to [dependencies]: teksilo does not re-export it.

A magnetism anchor attached to a scene item.

Built fluently and handed to SceneModel::add_magnet. Carries a local-frame position, a MagnetRole, an optional type-erased payload, an enabled flag, and an optional accessibility label.

#![allow(unused)]
fn main() {
pub struct Magnet { /* fields */ }
}

Methods

pub fn new(local_pos: Point) -> Self

A magnet at local_pos in the owning item's local frame, role Bidirectional, no payload, enabled.

pub fn role(mut self, role: MagnetRole) -> Self (default: MagnetRole::Bidirectional)

Set the connection direction (advisory — see MagnetRole).

pub fn payload<P: 'static>(mut self, payload: P) -> Self

Attach a type-erased payload the predicate and the connection event can downcast. Cheap to carry around (held in an Rc).

pub fn payload_rc(mut self, payload: Rc<dyn Any>) -> Self (default: None)

Attach an already-Rc-wrapped payload (use when several magnets share one payload object).

pub fn label(mut self, label: impl Into<LocalizedString>) -> Self

The accessibility name announced for this magnet's synthetic AT node. Defaults to a generic role-based label when unset.

pub fn enabled(mut self, on: bool) -> Self

Disabled magnets are skipped by broad-phase, feedback, the keyboard cycle, and AT emission. Enabled by default.

pub struct MagnetRef

Import: use teksilo_scene::MagnetRef; — add teksilo-scene to [dependencies]: teksilo does not re-export it.

An owned, borrow-free snapshot of one magnet, handed to the accept/reject predicate and carried in a MagnetConnection.

The payload is an Rc clone, so a snapshot can outlive the borrow taken to collect candidates. The predicate inspects these snapshots while a shared (read-only) scene borrow is held — it may read the model but must not mutate it. The on_connect handler, by contrast, runs after every borrow is dropped and may freely mutate the model (add an edge item, reparent, fire an intent).

#[non_exhaustive]: the crate hands this to consumer code — it is what a predicate inspects — the same reason MagnetVisualState carries it. Build one with new.

#![allow(unused)]
fn main() {
pub struct MagnetRef { /* fields */ }
}

Fields

  • id: MagnetId — The magnet's id.
  • item: ItemId — The item the magnet is attached to.
  • role: MagnetRole — The magnet's advisory direction.
  • payload: Option<Rc<dyn Any>> — The magnet's payload, if any (an Rc clone of the stored one).
  • scene_pos: Point — The magnet's current position in scene coordinates.

Methods

pub fn new( id: MagnetId, item: ItemId, role: MagnetRole, payload: Option<Rc<dyn Any>>, scene_pos: Point, ) -> Self

A snapshot stated field by field — the constructor #[non_exhaustive] takes the place of a struct literal for. The scene builds its own; this is for a consumer testing its predicate.

pub fn payload_as<P: 'static>(&self) -> Option<&P>

Borrow the payload downcast to P, or None if absent or a different type. The ergonomic way to read a typed payload inside a predicate.

pub enum MagnetVerdict

Import: use teksilo_scene::MagnetVerdict; — add teksilo-scene to [dependencies]: teksilo does not re-export it.

The result of running the accept/reject predicate on a candidate magnet pair. "Both payloads in, reject or accept-with-payload out."

#![allow(unused)]
fn main() {
pub enum MagnetVerdict { /* variants */ }
}

Variants

  • Reject — The pair may not connect; the scene skips it.
  • Accept (Option<Rc<dyn Any>>) — The pair may connect. The optional payload is attached to the resulting MagnetConnection (e.g. a derived edge descriptor).

Methods

pub fn accept() -> Self

Accept with no extra connection payload.

pub fn accept_with<P: 'static>(payload: P) -> Self

Accept and attach a typed connection payload.

pub fn is_accept(&self) -> bool

Whether this verdict accepts the pair.

pub struct MagnetConnection

Import: use teksilo_scene::MagnetConnection; — add teksilo-scene to [dependencies]: teksilo does not re-export it.

A formed connection between two magnets, delivered to the consumer's on_connect handler on release (mouse) or confirm (keyboard).

from is the magnet that initiated the connection (the dragged item's magnet, the grabbed port, or the keyboard-activated source); to is the magnet it connected onto. payload is whatever the predicate's MagnetVerdict::Accept carried.

#[non_exhaustive]: the crate hands this to consumer code — it is the argument of on_connect. Build one with new.

#![allow(unused)]
fn main() {
pub struct MagnetConnection { /* fields */ }
}

Fields

  • from: MagnetRef — The initiating magnet.
  • to: MagnetRef — The receiving magnet.
  • payload: Option<Rc<dyn Any>> — The connection payload from the accepting verdict, if any.

Methods

pub fn new(from: MagnetRef, to: MagnetRef, payload: Option<Rc<dyn Any>>) -> Self

A connection stated field by field — the constructor #[non_exhaustive] takes the place of a struct literal for. The scene builds its own; this is for a consumer testing on_connect.

pub fn payload_as<P: 'static>(&self) -> Option<&P>

Borrow the connection payload downcast to P.

pub struct MagnetSnap

Import: use teksilo_scene::MagnetSnap; — add teksilo-scene to [dependencies]: teksilo does not re-export it.

The chosen snap when a dragged item's magnet aligns onto another item's magnet. Returned by Scene::compute_item_snap.

A heavyweight consumer that drives its own drag uses snap_vector to place the item so from lands on to, and resolves from / to via Scene::magnet to build the connection for its own on_connect.

#[non_exhaustive]: the crate hands this to consumer code — a heavyweight consumer driving its own drag reads it. Build one with new.

#![allow(unused)]
fn main() {
pub struct MagnetSnap { /* fields */ }
}

Fields

  • from: MagnetId — The dragged item's magnet that is snapping.
  • to: MagnetId — The stationary magnet it snaps onto.
  • snap_vector: Vec2 — Add this to the drag delta (or the item's position) so from's scene position coincides with to's.
  • payload: Option<Rc<dyn Any>> — The accepting verdict's payload, if any.
  • distance: f32 — Scene-space distance between the pair before snapping (the tie-break used to pick the closest accepting pair).

Methods

pub fn new( from: MagnetId, to: MagnetId, snap_vector: Vec2, payload: Option<Rc<dyn Any>>, distance: f32, ) -> Self

A snap stated field by field — the constructor #[non_exhaustive] takes the place of a struct literal for. The scene computes its own; this is for a consumer's own tests.

pub enum MarkerVisibility

Import: use teksilo_scene::MarkerVisibility; — add teksilo-scene to [dependencies]: teksilo does not re-export it.

When the SceneView paints magnet markers.

#![allow(unused)]
fn main() {
pub enum MarkerVisibility { /* variants */ }
}

Variants

  • Always — Always draw a marker for every enabled magnet (busy, but the clearest discoverability — good for a dedicated editor).
  • DuringInteraction — Draw markers only while an interaction is in progress (an item drag, a port drag, or keyboard connect mode). The default — keeps an idle scene clean.
  • Never — Never draw markers (the consumer paints its own via the feedback hook, or wants no visual at all).

pub enum MagnetVisualState

Import: use teksilo_scene::MagnetVisualState; — add teksilo-scene to [dependencies]: teksilo does not re-export it.

The visual state of a magnet as the feedback renderer sees it.

#[non_exhaustive]: the crate hands this to consumer code — a custom MagnetFeedback must match it — and the set grows with every interaction state magnetism learns. A renderer's wildcard arm then keeps meaning "draw it plainly" instead of failing to compile.

#![allow(unused)]
fn main() {
pub enum MagnetVisualState { /* variants */ }
}

Variants

  • Idle — A normal, idle magnet.
  • Candidate — A magnet the current interaction could connect to (it passes the predicate against the active source).
  • Snapped — The magnet the active interaction is currently snapped onto.
  • Focused — The keyboard-focused magnet (connect mode).
  • PendingSource — The keyboard-activated source magnet awaiting a target.

pub struct MagnetMarker

Import: use teksilo_scene::MagnetMarker; — add teksilo-scene to [dependencies]: teksilo does not re-export it.

One magnet's render data, handed to the feedback renderer.

#[non_exhaustive]: the crate hands this to consumer code — it is what a custom MagnetismConfig::feedback draws, and it is the struct MagnetVisualState lives inside, which already says so for itself. Build one with new.

#![allow(unused)]
fn main() {
pub struct MagnetMarker { /* fields */ }
}

Fields

  • id: MagnetId — The magnet's id.
  • scene_pos: Point — Its current scene position.
  • role: MagnetRole — Its advisory role.
  • state: MagnetVisualState — Its visual state for this frame.

Methods

pub fn new(id: MagnetId, scene_pos: Point, role: MagnetRole, state: MagnetVisualState) -> Self

A marker stated field by field — the constructor #[non_exhaustive] takes the place of a struct literal for.

pub struct MagnetFeedback

Import: use teksilo_scene::MagnetFeedback; — add teksilo-scene to [dependencies]: teksilo does not re-export it.

Everything the magnetism feedback renderer needs for one frame, in scene coordinates (the canvas is already in the view-transform scope). The built-in renderer draws markers plus a connector; a custom MagnetismConfig::feedback closure receives the same data.

#[non_exhaustive]: the crate hands this to consumer code — it is the argument of a custom feedback renderer, and the set grows with every cue magnetism learns to draw. Build one with new.

#![allow(unused)]
fn main() {
pub struct MagnetFeedback { /* fields */ }
}

Fields

  • zoom: f32 — The view's current geometric zoom, so the renderer can size constant-pixel chrome as pixels / zoom in scene units.
  • markers: Vec<MagnetMarker> — Eligible magnets to mark, with their per-frame state.
  • connector: Option<(Point, Point, bool)> — A connector to draw between two scene points, if an interaction is forming one: the active item-drag snap pair, the port-drag wire (source to snapped target or cursor), or the keyboard preview (pending source to focused candidate). true in the second field marks an accepted connector (drawn solid / highlighted) versus a tentative one (the free port-drag wire).

Methods

pub fn new( zoom: f32, markers: Vec<MagnetMarker>, connector: Option<(Point, Point, bool)>, ) -> Self

A frame of feedback stated field by field — the constructor #[non_exhaustive] takes the place of a struct literal for.

pub struct MagnetismConfig

Import: use teksilo_scene::MagnetismConfig; — add teksilo-scene to [dependencies]: teksilo does not re-export it.

Per-view magnetism configuration, installed via SceneView::magnetism.

Holds the consumer's policy — the accept/reject predicate and the on_connect handler — plus presentation knobs. The scene supplies the mechanism (snap math, broad-phase, feedback rendering, the connection event); this config is where the consumer plugs its policy in.

#![allow(unused)]
fn main() {
pub struct MagnetismConfig { /* fields */ }
}

Methods

pub fn new(predicate: impl Fn(&MagnetRef, &MagnetRef) -> MagnetVerdict + 'static) -> Self

A config with the given accept/reject predicate and defaults: 14 px capture radius, markers during interaction, the built-in feedback renderer, m to toggle keyboard connect mode, enabled. Install an on_connect handler to actually do something on connect.

pub fn on_connect( mut self, f: impl Fn(&MagnetConnection, &mut EventContext) + 'static, ) -> Self

The handler invoked when a connection is formed (mouse release or keyboard confirm). Runs with a live EventContext and no scene borrow held, so it may mutate the model (add an edge item, reparent), call scene.add_a11y_relation, or fire an intent.

pub fn capture_px(mut self, px: f32) -> Self

Capture and grab radius in screen pixels (converted to scene units by dividing by the live zoom, so snapping feels consistent at any zoom). Default 14.

pub fn markers(mut self, markers: MarkerVisibility) -> Self

When magnet markers are painted. Default MarkerVisibility::DuringInteraction.

pub fn feedback( mut self, f: impl Fn(&mut Canvas, &PaintContext, &MagnetFeedback) + 'static, ) -> Self

Replace the built-in feedback renderer with a custom one. The closure paints in scene coordinates (the canvas already has the view transform pushed).

pub fn connect_key(mut self, key: Key) -> Self

The key that toggles keyboard connect mode while the SceneView is focused. Default m.

pub fn enabled(mut self, on: impl Into<Prop<bool>>) -> Self

Set the enabled state, statically or reactively (an app-owned signal drives enabled/disabled from e.g. a toolbar toggle).

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

The reactive enabled signal, for a toolbar to read or bind.

pub fn is_enabled(&self) -> bool

Whether magnetism is currently enabled.