imnodes 0.5.0

Rust bindings to https://github.com/Nelarius/imnodes
Documentation
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
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
#![deny(missing_docs)]
#![warn(rustdoc::broken_intra_doc_links)]

/*!
High-level, safe bindings for [imnodes](https://github.com/Nelarius/imnodes),
a node editor extension for [imgui-rs](https://github.com/imgui-rs/imgui-rs).

Based on the C API bindings generated by [cimnodes](https://github.com/cimgui/cimnodes).

# Usage

1. Create an [`imnodes::Context`](crate::Context) once at application startup.
2. Create an [`imnodes::EditorContext`](crate::EditorContext) for each node editor you want to display.
3. In your UI loop, call [`EditorContext::set_as_current_editor`] before defining the editor UI.
4. Use the [`imnodes::editor`](crate::editor) function to define the node editor content.

```no_run
# use imgui;
# use imnodes;
# struct State { editor_context: imnodes::EditorContext }
# let mut state: State = unimplemented!();
# let ui: &imgui::Ui = unimplemented!();
// Before calling editor:
state.editor_context.set_as_current_editor();
let mut node_id_gen = state.editor_context.new_identifier_generator();

let outer_scope = imnodes::editor(&mut state.editor_context, |mut editor_scope| {
    editor_scope.add_node(node_id_gen.next_node(), |mut node_scope| {
        node_scope.add_titlebar(|| ui.text("My Node"));
        node_scope.add_input(node_id_gen.next_input_pin(), imnodes::PinShape::Circle, || {
             ui.text("Input Pin");
        });
        node_scope.add_output(node_id_gen.next_output_pin(), imnodes::PinShape::Triangle, || {
             ui.text("Output Pin");
        });
    });
    // Add more nodes and links...
});

// Handle events after the editor scope ends
if let Some(link) = outer_scope.links_created() {
   // handle new link creation
}
if let Some(dropped_link_id) = outer_scope.get_destroyed_link() {
    // handle link deletion
}



Identifiers

imnodes requires unique integer IDs for nodes, pins (attributes), and links.
The [IdentifierGenerator] struct provides a convenient way to generate these unique IDs within an editor context.
*/

use imnodes_sys as sys;

/// Re-export the low-level FFI bindings if the feature is enabled.
/// Use with caution.
#[cfg(feature = "include_low_level_bindings")]
pub mod internal {
    pub use imnodes_sys::*;
}

mod context;
pub use context::*;

mod helpers;
// Helpers are exposed directly on EditorContext or as standalone functions where appropriate.

mod styling;
pub use styling::*;

mod scopes;
pub use scopes::*;

// Re-export essential types from the sys crate or imgui crate
pub use sys::{ImNodesIO, ImVec2}; // Re-export ImNodesStyle via the wrapper 'Style' in styling.rs

/// Provides unique identifiers for nodes, pins, attributes, and links within an editor context.
///
/// Create one generator per [EditorContext] using [EditorContext::new_identifier_generator].
#[derive(Debug)]
pub struct IdentifierGenerator {
    current_node: i32,
    // Input pins, output pins, and static attributes share the same ID space in imnodes.
    current_attribute: i32,
    current_link: i32,
}

impl IdentifierGenerator {
    /// Creates a new identifier generator, starting all IDs from 0.
    /// Intended to be called via [EditorContext::new_identifier_generator].
    pub(crate) fn new() -> Self {
        Self {
            current_node: 0,
            current_attribute: 0,
            current_link: 0,
        }
    }

    /// Generates the next unique ID for a node.
    pub fn next_node(&mut self) -> NodeId {
        let id = self.current_node;
        self.current_node = self.current_node.checked_add(1).unwrap();
        NodeId { id }
    }

    /// Generates the next unique ID for an input pin.
    pub fn next_input_pin(&mut self) -> InputPinId {
        let id = self.current_attribute;
        self.current_attribute = self.current_attribute.checked_add(1).unwrap();
        InputPinId { id }
    }

    /// Generates the next unique ID for an output pin.
    pub fn next_output_pin(&mut self) -> OutputPinId {
        let id = self.current_attribute;
        self.current_attribute = self.current_attribute.checked_add(1).unwrap();
        OutputPinId { id }
    }

    /// Generates the next unique ID for a static attribute (one without a pin).
    pub fn next_attribute(&mut self) -> AttributeId {
        let id = self.current_attribute;
        self.current_attribute = self.current_attribute.checked_add(1).unwrap();
        AttributeId { id }
    }

