1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
// The `on_*` callback fields are `Option<Box<dyn Fn(..) -> Message>>`. Naming each
// one would trade a legible type for a single-use alias, so the lint is off here
// rather than worked around.
//! # iced_nodegraph
//!
//! A node graph editor widget for the [iced](https://github.com/iced-rs/iced)
//! GUI framework. Nodes are ordinary iced widgets placed on an infinite zoom/pan
//! canvas and wired together through typed pins; everything on the canvas -
//! node bodies, edges, pins, shadows, background - is drawn by one WGPU pipeline
//! as signed distance fields, so it stays sharp at every zoom level.
//!
//! <figure class="demo-embed compact" data-scene="hello_world">
//! <div class="demo-frame">
//! <a href="https://tuco86.github.io/iced_nodegraph/demo_hello_world/index.html">
//! <img src="https://tuco86.github.io/iced_nodegraph/gallery/hello_world.png" alt="The hello_world demo: an email workflow graph with four connected nodes">
//! </a>
//! </div>
//! <figcaption>Runs live when scrolled into view (WebGPU, Chrome recommended); a still image otherwise. Click the canvas for keyboard input.</figcaption>
//! </figure>
//!
//! ## Quick Start
//!
//! ```rust,no_run
//! use iced_nodegraph::{PinRef, edge, node, node_graph};
//! use iced::{Element, Theme, Point, Vector};
//! use iced::widget::text;
//! use iced_wgpu::Renderer;
//!
//! #[derive(Debug, Clone)]
//! enum Message {
//! EdgeConnected { from: PinRef, to: PinRef },
//! NodesMoved { delta: Vector, node_ids: Vec<usize> },
//! }
//!
//! fn view(edges: &[(PinRef, PinRef)]) -> Element<'_, Message, Theme, Renderer> {
//! node_graph()
//! .on_connect(|from, to| Message::EdgeConnected { from, to })
//! .on_move(|delta, node_ids| Message::NodesMoved { delta, node_ids })
//! .push_node(node(0, Point::new(100.0, 100.0), text("Node A")))
//! .push_node(node(1, Point::new(300.0, 100.0), text("Node B")))
//! .edges(edges.iter().map(|(from, to)| edge((), *from, *to)))
//! .into()
//! }
//! ```
//!
//! ## Ids
//!
//! Nodes, pins, edges and anchors carry your own id types, and a pin can carry
//! a payload. Those five types are named once, on a marker implementing
//! [`Ids`]; [`Indexed`] (`usize` ids, no edge id, no payload) is the default
//! and what [`node_graph`] builds. Any type that is
//! `Clone + Eq + Hash + Debug + Send + Sync + 'static` is an id, so a newtype,
//! an enum or a `uuid::Uuid` needs no impl.
//!
//! ```rust,no_run
//! use iced_nodegraph::{Ids, NodeGraph, PinRef, node};
//! use iced::{Element, Point};
//! use iced::widget::text;
//!
//! #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
//! struct AppIds;
//!
//! impl Ids for AppIds {
//! type NodeId = u64;
//! type PinId = &'static str;
//! type EdgeId = u64;
//! type AnchorId = usize;
//! type Payload = ();
//! }
//!
//! #[derive(Debug, Clone)]
//! enum Message {
//! Connected(PinRef<AppIds>, PinRef<AppIds>),
//! }
//!
//! fn view() -> Element<'static, Message> {
//! NodeGraph::<AppIds, _, _, _>::new()
//! .on_connect(Message::Connected)
//! .push_node(node(7, Point::ORIGIN, text("seven")))
//! .into()
//! }
//! ```
//!
//! The marker is the one type the compiler cannot infer, so it is named on the
//! graph and in the messages that carry a [`PinRef`]; every builder and
//! callback infers it from there.
//!
//! ## What the host owns
//!
//! The widget is stateless between frames and never mutates your data model. It
//! renders the nodes and edges you pass in and reports intent through callbacks;
//! your `update` applies the change and the next `view` reflects it. Which makes
//! these yours to uphold:
//!
//! - **Unique node ids.** Lookups resolve by id, so a duplicate push is ignored
//! (first wins) and debug builds assert on it. Prefer a stable id from your
//! data - a database key, `uuid::Uuid`, a typed newtype - over a hand-managed
//! counter.
//! - **Edge dedupe.** [`on_connect`](NodeGraph::on_connect) fires on every snap
//! during a drag, not on release, so one drag can report several connections.
//! The default [`can_connect`](NodeGraph::can_connect) already rejects a second
//! edge into an occupied input; for replace-on-drop instead, drop that rule
//! (see [`connection`]) and remove the prior edge whose input matches - `to` is
//! always the input pin.
//! - **Applying moves, deletes and clones.** `on_move` / `on_delete` /
//! `on_clone` report intent only.
//! - **Applying selection.** Optional: the widget keeps a working selection, so
//! clicks and the selection box work on their own.
//! [`on_select`](NodeGraph::on_select) reports it; to own it, mark the matching
//! nodes with [`Node::selected`] on the next `view` and your value takes over
//! whenever it changes. Selection is a node property, so there is no ordering
//! to get right. The camera is the same story through
//! [`on_camera`](NodeGraph::on_camera) and [`camera`](NodeGraph::camera).
//!
//! ## Core types
//!
//! - [`PinRef`] addresses a pin as `(node_id, pin_id)` over your [`Ids`]. It is
//! the endpoint type in every connection callback.
//! - [`PinEnd`] is the richer endpoint view (direction, occupancy, payload)
//! handed to [`can_connect`](NodeGraph::can_connect); [`PinInfo`] is the
//! per-pin view handed to the style closures.
//! - The camera is not a type you hold: push pan and zoom in through
//! [`NodeGraph::camera`] and read the user's back out through
//! [`on_camera`](NodeGraph::on_camera), both as a plain `(Point, f32)`. To
//! frame content programmatically, give the graph an
//! [`id`](NodeGraph::id) and run the [`focus`] task, as with
//! `text_input::focus` or `scrollable::scroll_to`.
//! - [`Keymap`] holds the rebindable key and pointer bindings, with
//! platform-appropriate defaults.
//! - [`Particle`] is a marker gliding along an edge, for traffic or queued
//! work: [`particle`]`(born, speed)` pushed through [`Edge::particles`] is
//! drawn `speed * age` world units along the cable and vanishes at the
//! input pin. The widget keeps the redraw loop running while one moves;
//! the host owns each particle's lifetime by pushing it every frame.
//! - [`GraphInfo`] carries per-frame diagnostics to
//! [`on_info`](NodeGraph::on_info): element counts (total / in view / culled),
//! CPU op timings, and the SDF pipeline's GPU work and memory counters.
//! Delivered one frame behind, since it is measured during `draw`.
//!
//! ## Styling
//!
//! Node, edge and pin styles are flat concrete structs. Override individual
//! fields with struct-update over a theme-derived default, inside a `.style()`
//! closure that also receives the element's status:
//!
//! ```rust,no_run
//! use iced::{widget::text, Color, Point};
//! use iced_nodegraph::{ColorQuad, Indexed, Node, NodeStyle, default_node_style, node};
//!
//! # #[derive(Debug, Clone)]
//! # enum Message {}
//! # let (pos, body) = (Point::ORIGIN, text("body"));
//! let n: Node<'_, Indexed, Message, iced::Theme, iced::Renderer> = node(0, pos, body).style(|theme, status| NodeStyle {
//! fill_color: ColorQuad::solid(Color::from_rgb(0.2, 0.3, 0.5)),
//! ..default_node_style(theme, status)
//! });
//! ```
//!
//! The presets have the same shape as the defaults, so they drop straight into
//! `.style(..)`: [`NodeStyle::input`], [`NodeStyle::process`],
//! [`NodeStyle::output`], [`NodeStyle::comment`]; [`EdgeStyle::error`],
//! [`EdgeStyle::disabled`], [`EdgeStyle::highlighted`], [`EdgeStyle::data_flow`],
//! [`EdgeStyle::debug`]. All derive from the theme's palette, like iced's own
//! `button::success`.
//!
//! [`Pattern`] (re-exported from `iced_nodegraph_sdf`) controls every stroke:
//! `Pattern::solid(width)`, `Pattern::dashed(width, dash, gap)`,
//! `Pattern::dotted(spacing, radius)`, plus `.flow(speed)` to animate it along
//! the stroke. An animated pattern self-drives redraws - no host frame loop
//! needed.
//!
//! The style closure intentionally does not receive the node id: your `view` loop
//! already has it, along with any per-node status. Derive the status there and
//! capture it:
//!
//! ```rust,no_run
//! use iced::{widget::text, Point};
//! use iced_nodegraph::{NodeStyle, Pattern, default_node_style, node, node_graph};
//!
//! # #[derive(Debug, Clone)]
//! # enum Message {}
//! # struct MyNode { id: usize, pos: Point }
//! # let nodes = [MyNode { id: 0, pos: Point::ORIGIN }];
//! # let is_working = |_: usize| true;
//! let ng = node_graph::<Message, iced::Theme, iced::Renderer>().nodes(nodes.iter().map(|n| {
//! let working = is_working(n.id);
//! node(n.id, n.pos, text("body")).style(move |theme, status| {
//! let base = default_node_style(theme, status);
//! if working {
//! NodeStyle { border_pattern: Pattern::dashed(2.0, 6.0, 4.0).flow(40.0), ..base }
//! } else {
//! base
//! }
//! })
//! }));
//! ```
//!
//! The chrome the widget draws itself follows the same closure-plus-default
//! shape, one type per thing: [`GraphStyle`] (canvas background and tiling) via
//! [`graph_style`](NodeGraph::graph_style), [`SelectionBoxStyle`] via
//! [`selection_box_style`](NodeGraph::selection_box_style), [`CuttingToolStyle`]
//! via [`cutting_tool_style`](NodeGraph::cutting_tool_style), [`ParticleStyle`]
//! via [`Particle::style`], and
//! [`MinimapStyle`] via [`minimap_style`](NodeGraph::minimap_style) for the
//! overview [`minimap`](NodeGraph::minimap) puts in a corner. A selected node's
//! look is not chrome - it comes from the node's own closure through
//! [`NodeStatus`].
//!
//! <figure class="demo-embed compact" data-scene="styling">
//! <div class="demo-frame">
//! <a href="https://tuco86.github.io/iced_nodegraph/demo_styling/index.html">
//! <img src="https://tuco86.github.io/iced_nodegraph/gallery/styling.png" alt="The styling demo: four preset-styled nodes with routing anchors and the style control panel">
//! </a>
//! </div>
//! <figcaption>Runs live when scrolled into view (WebGPU, Chrome recommended); a still image otherwise. Click the canvas for keyboard input.</figcaption>
//! </figure>
//!
//! ## Interaction
//!
//! Connections behave like physical plugs: a dragged edge *snaps* to a compatible
//! pin and [`on_connect`](NodeGraph::on_connect) fires immediately; moving away
//! unsnaps and fires [`on_disconnect`](NodeGraph::on_disconnect). Releasing while
//! snapped keeps the connection, releasing while loose discards the drag. Treat
//! these callbacks as live state, not a commit. A drop onto a pin the
//! validation turns down snaps nothing, and
//! [`on_connect_refused`](NodeGraph::on_connect_refused) names that pair so
//! the host can say why.
//!
//! [`NodeGraph::snap_grid`] puts a dragged node's origin on a world-unit grid.
//! The preview and the delta [`on_move`](NodeGraph::on_move) reports are the
//! same number, and holding [`Keymap::snap_override`] (Alt by default) suspends
//! the snap for as long as it is held. A drag carrying several nodes shares one
//! delta computed on the grabbed node, so a group keeps its internal layout.
//!
//! A node built with [`Node::frame`] is a backdrop: it renders behind every
//! non-frame node, answers a press only where none of them covers the point,
//! and carries the nodes fully inside its bounds along with it. Membership is
//! resolved from the live layout at press time, so a node dropped into a frame
//! is carried by the next drag and the host registers nothing.
//!
//! Rebindable bindings and their platform defaults are documented on [`Keymap`];
//! the full control scheme including mouse and touch gestures is in the
//! [repository README](https://github.com/tuco86/iced_nodegraph#controls).
//!
//! <figure class="demo-embed compact" data-scene="interaction">
//! <div class="demo-frame">
//! <a href="https://tuco86.github.io/iced_nodegraph/demo_interaction/index.html">
//! <img src="https://tuco86.github.io/iced_nodegraph/gallery/interaction.png" alt="The interaction demo: typed pins with valid and rejected connections">
//! </a>
//! </div>
//! <figcaption>Runs live when scrolled into view (WebGPU, Chrome recommended); a still image otherwise. Click the canvas for keyboard input.</figcaption>
//! </figure>
//!
//! ## Nesting
//!
//! A [`NodeGraph`] is an ordinary element and can be a node's body. Each graph
//! keeps its own camera, selection and drag state and reports through its own
//! callbacks; its [`Ids`] need not match the enclosing graph's.
//!
//! Give the nested graph an explicit size (`.width(Length::Fixed(..))` and
//! `.height(..)`, or a sized `container`). Node bodies are laid out against
//! unbounded limits, so the default `Length::Fill` resolves to an infinite
//! graph, which debug builds reject at layout like any other infinite body.
//!
//! The inner canvas takes the presses and wheel ticks that land on it; the
//! containing node is moved by a body region outside the inner graph, such as
//! a title bar. Keyboard shortcuts resolve innermost-first; disable bindings
//! on one graph's [`Keymap`] to route them to the other.
//!
//! The inner graph's pins are its own: the outer graph never connects to them.
//! Nested state lives with the node index like any other body state.
//!
//! ## Coordinates
//!
//! Screen space (pixels from input and the viewport) and world space (the
//! infinite canvas) are distinct [`euclid`](https://docs.rs/euclid) types, so
//! mixing them is a compile error:
//!
//! - **Screen -> world**: `world = screen / zoom - position`
//! - **World -> screen**: `screen = (world + position) * zoom`
//! - **Zoom at cursor**: `new_pos = old_pos + cursor_screen * (1/new_zoom - 1/old_zoom)`
//!
//! The derivations, and the screen/world type discipline they rest on, are in
//! `node_graph/camera.rs`.
//!
//! ## Platform support
//!
//! Native Windows, macOS and Linux via WGPU, and WebAssembly on WebGPU-capable
//! browsers. There is no WebGL and no tiny-skia fallback, so Chrome/Chromium is
//! recommended on the web.
//!
//! Every demo runs in the browser at <https://tuco86.github.io/iced_nodegraph/>.
//!
//! <link rel="stylesheet" href="../gallery/pkg/demo.css"><script type="module" src="../gallery/pkg/demo-loader.js"></script>
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
// Re-export iced_nodegraph_sdf types downstream crates meet through the widget
pub use ;
// Re-exported so downstream crates can name the exact iced types this widget's
// API is built from without risking a version mismatch. The `iced` umbrella
// crate is not re-exported: it is not a dependency of this crate (see Cargo.toml).
pub use iced_wgpu;
pub use iced_widget;