    /// Generates the next unique ID for a link.
    pub fn next_link(&mut self) -> LinkId {
        let id = self.current_link;
        self.current_link = self.current_link.checked_add(1).unwrap();
        LinkId { id }
    }
}

/// Identifier for a static attribute within a node (an attribute without a pin).
///
/// IDs must be unique within the editor context. Generated using [IdentifierGenerator::next_attribute].
#[repr(C)]
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub struct AttributeId {
    id: i32,
}

impl From<AttributeId> for i32 {
    fn from(val: AttributeId) -> Self {
        val.id
    }
}

/// Specifies the coordinate system for getting or setting node positions.
///
/// The node's position can be expressed in three coordinate systems:
/// * ScreenSpace: The origin (0,0) is the top-left corner of the main application window. Useful for placing nodes relative to the entire window or other ImGui elements.
/// * EditorSpace: The origin (0,0) is the top-left corner of the node editor canvas (the area within the imnodes::editor block). Node positions are relative to this canvas, ignoring panning.
/// * GridSpace: The origin (0,0) is the logical origin of the node grid, taking into account the current panning of the editor canvas. This is often the most intuitive system for saving/loading node layouts, as positions remain consistent regardless of the view's pan offset. See [EditorContext::get_panning()] and [EditorContext::reset_panning()].
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
pub enum CoordinateSystem {
    /// Origin at the top-left of the application window.
    ScreenSpace,
    /// Origin at the top-left of the node editor canvas.
    EditorSpace,
    /// Origin at the top-left of the node editor canvas, adjusted by panning.
    GridSpace,
}

/// Identifier for a Node.
///
/// IDs must be unique within the editor context. Generated using [IdentifierGenerator::next_node].
#[repr(C)]
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub struct NodeId {
    id: i32,
}

impl NodeId {
    /// Sets whether this node can be dragged by the user.
    #[doc(alias = "SetNodeDraggable")]
    #[must_use]
    pub fn set_draggable(&self, draggable: bool) -> &Self {
        // Safety: C API call with a valid node ID.
        unsafe { sys::imnodes_SetNodeDraggable(self.id, draggable) };
        self
    }

    /// Pans the editor view to center on this node.
    #[doc(alias = "EditorContextMoveToNode")]
    #[must_use]
    pub fn move_editor_to(&self) -> &Self {
        // Safety: C API call with a valid node ID.
        // Assumes the editor context owning this node is current.
        unsafe {
            sys::imnodes_EditorContextMoveToNode(self.id);
        }
        self
    }

    /// Gets the dimensions (width, height) of this node.
    ///
    /// Note: This must be called *after* the node has been submitted in the current frame,
    /// otherwise the dimensions might be outdated or zero.
    #[doc(alias = "GetNodeDimensions")]
    #[must_use]
    pub fn get_dimensions(&self) -> ImVec2 {
        let mut dimension = ImVec2 { x: 0.0, y: 0.0 };
        // Safety: C API call. `dimension` is written to by the function.
        unsafe {
            sys::imnodes_GetNodeDimensions(core::ptr::from_mut(&mut dimension), self.id);
        }
        dimension
    }

    /// Sets the position of the top-left corner of this node in the specified coordinate system.
    #[doc(
        alias = "SetNodeScreenSpacePos",
        alias = "SetNodeEditorSpacePos",
        alias = "SetNodeGridSpacePos"
    )]
    #[must_use]
    pub fn set_position(&self, x: f32, y: f32, coordinate_system: CoordinateSystem) -> &Self {
        let pos = ImVec2 { x, y };
        // Safety: C API calls with a valid node ID and position.
        match coordinate_system {
            CoordinateSystem::ScreenSpace => unsafe {
                sys::imnodes_SetNodeScreenSpacePos(self.id, pos);
            },
            CoordinateSystem::EditorSpace => unsafe {
                sys::imnodes_SetNodeEditorSpacePos(self.id, pos);
            },
            CoordinateSystem::GridSpace => unsafe {
                sys::imnodes_SetNodeGridSpacePos(self.id, pos);
            },
        };
        self
    }

    /// Gets the position of the top-left corner of this node in the specified coordinate system.
    ///
    /// Note: This must be called *after* the node has been submitted in the current frame,
    /// otherwise the position might be outdated.
    #[doc(
        alias = "GetNodeScreenSpacePos",
        alias = "GetNodeEditorSpacePos",
        alias = "GetNodeGridSpacePos"
    )]
    #[must_use]
    pub fn get_position(&self, coordinate_system: CoordinateSystem) -> ImVec2 {
        let mut pos = ImVec2 { x: 0.0, y: 0.0 };
        // Safety: C API calls. `pos` is written to by the function.
        match coordinate_system {
            CoordinateSystem::ScreenSpace => unsafe {
                sys::imnodes_GetNodeScreenSpacePos(core::ptr::from_mut(&mut pos), self.id);
            },
            CoordinateSystem::EditorSpace => unsafe {
                sys::imnodes_GetNodeEditorSpacePos(core::ptr::from_mut(&mut pos), self.id);
            },
            CoordinateSystem::GridSpace => unsafe {
                sys::imnodes_GetNodeGridSpacePos(core::ptr::from_mut(&mut pos), self.id);
            },
        };
        pos
    }

    /// Aligns the node's top-left corner to the grid lines.
    /// Requires the [`StyleFlags::GridSnapping`] flag to be enabled in the style.
    #[doc(alias = "SnapNodeToGrid")]
    #[must_use]
    pub fn snap_to_grid(&self) -> &Self {
        // Safety: C API call with a valid node ID.
        unsafe { sys::imnodes_SnapNodeToGrid(self.id) };
        self
    }

    /// Selects this node.
    #[doc(alias = "SelectNode")]
    #[must_use]
    pub fn select(&self) -> &Self {
        // Safety: C API call with a valid node ID.
        unsafe { sys::imnodes_SelectNode(self.id) };
        self
    }

    /// Deselects this node.
    /// If no other nodes are selected, this is equivalent to [`EditorContext::clear_node_selection`].
    #[doc(alias = "ClearNodeSelection_Int")]
    #[must_use]
    pub fn deselect(&self) -> &Self {
        // Safety: C API call with a valid node ID.
        unsafe { sys::imnodes_ClearNodeSelection_Int(self.id) };
        self
    }

    /// Checks if this node is currently selected.
    #[doc(alias = "IsNodeSelected")]
    #[must_use]
    pub fn is_selected(&self) -> bool {
        // Safety: C API call with a valid node ID.
        unsafe { sys::imnodes_IsNodeSelected(self.id) }
    }
}

impl From<NodeId> for i32 {
    fn from(val: NodeId) -> Self {
        val.id
    }
}

/// Generic identifier for either an input or output pin.
///
/// Corresponds to attribute_id in the C++ source.
/// Can be obtained by converting from [InputPinId] or [OutputPinId].
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub struct PinId {
    id: i32,
}

impl PinId {
    /// Checks if the user started dragging a new link from this pin in the current frame.
    ///
    /// Call this after [crate::editor()] has returned.
    #[doc(alias = "IsLinkStarted")]
    #[must_use]
    pub fn is_start_of_link(&self, scope: &OuterScope) -> bool {
        Some(*self) == scope.from_where_link_started()
    }

    /// Checks if the user dropped a dragged link originating from this pin without connecting it.
    ///
    /// Call this after [`crate::editor()`] has returned.
    ///
    /// # Arguments
    /// * `including_detached_links`: If `true`, also returns `true` if an existing link was detached from this pin and then dropped. If `false`, only triggers for newly created links that are dropped.
    #[doc(alias = "IsLinkDropped")]
    #[must_use]
    pub fn dropped_link(&self, including_detached_links: bool, scope: &OuterScope) -> bool {
        Some(*self) == scope.from_where_link_dropped(including_detached_links)
    }
}

/// Identifier for an input pin (rendered on the left side of a node).
///
/// IDs must be unique within the editor context. Generated using [IdentifierGenerator::next_input_pin].
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub struct InputPinId {
    id: i32,
}

impl From<InputPinId> for i32 {
    fn from(val: InputPinId) -> Self {
        val.id
    }
}

impl From<InputPinId> for PinId {
    fn from(val: InputPinId) -> Self {
        Self { id: val.id }
    }
}

/// Identifier for an output pin (rendered on the right side of a node).
///
/// IDs must be unique within the editor context. Generated using [IdentifierGenerator::next_output_pin].
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub struct OutputPinId {
    id: i32,
}

impl From<OutputPinId> for i32 {
    fn from(val: OutputPinId) -> Self {
        val.id
    }
}

impl From<OutputPinId> for PinId {
    fn from(val: OutputPinId) -> Self {
        Self { id: val.id }
    }
}

/// Identifier for a link between two pins.
///
/// IDs must be unique within the editor context. Generated using [IdentifierGenerator::next_link].
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub struct LinkId {
    id: i32,
}

impl LinkId {
    /// Checks if this link was destroyed (detached) by the user in the current frame.
    ///
    /// Call this after [crate::editor()] has returned.
    #[doc(alias = "IsLinkDestroyed")]
    #[must_use]
    pub fn is_destroyed(&self, scope: &OuterScope) -> bool {
        Some(*self) == scope.get_destroyed_link()
    }

    /// Selects this link.
    #[doc(alias = "SelectLink")]
    #[must_use]
    pub fn select(&self) -> &Self {
        // Safety: C API call with a valid link ID.
        unsafe { sys::imnodes_SelectLink(self.id) };
        self
    }

    /// Deselects this link.
    /// If no other links are selected, this is equivalent to [`EditorContext::clear_link_selection`].
    #[doc(alias = "ClearLinkSelection_Int")]
    #[must_use]
    pub fn deselect(&self) -> &Self {
        // Safety: C API call with a valid link ID.
        unsafe { sys::imnodes_ClearLinkSelection_Int(self.id) };
        self
    }

    /// Checks if this link is currently selected.
    #[doc(alias = "IsLinkSelected")]
    #[must_use]
    pub fn is_selected(&self) -> bool {
        // Safety: C API call with a valid link ID.
        unsafe { sys::imnodes_IsLinkSelected(self.id) }
    }
}

impl From<LinkId> for i32 {
    fn from(val: LinkId) -> Self {
        val.id
    }
}

/// Trait implemented by elements that can be hovered over by the mouse.
pub trait Hoverable {
    /// Checks if this UI element is being hovered over by the mouse cursor in the current frame.
    ///
    /// Call this after [crate::editor()] has returned.
    /// For checking editor canvas hovering, see [crate::scopes::EditorScope::is_hovered()].
    #[doc(
        alias = "IsPinHovered",
        alias = "IsNodeHovered",
        alias = "IsLinkHovered"
    )]
    fn is_hovered(&self, scope: &OuterScope) -> bool;
}

impl Hoverable for OutputPinId {
    /// Checks if the pin is hovered.
    #[doc(alias = "IsPinHovered")]
    fn is_hovered(&self, scope: &OuterScope) -> bool {
        Some(PinId { id: self.id }) == scope.get_hovered_pin()
    }
}

impl Hoverable for InputPinId {
    /// Checks if the pin is hovered.
    #[doc(alias = "IsPinHovered")]
    fn is_hovered(&self, scope: &OuterScope) -> bool {
        Some(PinId { id: self.id }) == scope.get_hovered_pin()
    }
}

impl Hoverable for NodeId {
    /// Checks if the node is hovered.
    #[doc(alias = "IsNodeHovered")]
    fn is_hovered(&self, _scope: &OuterScope) -> bool {
        // Can be checked globally, doesn't need the scope.
        Some(*self) == get_hovered_node()
    }
}

impl Hoverable for LinkId {
    /// Checks if the link is hovered.
    #[doc(alias = "IsLinkHovered")]
    fn is_hovered(&self, scope: &OuterScope) -> bool {
        Some(*self) == scope.get_hovered_link()
    }
}

/// Returns the ID of the node currently being hovered over, if any.
/// This can be called outside the [OuterScope] if needed.
#[doc(alias = "IsNodeHovered")]
#[must_use]
pub fn get_hovered_node() -> Option<NodeId> {
    let mut id: i32 = -1;
    // Safety: C API call. id is potentially written to.
    let ok = unsafe { sys::imnodes_IsNodeHovered(core::ptr::from_mut(&mut id)) };
    if ok && id >= 0 {
        Some(NodeId { id })
    } else {
        None
    }
}

/// Represents a link successfully created by the user in the current frame.
///
/// Obtained from [OuterScope::links_created()]. Contains the start and end pins
/// as well as the start and end nodes involved in the link creation.
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
pub struct Link {
    /// The ID of the node where the link started.
    pub start_node: NodeId,
    /// The ID of the node where the link ended.
    pub end_node: NodeId,
    /// The ID of the output pin where the link started.
    pub start_pin: OutputPinId,
    /// The ID of the input pin where the link ended.
    pub end_pin: InputPinId,
    /// Flag indicating if the link was created by snapping to a pin node when [AttributeFlags::EnableLinkCreationOnSnap] is enabled.
    pub created_from_snap: bool,
}