egui_snarl/
ui.rs

1//! This module provides functionality for showing [`Snarl`] graph in [`Ui`].
2
3use std::{collections::HashMap, hash::Hash};
4
5use egui::{
6    Align, Color32, CornerRadius, Frame, Id, LayerId, Layout, Margin, Modifiers, PointerButton,
7    Pos2, Rect, Scene, Sense, Shape, Stroke, StrokeKind, Style, Ui, UiBuilder, UiKind, UiStackInfo,
8    Vec2,
9    collapsing_header::paint_default_icon,
10    emath::{GuiRounding, TSTransform},
11    epaint::Shadow,
12    pos2,
13    response::Flags,
14    vec2,
15};
16use egui_scale::EguiScale;
17use smallvec::SmallVec;
18
19use crate::{InPin, InPinId, Node, NodeId, OutPin, OutPinId, Snarl, ui::wire::WireId};
20
21mod background_pattern;
22mod pin;
23mod scale;
24mod state;
25mod viewer;
26mod wire;
27
28use self::{
29    pin::AnyPin,
30    state::{NewWires, NodeState, RowHeights, SnarlState},
31    wire::{draw_wire, hit_wire, pick_wire_style},
32};
33
34pub use self::{
35    background_pattern::{BackgroundPattern, Grid},
36    pin::{AnyPins, PinInfo, PinShape, PinWireInfo, SnarlPin},
37    state::get_selected_nodes,
38    viewer::SnarlViewer,
39    wire::{WireLayer, WireStyle},
40};
41
42/// Controls how header, pins, body and footer are placed in the node.
43#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
45#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
46pub enum NodeLayoutKind {
47    /// Input pins, body and output pins are placed horizontally.
48    /// With header on top and footer on bottom.
49    ///
50    /// +---------------------+
51    /// |       Header        |
52    /// +----+-----------+----+
53    /// | In |           | Out|
54    /// | In |   Body    | Out|
55    /// | In |           | Out|
56    /// | In |           |    |
57    /// +----+-----------+----+
58    /// |       Footer        |
59    /// +---------------------+
60    ///
61    #[default]
62    Coil,
63
64    /// All elements are placed in vertical stack.
65    /// Header is on top, then input pins, body, output pins and footer.
66    ///
67    /// +---------------------+
68    /// |       Header        |
69    /// +---------------------+
70    /// | In                  |
71    /// | In                  |
72    /// | In                  |
73    /// | In                  |
74    /// +---------------------+
75    /// |       Body          |
76    /// +---------------------+
77    /// |                 Out |
78    /// |                 Out |
79    /// |                 Out |
80    /// +---------------------+
81    /// |       Footer        |
82    /// +---------------------+
83    Sandwich,
84
85    /// All elements are placed in vertical stack.
86    /// Header is on top, then output pins, body, input pins and footer.
87    ///
88    /// +---------------------+
89    /// |       Header        |
90    /// +---------------------+
91    /// |                 Out |
92    /// |                 Out |
93    /// |                 Out |
94    /// +---------------------+
95    /// |       Body          |
96    /// +---------------------+
97    /// | In                  |
98    /// | In                  |
99    /// | In                  |
100    /// | In                  |
101    /// +---------------------+
102    /// |       Footer        |
103    /// +---------------------+
104    FlippedSandwich,
105    // TODO: Add vertical layouts.
106}
107
108/// Controls how node elements are laid out.
109///
110///
111#[derive(Clone, Copy, Debug, PartialEq)]
112#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
113#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
114pub struct NodeLayout {
115    /// Controls method of laying out node elements.
116    pub kind: NodeLayoutKind,
117
118    /// Controls minimal height of pin rows.
119    pub min_pin_row_height: f32,
120
121    /// Controls how pin rows heights are set.
122    /// If true, all pin rows will have the same height, matching the largest content.
123    /// False by default.
124    pub equal_pin_row_heights: bool,
125}
126
127impl NodeLayout {
128    /// Creates new [`NodeLayout`] with `Coil` kind and flexible pin heights.
129    #[must_use]
130    #[inline]
131    pub const fn coil() -> Self {
132        NodeLayout {
133            kind: NodeLayoutKind::Coil,
134            min_pin_row_height: 0.0,
135            equal_pin_row_heights: false,
136        }
137    }
138
139    /// Creates new [`NodeLayout`] with `Sandwich` kind and flexible pin heights.
140    #[must_use]
141    #[inline]
142    pub const fn sandwich() -> Self {
143        NodeLayout {
144            kind: NodeLayoutKind::Sandwich,
145            min_pin_row_height: 0.0,
146            equal_pin_row_heights: false,
147        }
148    }
149
150    /// Creates new [`NodeLayout`] with `FlippedSandwich` kind and flexible pin heights.
151    #[must_use]
152    #[inline]
153    pub const fn flipped_sandwich() -> Self {
154        NodeLayout {
155            kind: NodeLayoutKind::FlippedSandwich,
156            min_pin_row_height: 0.0,
157            equal_pin_row_heights: false,
158        }
159    }
160
161    /// Returns new [`NodeLayout`] with same `kind` and specified pin heights.
162    #[must_use]
163    #[inline]
164    pub const fn with_equal_pin_rows(self) -> Self {
165        NodeLayout {
166            kind: self.kind,
167            min_pin_row_height: self.min_pin_row_height,
168            equal_pin_row_heights: true,
169        }
170    }
171
172    /// Returns new [`NodeLayout`] with same `kind` and specified minimum pin row height.
173    #[must_use]
174    #[inline]
175    pub const fn with_min_pin_row_height(self, min_pin_row_height: f32) -> Self {
176        NodeLayout {
177            kind: self.kind,
178            min_pin_row_height,
179            equal_pin_row_heights: self.equal_pin_row_heights,
180        }
181    }
182}
183
184impl From<NodeLayoutKind> for NodeLayout {
185    #[inline]
186    fn from(kind: NodeLayoutKind) -> Self {
187        NodeLayout {
188            kind,
189            min_pin_row_height: 0.0,
190            equal_pin_row_heights: false,
191        }
192    }
193}
194
195impl Default for NodeLayout {
196    #[inline]
197    fn default() -> Self {
198        NodeLayout::coil()
199    }
200}
201
202#[derive(Clone, Copy, Debug)]
203enum OuterHeights<'a> {
204    Flexible { rows: &'a [f32] },
205    Matching { max: f32 },
206    Tight,
207}
208
209#[derive(Clone, Copy, Debug)]
210struct Heights<'a> {
211    rows: &'a [f32],
212    outer: OuterHeights<'a>,
213    min_outer: f32,
214}
215
216impl Heights<'_> {
217    fn get(&self, idx: usize) -> (f32, f32) {
218        let inner = match self.rows.get(idx) {
219            Some(&value) => value,
220            None => 0.0,
221        };
222
223        let outer = match &self.outer {
224            OuterHeights::Flexible { rows } => match rows.get(idx) {
225                Some(&outer) => outer.max(inner),
226                None => inner,
227            },
228            OuterHeights::Matching { max } => max.max(inner),
229            OuterHeights::Tight => inner,
230        };
231
232        (inner, outer.max(self.min_outer))
233    }
234}
235
236impl NodeLayout {
237    fn input_heights(self, state: &NodeState) -> Heights<'_> {
238        let rows = state.input_heights().as_slice();
239
240        let outer = match (self.kind, self.equal_pin_row_heights) {
241            (NodeLayoutKind::Coil, false) => OuterHeights::Flexible {
242                rows: state.output_heights().as_slice(),
243            },
244            (_, true) => {
245                let mut max_height = 0.0f32;
246                for &h in state.input_heights() {
247                    max_height = max_height.max(h);
248                }
249                for &h in state.output_heights() {
250                    max_height = max_height.max(h);
251                }
252                OuterHeights::Matching { max: max_height }
253            }
254            (_, false) => OuterHeights::Tight,
255        };
256
257        Heights {
258            rows,
259            outer,
260            min_outer: self.min_pin_row_height,
261        }
262    }
263
264    fn output_heights(self, state: &'_ NodeState) -> Heights<'_> {
265        let rows = state.output_heights().as_slice();
266
267        let outer = match (self.kind, self.equal_pin_row_heights) {
268            (NodeLayoutKind::Coil, false) => OuterHeights::Flexible {
269                rows: state.input_heights().as_slice(),
270            },
271            (_, true) => {
272                let mut max_height = 0.0f32;
273                for &h in state.input_heights() {
274                    max_height = max_height.max(h);
275                }
276                for &h in state.output_heights() {
277                    max_height = max_height.max(h);
278                }
279                OuterHeights::Matching { max: max_height }
280            }
281            (_, false) => OuterHeights::Tight,
282        };
283
284        Heights {
285            rows,
286            outer,
287            min_outer: self.min_pin_row_height,
288        }
289    }
290}
291
292/// Controls style of node selection rect.
293#[derive(Clone, Copy, Debug, Default, PartialEq)]
294#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
295#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
296pub struct SelectionStyle {
297    /// Margin between selection rect and node frame.
298    pub margin: Margin,
299
300    /// Rounding of selection rect.
301    pub rounding: CornerRadius,
302
303    /// Fill color of selection rect.
304    pub fill: Color32,
305
306    /// Stroke of selection rect.
307    pub stroke: Stroke,
308}
309
310/// Controls how pins are placed in the node.
311#[derive(Clone, Copy, Debug, Default, PartialEq)]
312#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
313#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
314pub enum PinPlacement {
315    /// Pins are placed inside the node frame.
316    #[default]
317    Inside,
318
319    /// Pins are placed on the edge of the node frame.
320    Edge,
321
322    /// Pins are placed outside the node frame.
323    Outside {
324        /// Margin between node frame and pins.
325        margin: f32,
326    },
327}
328
329/// Style for rendering Snarl.
330#[derive(Clone, Copy, Debug, PartialEq)]
331#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
332#[cfg_attr(feature = "egui-probe", derive(egui_probe::EguiProbe))]
333pub struct SnarlStyle {
334    /// Controls how nodes are laid out.
335    /// Defaults to [`NodeLayoutKind::Coil`].
336    #[cfg_attr(
337        feature = "serde",
338        serde(skip_serializing_if = "Option::is_none", default)
339    )]
340    pub node_layout: Option<NodeLayout>,
341
342    /// Frame used to draw nodes.
343    /// Defaults to [`Frame::window`] constructed from current ui's style.
344    #[cfg_attr(
345        feature = "serde",
346        serde(
347            skip_serializing_if = "Option::is_none",
348            default,
349            with = "serde_frame_option"
350        )
351    )]
352    pub node_frame: Option<Frame>,
353
354    /// Frame used to draw node headers.
355    /// Defaults to [`node_frame`] without shadow and transparent fill.
356    ///
357    /// If set, it should not have shadow and fill should be either opaque of fully transparent
358    /// unless layering of header fill color with node fill color is desired.
359    #[cfg_attr(
360        feature = "serde",
361        serde(
362            skip_serializing_if = "Option::is_none",
363            default,
364            with = "serde_frame_option"
365        )
366    )]
367    pub header_frame: Option<Frame>,
368
369    /// Blank space for dragging node by its header.
370    /// Elements in the header are placed after this space.
371    #[cfg_attr(
372        feature = "serde",
373        serde(skip_serializing_if = "Option::is_none", default)
374    )]
375    pub header_drag_space: Option<Vec2>,
376
377    /// Whether nodes can be collapsed.
378    /// If true, headers will have collapsing button.
379    /// When collapsed, node will not show its pins, body and footer.
380    #[cfg_attr(
381        feature = "serde",
382        serde(skip_serializing_if = "Option::is_none", default)
383    )]
384    pub collapsible: Option<bool>,
385
386    /// Size of pins.
387    #[cfg_attr(feature = "egui-probe", egui_probe(range = 0.0..))]
388    #[cfg_attr(
389        feature = "serde",
390        serde(skip_serializing_if = "Option::is_none", default)
391    )]
392    pub pin_size: Option<f32>,
393
394    /// Default fill color for pins.
395    #[cfg_attr(
396        feature = "serde",
397        serde(skip_serializing_if = "Option::is_none", default)
398    )]
399    pub pin_fill: Option<Color32>,
400
401    /// Default stroke for pins.
402    #[cfg_attr(
403        feature = "serde",
404        serde(skip_serializing_if = "Option::is_none", default)
405    )]
406    pub pin_stroke: Option<Stroke>,
407
408    /// Shape of pins.
409    #[cfg_attr(
410        feature = "serde",
411        serde(skip_serializing_if = "Option::is_none", default)
412    )]
413    pub pin_shape: Option<PinShape>,
414
415    /// Placement of pins.
416    #[cfg_attr(
417        feature = "serde",
418        serde(skip_serializing_if = "Option::is_none", default)
419    )]
420    pub pin_placement: Option<PinPlacement>,
421
422    /// Width of wires.
423    #[cfg_attr(feature = "egui-probe", egui_probe(range = 0.0..))]
424    #[cfg_attr(
425        feature = "serde",
426        serde(skip_serializing_if = "Option::is_none", default)
427    )]
428    pub wire_width: Option<f32>,
429
430    /// Size of wire frame which controls curvature of wires.
431    #[cfg_attr(feature = "egui-probe", egui_probe(range = 0.0..))]
432    #[cfg_attr(
433        feature = "serde",
434        serde(skip_serializing_if = "Option::is_none", default)
435    )]
436    pub wire_frame_size: Option<f32>,
437
438    /// Whether to downscale wire frame when nodes are close.
439    #[cfg_attr(
440        feature = "serde",
441        serde(skip_serializing_if = "Option::is_none", default)
442    )]
443    pub downscale_wire_frame: Option<bool>,
444
445    /// Weather to upscale wire frame when nodes are far.
446    #[cfg_attr(
447        feature = "serde",
448        serde(skip_serializing_if = "Option::is_none", default)
449    )]
450    pub upscale_wire_frame: Option<bool>,
451
452    /// Controls default style of wires.
453    #[cfg_attr(
454        feature = "serde",
455        serde(skip_serializing_if = "Option::is_none", default)
456    )]
457    pub wire_style: Option<WireStyle>,
458
459    /// Layer where wires are rendered.
460    #[cfg_attr(
461        feature = "serde",
462        serde(skip_serializing_if = "Option::is_none", default)
463    )]
464    pub wire_layer: Option<WireLayer>,
465
466    /// Frame used to draw background
467    #[cfg_attr(
468        feature = "serde",
469        serde(
470            skip_serializing_if = "Option::is_none",
471            default,
472            with = "serde_frame_option"
473        )
474    )]
475    pub bg_frame: Option<Frame>,
476
477    /// Background pattern.
478    /// Defaults to [`BackgroundPattern::Grid`].
479    #[cfg_attr(
480        feature = "serde",
481        serde(skip_serializing_if = "Option::is_none", default)
482    )]
483    pub bg_pattern: Option<BackgroundPattern>,
484
485    /// Stroke for background pattern.
486    /// Defaults to `ui.visuals().widgets.noninteractive.bg_stroke`.
487    #[cfg_attr(
488        feature = "serde",
489        serde(skip_serializing_if = "Option::is_none", default)
490    )]
491    pub bg_pattern_stroke: Option<Stroke>,
492
493    /// Minimum viewport scale that can be set.
494    #[cfg_attr(feature = "egui-probe", egui_probe(range = 0.0..=1.0))]
495    #[cfg_attr(
496        feature = "serde",
497        serde(skip_serializing_if = "Option::is_none", default)
498    )]
499    pub min_scale: Option<f32>,
500
501    /// Maximum viewport scale that can be set.
502    #[cfg_attr(feature = "egui-probe", egui_probe(range = 1.0..))]
503    #[cfg_attr(
504        feature = "serde",
505        serde(skip_serializing_if = "Option::is_none", default)
506    )]
507    pub max_scale: Option<f32>,
508
509    /// Enable centering by double click on background
510    #[cfg_attr(
511        feature = "serde",
512        serde(skip_serializing_if = "Option::is_none", default)
513    )]
514    pub centering: Option<bool>,
515
516    /// Stroke for selection.
517    #[cfg_attr(
518        feature = "serde",
519        serde(skip_serializing_if = "Option::is_none", default)
520    )]
521    pub select_stoke: Option<Stroke>,
522
523    /// Fill for selection.
524    #[cfg_attr(
525        feature = "serde",
526        serde(skip_serializing_if = "Option::is_none", default)
527    )]
528    pub select_fill: Option<Color32>,
529
530    /// Flag to control how rect selection works.
531    /// If set to true, only nodes fully contained in selection rect will be selected.
532    /// If set to false, nodes intersecting with selection rect will be selected.
533    pub select_rect_contained: Option<bool>,
534
535    /// Style for node selection.
536    #[cfg_attr(
537        feature = "serde",
538        serde(skip_serializing_if = "Option::is_none", default)
539    )]
540    pub select_style: Option<SelectionStyle>,
541
542    /// Controls whether to show magnified text in crisp mode.
543    /// This zooms UI style to max scale and scales down the scene.
544    #[cfg_attr(
545        feature = "serde",
546        serde(skip_serializing_if = "Option::is_none", default)
547    )]
548    pub crisp_magnified_text: Option<bool>,
549
550    /// Controls smoothness of wire curves.
551    #[cfg_attr(
552        feature = "serde",
553        serde(skip_serializing_if = "Option::is_none", default)
554    )]
555    #[cfg_attr(
556        feature = "egui-probe",
557        egui_probe(range = 0.0f32..=10.0f32 by 0.05f32)
558    )]
559    pub wire_smoothness: Option<f32>,
560
561    #[doc(hidden)]
562    #[cfg_attr(feature = "egui-probe", egui_probe(skip))]
563    #[cfg_attr(feature = "serde", serde(skip_serializing, default))]
564    /// Do not access other than with .., here to emulate `#[non_exhaustive(pub)]`
565    pub _non_exhaustive: (),
566}
567
568impl SnarlStyle {
569    fn get_node_layout(&self) -> NodeLayout {
570        self.node_layout.unwrap_or_default()
571    }
572
573    fn get_pin_size(&self, style: &Style) -> f32 {
574        self.pin_size.unwrap_or(style.spacing.interact_size.y * 0.6)
575    }
576
577    fn get_pin_fill(&self, style: &Style) -> Color32 {
578        self.pin_fill
579            .unwrap_or(style.visuals.widgets.active.bg_fill)
580    }
581
582    fn get_pin_stroke(&self, style: &Style) -> Stroke {
583        self.pin_stroke.unwrap_or_else(|| {
584            Stroke::new(
585                style.visuals.widgets.active.bg_stroke.width,
586                style.visuals.widgets.active.bg_stroke.color,
587            )
588        })
589    }
590
591    fn get_pin_shape(&self) -> PinShape {
592        self.pin_shape.unwrap_or(PinShape::Circle)
593    }
594
595    fn get_pin_placement(&self) -> PinPlacement {
596        self.pin_placement.unwrap_or_default()
597    }
598
599    fn get_wire_width(&self, style: &Style) -> f32 {
600        self.wire_width
601            .unwrap_or_else(|| self.get_pin_size(style) * 0.1)
602    }
603
604    fn get_wire_frame_size(&self, style: &Style) -> f32 {
605        self.wire_frame_size
606            .unwrap_or_else(|| self.get_pin_size(style) * 3.0)
607    }
608
609    fn get_downscale_wire_frame(&self) -> bool {
610        self.downscale_wire_frame.unwrap_or(true)
611    }
612
613    fn get_upscale_wire_frame(&self) -> bool {
614        self.upscale_wire_frame.unwrap_or(false)
615    }
616
617    fn get_wire_style(&self) -> WireStyle {
618        self.wire_style.unwrap_or(WireStyle::Bezier5)
619    }
620
621    fn get_wire_layer(&self) -> WireLayer {
622        self.wire_layer.unwrap_or(WireLayer::BehindNodes)
623    }
624
625    fn get_header_drag_space(&self, style: &Style) -> Vec2 {
626        self.header_drag_space
627            .unwrap_or_else(|| vec2(style.spacing.icon_width, style.spacing.icon_width))
628    }
629
630    fn get_collapsible(&self) -> bool {
631        self.collapsible.unwrap_or(true)
632    }
633
634    fn get_bg_frame(&self, style: &Style) -> Frame {
635        self.bg_frame.unwrap_or_else(|| Frame::canvas(style))
636    }
637
638    fn get_bg_pattern_stroke(&self, style: &Style) -> Stroke {
639        self.bg_pattern_stroke
640            .unwrap_or(style.visuals.widgets.noninteractive.bg_stroke)
641    }
642
643    fn get_min_scale(&self) -> f32 {
644        self.min_scale.unwrap_or(0.2)
645    }
646
647    fn get_max_scale(&self) -> f32 {
648        self.max_scale.unwrap_or(2.0)
649    }
650
651    fn get_node_frame(&self, style: &Style) -> Frame {
652        self.node_frame.unwrap_or_else(|| Frame::window(style))
653    }
654
655    fn get_header_frame(&self, style: &Style) -> Frame {
656        self.header_frame
657            .unwrap_or_else(|| self.get_node_frame(style).shadow(Shadow::NONE))
658    }
659
660    fn get_centering(&self) -> bool {
661        self.centering.unwrap_or(true)
662    }
663
664    fn get_select_stroke(&self, style: &Style) -> Stroke {
665        self.select_stoke.unwrap_or_else(|| {
666            Stroke::new(
667                style.visuals.selection.stroke.width,
668                style.visuals.selection.stroke.color.gamma_multiply(0.5),
669            )
670        })
671    }
672
673    fn get_select_fill(&self, style: &Style) -> Color32 {
674        self.select_fill
675            .unwrap_or_else(|| style.visuals.selection.bg_fill.gamma_multiply(0.3))
676    }
677
678    fn get_select_rect_contained(&self) -> bool {
679        self.select_rect_contained.unwrap_or(false)
680    }
681
682    fn get_select_style(&self, style: &Style) -> SelectionStyle {
683        self.select_style.unwrap_or_else(|| SelectionStyle {
684            margin: style.spacing.window_margin,
685            rounding: style.visuals.window_corner_radius,
686            fill: self.get_select_fill(style),
687            stroke: self.get_select_stroke(style),
688        })
689    }
690
691    fn get_crisp_magnified_text(&self) -> bool {
692        self.crisp_magnified_text.unwrap_or(false)
693    }
694
695    fn get_wire_smoothness(&self) -> f32 {
696        self.wire_smoothness.unwrap_or(1.0)
697    }
698}
699
700#[cfg(feature = "serde")]
701mod serde_frame_option {
702    use serde::{Deserialize, Deserializer, Serialize, Serializer};
703
704    #[derive(Serialize, Deserialize)]
705    pub struct Frame {
706        pub inner_margin: egui::Margin,
707        pub outer_margin: egui::Margin,
708        pub rounding: egui::CornerRadius,
709        pub shadow: egui::epaint::Shadow,
710        pub fill: egui::Color32,
711        pub stroke: egui::Stroke,
712    }
713
714    #[allow(clippy::ref_option)]
715    pub fn serialize<S>(frame: &Option<egui::Frame>, serializer: S) -> Result<S::Ok, S::Error>
716    where
717        S: Serializer,
718    {
719        match frame {
720            Some(frame) => Frame {
721                inner_margin: frame.inner_margin,
722                outer_margin: frame.outer_margin,
723                rounding: frame.corner_radius,
724                shadow: frame.shadow,
725                fill: frame.fill,
726                stroke: frame.stroke,
727            }
728            .serialize(serializer),
729            None => serializer.serialize_none(),
730        }
731    }
732
733    pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<egui::Frame>, D::Error>
734    where
735        D: Deserializer<'de>,
736    {
737        let frame_opt = Option::<Frame>::deserialize(deserializer)?;
738        Ok(frame_opt.map(|frame| egui::Frame {
739            inner_margin: frame.inner_margin,
740            outer_margin: frame.outer_margin,
741            corner_radius: frame.rounding,
742            shadow: frame.shadow,
743            fill: frame.fill,
744            stroke: frame.stroke,
745        }))
746    }
747}
748
749impl SnarlStyle {
750    /// Creates new [`SnarlStyle`] filled with default values.
751    #[must_use]
752    pub const fn new() -> Self {
753        SnarlStyle {
754            node_layout: None,
755            pin_size: None,
756            pin_fill: None,
757            pin_stroke: None,
758            pin_shape: None,
759            pin_placement: None,
760            wire_width: None,
761            wire_frame_size: None,
762            downscale_wire_frame: None,
763            upscale_wire_frame: None,
764            wire_style: None,
765            wire_layer: None,
766            header_drag_space: None,
767            collapsible: None,
768
769            bg_frame: None,
770            bg_pattern: None,
771            bg_pattern_stroke: None,
772
773            min_scale: None,
774            max_scale: None,
775            node_frame: None,
776            header_frame: None,
777            centering: None,
778            select_stoke: None,
779            select_fill: None,
780            select_rect_contained: None,
781            select_style: None,
782            crisp_magnified_text: None,
783            wire_smoothness: None,
784
785            _non_exhaustive: (),
786        }
787    }
788}
789
790impl Default for SnarlStyle {
791    #[inline]
792    fn default() -> Self {
793        Self::new()
794    }
795}
796
797struct DrawNodeResponse {
798    node_moved: Option<(NodeId, Vec2)>,
799    node_to_top: Option<NodeId>,
800    drag_released: bool,
801    pin_hovered: Option<AnyPin>,
802    final_rect: Rect,
803}
804
805struct DrawPinsResponse {
806    drag_released: bool,
807    pin_hovered: Option<AnyPin>,
808    final_rect: Rect,
809    new_heights: RowHeights,
810}
811
812struct DrawBodyResponse {
813    final_rect: Rect,
814}
815
816struct PinResponse {
817    pos: Pos2,
818    wire_color: Color32,
819    wire_style: WireStyle,
820}
821
822/// Widget to display [`Snarl`] graph in [`Ui`].
823#[derive(Clone, Copy, Debug)]
824pub struct SnarlWidget {
825    id_salt: Id,
826    id: Option<Id>,
827    style: SnarlStyle,
828    min_size: Vec2,
829    max_size: Vec2,
830}
831
832impl Default for SnarlWidget {
833    #[inline]
834    fn default() -> Self {
835        Self::new()
836    }
837}
838
839impl SnarlWidget {
840    /// Returns new [`SnarlWidget`] with default parameters.
841    #[inline]
842    #[must_use]
843    pub fn new() -> Self {
844        SnarlWidget {
845            id_salt: Id::new(":snarl:"),
846            id: None,
847            style: SnarlStyle::new(),
848            min_size: Vec2::ZERO,
849            max_size: Vec2::INFINITY,
850        }
851    }
852
853    /// Assign an explicit and globally unique [`Id`].
854    ///
855    /// Use this if you want to persist the state of the widget
856    /// when it changes position in the widget hierarchy.
857    ///
858    /// Prefer using [`SnarlWidget::id_salt`] otherwise.
859    #[inline]
860    #[must_use]
861    pub const fn id(mut self, id: Id) -> Self {
862        self.id = Some(id);
863        self
864    }
865
866    /// Assign a source for the unique [`Id`]
867    ///
868    /// It must be locally unique for the current [`Ui`] hierarchy position.
869    ///
870    /// Ignored if [`SnarlWidget::id`] was set.
871    #[inline]
872    #[must_use]
873    pub fn id_salt(mut self, id_salt: impl Hash) -> Self {
874        self.id_salt = Id::new(id_salt);
875        self
876    }
877
878    /// Set style parameters for the [`Snarl`] widget.
879    #[inline]
880    #[must_use]
881    pub const fn style(mut self, style: SnarlStyle) -> Self {
882        self.style = style;
883        self
884    }
885
886    /// Set minimum size of the [`Snarl`] widget.
887    #[inline]
888    #[must_use]
889    pub const fn min_size(mut self, min_size: Vec2) -> Self {
890        self.min_size = min_size;
891        self
892    }
893
894    /// Set maximum size of the [`Snarl`] widget.
895    #[inline]
896    #[must_use]
897    pub const fn max_size(mut self, max_size: Vec2) -> Self {
898        self.max_size = max_size;
899        self
900    }
901
902    #[inline]
903    fn get_id(&self, ui_id: Id) -> Id {
904        self.id.unwrap_or_else(|| ui_id.with(self.id_salt))
905    }
906
907    /// Render [`Snarl`] using given viewer and style into the [`Ui`].
908    #[inline]
909    pub fn show<T, V>(&self, snarl: &mut Snarl<T>, viewer: &mut V, ui: &mut Ui) -> egui::Response
910    where
911        V: SnarlViewer<T>,
912    {
913        let snarl_id = self.get_id(ui.id());
914
915        show_snarl(
916            snarl_id,
917            self.style,
918            self.min_size,
919            self.max_size,
920            snarl,
921            viewer,
922            ui,
923        )
924    }
925}
926
927#[inline(never)]
928fn show_snarl<T, V>(
929    snarl_id: Id,
930    mut style: SnarlStyle,
931    min_size: Vec2,
932    max_size: Vec2,
933    snarl: &mut Snarl<T>,
934    viewer: &mut V,
935    ui: &mut Ui,
936) -> egui::Response
937where
938    V: SnarlViewer<T>,
939{
940    #![allow(clippy::too_many_lines)]
941
942    let (mut latest_pos, modifiers) = ui.ctx().input(|i| (i.pointer.latest_pos(), i.modifiers));
943
944    let bg_frame = style.get_bg_frame(ui.style());
945
946    let outer_size_bounds = ui.available_size_before_wrap().max(min_size).min(max_size);
947
948    let outer_resp = ui.allocate_response(outer_size_bounds, Sense::hover());
949
950    ui.painter().add(bg_frame.paint(outer_resp.rect));
951
952    let mut content_rect = outer_resp.rect - bg_frame.total_margin();
953
954    // Make sure we don't shrink to the negative:
955    content_rect.max.x = content_rect.max.x.max(content_rect.min.x);
956    content_rect.max.y = content_rect.max.y.max(content_rect.min.y);
957
958    let snarl_layer_id = LayerId::new(ui.layer_id().order, snarl_id);
959
960    ui.ctx().set_sublayer(ui.layer_id(), snarl_layer_id);
961
962    let mut min_scale = style.get_min_scale();
963    let mut max_scale = style.get_max_scale();
964
965    let ui_rect = content_rect;
966
967    let mut snarl_state =
968        SnarlState::load(ui.ctx(), snarl_id, snarl, ui_rect, min_scale, max_scale);
969    let mut to_global = snarl_state.to_global();
970
971    let clip_rect = ui.clip_rect();
972
973    let mut ui = ui.new_child(
974        UiBuilder::new()
975            .ui_stack_info(UiStackInfo::new(UiKind::Frame).with_frame(bg_frame))
976            .layer_id(snarl_layer_id)
977            .max_rect(Rect::EVERYTHING)
978            .sense(Sense::click_and_drag()),
979    );
980
981    if style.get_crisp_magnified_text() {
982        style.scale(max_scale);
983        ui.style_mut().scale(max_scale);
984
985        min_scale /= max_scale;
986        max_scale = 1.0;
987    }
988
989    clamp_scale(&mut to_global, min_scale, max_scale, ui_rect);
990
991    let mut snarl_resp = ui.response();
992    Scene::new()
993        .zoom_range(min_scale..=max_scale)
994        .register_pan_and_zoom(&ui, &mut snarl_resp, &mut to_global);
995
996    if snarl_resp.changed() {
997        ui.ctx().request_repaint();
998    }
999
1000    // Inform viewer about current transform.
1001    viewer.current_transform(&mut to_global, snarl);
1002
1003    snarl_state.set_to_global(to_global);
1004
1005    let to_global = to_global;
1006    let from_global = to_global.inverse();
1007
1008    // Graph viewport
1009    let viewport = (from_global * ui_rect).round_ui();
1010    let viewport_clip = from_global * clip_rect;
1011
1012    ui.set_clip_rect(viewport.intersect(viewport_clip));
1013    ui.expand_to_include_rect(viewport);
1014
1015    // Set transform for snarl layer.
1016    ui.ctx().set_transform_layer(snarl_layer_id, to_global);
1017
1018    // Map latest pointer position to graph space.
1019    latest_pos = latest_pos.map(|pos| from_global * pos);
1020
1021    viewer.draw_background(
1022        style.bg_pattern.as_ref(),
1023        &viewport,
1024        &style,
1025        ui.style(),
1026        ui.painter(),
1027        snarl,
1028    );
1029
1030    let mut node_moved = None;
1031    let mut node_to_top = None;
1032
1033    // Process selection rect.
1034    let mut rect_selection_ended = None;
1035    if modifiers.shift || snarl_state.is_rect_selection() {
1036        let select_resp = ui.interact(snarl_resp.rect, snarl_id.with("select"), Sense::drag());
1037
1038        if select_resp.dragged_by(PointerButton::Primary)
1039            && let Some(pos) = select_resp.interact_pointer_pos()
1040        {
1041            if snarl_state.is_rect_selection() {
1042                snarl_state.update_rect_selection(pos);
1043            } else {
1044                snarl_state.start_rect_selection(pos);
1045            }
1046        }
1047
1048        if select_resp.drag_stopped_by(PointerButton::Primary) {
1049            if let Some(select_rect) = snarl_state.rect_selection() {
1050                rect_selection_ended = Some(select_rect);
1051            }
1052            snarl_state.stop_rect_selection();
1053        }
1054    }
1055
1056    let wire_frame_size = style.get_wire_frame_size(ui.style());
1057    let wire_width = style.get_wire_width(ui.style());
1058    let wire_threshold = style.get_wire_smoothness();
1059
1060    let wire_shape_idx = match style.get_wire_layer() {
1061        WireLayer::BehindNodes => Some(ui.painter().add(Shape::Noop)),
1062        WireLayer::AboveNodes => None,
1063    };
1064
1065    let mut input_info = HashMap::new();
1066    let mut output_info = HashMap::new();
1067
1068    let mut pin_hovered = None;
1069
1070    let draw_order = snarl_state.update_draw_order(snarl);
1071    let mut drag_released = false;
1072
1073    let mut nodes_bb = Rect::NOTHING;
1074    let mut node_rects = Vec::new();
1075
1076    for node_idx in draw_order {
1077        if !snarl.nodes.contains(node_idx.0) {
1078            continue;
1079        }
1080
1081        // show_node(node_idx);
1082        let response = draw_node(
1083            snarl,
1084            &mut ui,
1085            node_idx,
1086            viewer,
1087            &mut snarl_state,
1088            &style,
1089            snarl_id,
1090            &mut input_info,
1091            modifiers,
1092            &mut output_info,
1093        );
1094
1095        if let Some(response) = response {
1096            if let Some(v) = response.node_to_top {
1097                node_to_top = Some(v);
1098            }
1099            if let Some(v) = response.node_moved {
1100                node_moved = Some(v);
1101            }
1102            if let Some(v) = response.pin_hovered {
1103                pin_hovered = Some(v);
1104            }
1105            drag_released |= response.drag_released;
1106
1107            nodes_bb = nodes_bb.union(response.final_rect);
1108            if rect_selection_ended.is_some() {
1109                node_rects.push((node_idx, response.final_rect));
1110            }
1111        }
1112    }
1113
1114    let mut hovered_wire = None;
1115    let mut hovered_wire_disconnect = false;
1116    let mut wire_shapes = Vec::new();
1117
1118    // Draw and interact with wires
1119    for wire in snarl.wires.iter() {
1120        let Some(from_r) = output_info.get(&wire.out_pin) else {
1121            continue;
1122        };
1123        let Some(to_r) = input_info.get(&wire.in_pin) else {
1124            continue;
1125        };
1126
1127        if !snarl_state.has_new_wires() && snarl_resp.contains_pointer() && hovered_wire.is_none() {
1128            // Try to find hovered wire
1129            // If not dragging new wire
1130            // And not hovering over item above.
1131
1132            if let Some(latest_pos) = latest_pos {
1133                let wire_hit = hit_wire(
1134                    ui.ctx(),
1135                    WireId::Connected {
1136                        snarl_id,
1137                        out_pin: wire.out_pin,
1138                        in_pin: wire.in_pin,
1139                    },
1140                    wire_frame_size,
1141                    style.get_upscale_wire_frame(),
1142                    style.get_downscale_wire_frame(),
1143                    from_r.pos,
1144                    to_r.pos,
1145                    latest_pos,
1146                    wire_width.max(2.0),
1147                    pick_wire_style(from_r.wire_style, to_r.wire_style),
1148                );
1149
1150                if wire_hit {
1151                    hovered_wire = Some(wire);
1152
1153                    let wire_r =
1154                        ui.interact(snarl_resp.rect, ui.make_persistent_id(wire), Sense::click());
1155
1156                    //Remove hovered wire by second click
1157                    hovered_wire_disconnect |= wire_r.clicked_by(PointerButton::Secondary);
1158                }
1159            }
1160        }
1161
1162        let color = mix_colors(from_r.wire_color, to_r.wire_color);
1163
1164        let mut draw_width = wire_width;
1165        if hovered_wire == Some(wire) {
1166            draw_width *= 1.5;
1167        }
1168
1169        draw_wire(
1170            &ui,
1171            WireId::Connected {
1172                snarl_id,
1173                out_pin: wire.out_pin,
1174                in_pin: wire.in_pin,
1175            },
1176            &mut wire_shapes,
1177            wire_frame_size,
1178            style.get_upscale_wire_frame(),
1179            style.get_downscale_wire_frame(),
1180            from_r.pos,
1181            to_r.pos,
1182            Stroke::new(draw_width, color),
1183            wire_threshold,
1184            pick_wire_style(from_r.wire_style, to_r.wire_style),
1185        );
1186    }
1187
1188    // Remove hovered wire by second click
1189    if hovered_wire_disconnect && let Some(wire) = hovered_wire {
1190        let out_pin = OutPin::new(snarl, wire.out_pin);
1191        let in_pin = InPin::new(snarl, wire.in_pin);
1192        viewer.disconnect(&out_pin, &in_pin, snarl);
1193    }
1194
1195    if let Some(select_rect) = rect_selection_ended {
1196        let select_nodes = node_rects.into_iter().filter_map(|(id, rect)| {
1197            let select = if style.get_select_rect_contained() {
1198                select_rect.contains_rect(rect)
1199            } else {
1200                select_rect.intersects(rect)
1201            };
1202
1203            if select { Some(id) } else { None }
1204        });
1205
1206        if modifiers.command {
1207            snarl_state.deselect_many_nodes(select_nodes);
1208        } else {
1209            snarl_state.select_many_nodes(!modifiers.shift, select_nodes);
1210        }
1211    }
1212
1213    if let Some(select_rect) = snarl_state.rect_selection() {
1214        ui.painter().rect(
1215            select_rect,
1216            0.0,
1217            style.get_select_fill(ui.style()),
1218            style.get_select_stroke(ui.style()),
1219            StrokeKind::Inside,
1220        );
1221    }
1222
1223    // If right button is clicked while new wire is being dragged, cancel it.
1224    // This is to provide way to 'not open' the link graph node menu, but just
1225    // releasing the new wire to empty space.
1226    //
1227    // This uses `button_down` directly, instead of `clicked_by` to improve
1228    // responsiveness of the cancel action.
1229    if snarl_state.has_new_wires() && ui.input(|x| x.pointer.button_down(PointerButton::Secondary))
1230    {
1231        let _ = snarl_state.take_new_wires();
1232        snarl_resp.flags.remove(Flags::CLICKED);
1233    }
1234
1235    // Do centering unless no nodes are present.
1236    if style.get_centering() && snarl_resp.double_clicked() && nodes_bb.is_finite() {
1237        let nodes_bb = nodes_bb.expand(100.0);
1238        snarl_state.look_at(nodes_bb, ui_rect, min_scale, max_scale);
1239    }
1240
1241    if modifiers.command && snarl_resp.clicked_by(PointerButton::Primary) {
1242        snarl_state.deselect_all_nodes();
1243    }
1244
1245    // Wire end position will be overridden when link graph menu is opened.
1246    let mut wire_end_pos = latest_pos.unwrap_or_else(|| snarl_resp.rect.center());
1247
1248    if drag_released {
1249        let new_wires = snarl_state.take_new_wires();
1250        if new_wires.is_some() {
1251            ui.ctx().request_repaint();
1252        }
1253        match (new_wires, pin_hovered) {
1254            (Some(NewWires::In(in_pins)), Some(AnyPin::Out(out_pin))) => {
1255                for in_pin in in_pins {
1256                    viewer.connect(
1257                        &OutPin::new(snarl, out_pin),
1258                        &InPin::new(snarl, in_pin),
1259                        snarl,
1260                    );
1261                }
1262            }
1263            (Some(NewWires::Out(out_pins)), Some(AnyPin::In(in_pin))) => {
1264                for out_pin in out_pins {
1265                    viewer.connect(
1266                        &OutPin::new(snarl, out_pin),
1267                        &InPin::new(snarl, in_pin),
1268                        snarl,
1269                    );
1270                }
1271            }
1272            (Some(new_wires), None) if snarl_resp.hovered() => {
1273                let pins = match &new_wires {
1274                    NewWires::In(x) => AnyPins::In(x),
1275                    NewWires::Out(x) => AnyPins::Out(x),
1276                };
1277
1278                if viewer.has_dropped_wire_menu(pins, snarl) {
1279                    // A wire is dropped without connecting to a pin.
1280                    // Show context menu for the wire drop.
1281                    snarl_state.set_new_wires_menu(new_wires);
1282
1283                    // Force open context menu.
1284                    snarl_resp.flags.insert(Flags::LONG_TOUCHED);
1285                }
1286            }
1287            _ => {}
1288        }
1289    }
1290
1291    if let Some(interact_pos) = ui.ctx().input(|i| i.pointer.interact_pos()) {
1292        if let Some(new_wires) = snarl_state.take_new_wires_menu() {
1293            let pins = match &new_wires {
1294                NewWires::In(x) => AnyPins::In(x),
1295                NewWires::Out(x) => AnyPins::Out(x),
1296            };
1297
1298            if viewer.has_dropped_wire_menu(pins, snarl) {
1299                snarl_resp.context_menu(|ui| {
1300                    let pins = match &new_wires {
1301                        NewWires::In(x) => AnyPins::In(x),
1302                        NewWires::Out(x) => AnyPins::Out(x),
1303                    };
1304
1305                    let menu_pos = from_global * ui.cursor().min;
1306
1307                    // Override wire end position when the wire-drop context menu is opened.
1308                    wire_end_pos = menu_pos;
1309
1310                    // The context menu is opened as *link* graph menu.
1311                    viewer.show_dropped_wire_menu(menu_pos, ui, pins, snarl);
1312
1313                    // Even though menu could be closed in `show_dropped_wire_menu`,
1314                    // we need to revert the new wires here, because menu state is inaccessible.
1315                    // Next frame context menu won't be shown and wires will be removed.
1316                    snarl_state.set_new_wires_menu(new_wires);
1317                });
1318            }
1319        } else if viewer.has_graph_menu(interact_pos, snarl) {
1320            snarl_resp.context_menu(|ui| {
1321                let menu_pos = from_global * ui.cursor().min;
1322
1323                viewer.show_graph_menu(menu_pos, ui, snarl);
1324            });
1325        }
1326    }
1327
1328    match snarl_state.new_wires() {
1329        None => {}
1330        Some(NewWires::In(in_pins)) => {
1331            for &in_pin in in_pins {
1332                let from_pos = wire_end_pos;
1333                let to_r = &input_info[&in_pin];
1334
1335                draw_wire(
1336                    &ui,
1337                    WireId::NewInput { snarl_id, in_pin },
1338                    &mut wire_shapes,
1339                    wire_frame_size,
1340                    style.get_upscale_wire_frame(),
1341                    style.get_downscale_wire_frame(),
1342                    from_pos,
1343                    to_r.pos,
1344                    Stroke::new(wire_width, to_r.wire_color),
1345                    wire_threshold,
1346                    to_r.wire_style,
1347                );
1348            }
1349        }
1350        Some(NewWires::Out(out_pins)) => {
1351            for &out_pin in out_pins {
1352                let from_r = &output_info[&out_pin];
1353                let to_pos = wire_end_pos;
1354
1355                draw_wire(
1356                    &ui,
1357                    WireId::NewOutput { snarl_id, out_pin },
1358                    &mut wire_shapes,
1359                    wire_frame_size,
1360                    style.get_upscale_wire_frame(),
1361                    style.get_downscale_wire_frame(),
1362                    from_r.pos,
1363                    to_pos,
1364                    Stroke::new(wire_width, from_r.wire_color),
1365                    wire_threshold,
1366                    from_r.wire_style,
1367                );
1368            }
1369        }
1370    }
1371
1372    match wire_shape_idx {
1373        None => {
1374            ui.painter().add(Shape::Vec(wire_shapes));
1375        }
1376        Some(idx) => {
1377            ui.painter().set(idx, Shape::Vec(wire_shapes));
1378        }
1379    }
1380
1381    ui.advance_cursor_after_rect(Rect::from_min_size(snarl_resp.rect.min, Vec2::ZERO));
1382
1383    if let Some(node) = node_to_top
1384        && snarl.nodes.contains(node.0)
1385    {
1386        snarl_state.node_to_top(node);
1387    }
1388
1389    if let Some((node, delta)) = node_moved
1390        && snarl.nodes.contains(node.0)
1391    {
1392        ui.ctx().request_repaint();
1393        if snarl_state.selected_nodes().contains(&node) {
1394            for node in snarl_state.selected_nodes() {
1395                let node = &mut snarl.nodes[node.0];
1396                node.pos += delta;
1397            }
1398        } else {
1399            let node = &mut snarl.nodes[node.0];
1400            node.pos += delta;
1401        }
1402    }
1403
1404    snarl_state.store(snarl, ui.ctx());
1405
1406    snarl_resp
1407}
1408
1409#[allow(clippy::too_many_arguments)]
1410#[allow(clippy::too_many_lines)]
1411fn draw_inputs<T, V>(
1412    snarl: &mut Snarl<T>,
1413    viewer: &mut V,
1414    node: NodeId,
1415    inputs: &[InPin],
1416    pin_size: f32,
1417    style: &SnarlStyle,
1418    node_ui: &mut Ui,
1419    inputs_rect: Rect,
1420    payload_clip_rect: Rect,
1421    input_x: f32,
1422    min_pin_y_top: f32,
1423    min_pin_y_bottom: f32,
1424    input_spacing: Option<f32>,
1425    snarl_state: &mut SnarlState,
1426    modifiers: Modifiers,
1427    input_positions: &mut HashMap<InPinId, PinResponse>,
1428    heights: Heights,
1429) -> DrawPinsResponse
1430where
1431    V: SnarlViewer<T>,
1432{
1433    let mut drag_released = false;
1434    let mut pin_hovered = None;
1435
1436    // Input pins on the left.
1437    let mut inputs_ui = node_ui.new_child(
1438        UiBuilder::new()
1439            .max_rect(inputs_rect.round_ui())
1440            .layout(Layout::top_down(Align::Min))
1441            .id_salt("inputs"),
1442    );
1443
1444    let snarl_clip_rect = node_ui.clip_rect();
1445    inputs_ui.shrink_clip_rect(payload_clip_rect);
1446
1447    let pin_layout = Layout::left_to_right(Align::Min);
1448    let mut new_heights = SmallVec::with_capacity(inputs.len());
1449
1450    for in_pin in inputs {
1451        // Show input pin.
1452        let cursor = inputs_ui.cursor();
1453        let (height, height_outer) = heights.get(in_pin.id.input);
1454
1455        let margin = (height_outer - height) / 2.0;
1456        let outer_rect = cursor.with_max_y(cursor.top() + height_outer);
1457        let inner_rect = outer_rect.shrink2(vec2(0.0, margin));
1458
1459        let builder = UiBuilder::new().layout(pin_layout).max_rect(inner_rect);
1460
1461        inputs_ui.scope_builder(builder, |pin_ui| {
1462            if let Some(input_spacing) = input_spacing {
1463                let min = pin_ui.next_widget_position();
1464                pin_ui.advance_cursor_after_rect(Rect::from_min_size(
1465                    min,
1466                    vec2(input_spacing, pin_size),
1467                ));
1468            }
1469
1470            let y0 = pin_ui.max_rect().min.y;
1471            let y1 = pin_ui.max_rect().max.y;
1472
1473            // Show input content
1474            let snarl_pin = viewer.show_input(in_pin, pin_ui, snarl);
1475            if !snarl.nodes.contains(node.0) {
1476                // If removed
1477                return;
1478            }
1479
1480            let pin_rect = snarl_pin.pin_rect(
1481                input_x,
1482                min_pin_y_top.max(y0),
1483                min_pin_y_bottom.max(y1),
1484                pin_size,
1485            );
1486
1487            // Interact with pin shape.
1488            pin_ui.set_clip_rect(snarl_clip_rect);
1489
1490            let r = pin_ui.interact(pin_rect, pin_ui.next_auto_id(), Sense::click_and_drag());
1491
1492            pin_ui.skip_ahead_auto_ids(1);
1493
1494            if r.clicked_by(PointerButton::Secondary) {
1495                if snarl_state.has_new_wires() {
1496                    snarl_state.remove_new_wire_in(in_pin.id);
1497                } else {
1498                    viewer.drop_inputs(in_pin, snarl);
1499                    if !snarl.nodes.contains(node.0) {
1500                        // If removed
1501                        return;
1502                    }
1503                }
1504            }
1505            if r.drag_started_by(PointerButton::Primary) {
1506                if modifiers.command {
1507                    snarl_state.start_new_wires_out(&in_pin.remotes);
1508                    if !modifiers.shift {
1509                        snarl.drop_inputs(in_pin.id);
1510                        if !snarl.nodes.contains(node.0) {
1511                            // If removed
1512                            return;
1513                        }
1514                    }
1515                } else {
1516                    snarl_state.start_new_wire_in(in_pin.id);
1517                }
1518            }
1519
1520            if r.drag_stopped() {
1521                drag_released = true;
1522            }
1523
1524            let mut visual_pin_rect = r.rect;
1525
1526            if r.contains_pointer() {
1527                if snarl_state.has_new_wires_in() {
1528                    if modifiers.shift && !modifiers.command {
1529                        snarl_state.add_new_wire_in(in_pin.id);
1530                    }
1531                    if !modifiers.shift && modifiers.command {
1532                        snarl_state.remove_new_wire_in(in_pin.id);
1533                    }
1534                }
1535                pin_hovered = Some(AnyPin::In(in_pin.id));
1536                visual_pin_rect = visual_pin_rect.scale_from_center(1.2);
1537            }
1538
1539            let wire_info =
1540                snarl_pin.draw(style, pin_ui.style(), visual_pin_rect, pin_ui.painter());
1541
1542            input_positions.insert(
1543                in_pin.id,
1544                PinResponse {
1545                    pos: r.rect.center(),
1546                    wire_color: wire_info.color,
1547                    wire_style: wire_info.style,
1548                },
1549            );
1550
1551            new_heights.push(pin_ui.min_rect().height());
1552
1553            pin_ui.expand_to_include_y(outer_rect.bottom());
1554        });
1555    }
1556
1557    let final_rect = inputs_ui.min_rect();
1558    node_ui.expand_to_include_rect(final_rect.intersect(payload_clip_rect));
1559
1560    DrawPinsResponse {
1561        drag_released,
1562        pin_hovered,
1563        final_rect,
1564        new_heights,
1565    }
1566}
1567
1568#[allow(clippy::too_many_arguments)]
1569#[allow(clippy::too_many_lines)]
1570fn draw_outputs<T, V>(
1571    snarl: &mut Snarl<T>,
1572    viewer: &mut V,
1573    node: NodeId,
1574    outputs: &[OutPin],
1575    pin_size: f32,
1576    style: &SnarlStyle,
1577    node_ui: &mut Ui,
1578    outputs_rect: Rect,
1579    payload_clip_rect: Rect,
1580    output_x: f32,
1581    min_pin_y_top: f32,
1582    min_pin_y_bottom: f32,
1583    output_spacing: Option<f32>,
1584    snarl_state: &mut SnarlState,
1585    modifiers: Modifiers,
1586    output_positions: &mut HashMap<OutPinId, PinResponse>,
1587    heights: Heights,
1588) -> DrawPinsResponse
1589where
1590    V: SnarlViewer<T>,
1591{
1592    let mut drag_released = false;
1593    let mut pin_hovered = None;
1594
1595    let mut outputs_ui = node_ui.new_child(
1596        UiBuilder::new()
1597            .max_rect(outputs_rect.round_ui())
1598            .layout(Layout::top_down(Align::Max))
1599            .id_salt("outputs"),
1600    );
1601
1602    let snarl_clip_rect = node_ui.clip_rect();
1603    outputs_ui.shrink_clip_rect(payload_clip_rect);
1604
1605    let pin_layout = Layout::right_to_left(Align::Min);
1606    let mut new_heights = SmallVec::with_capacity(outputs.len());
1607
1608    // Output pins on the right.
1609    for out_pin in outputs {
1610        // Show output pin.
1611        let cursor = outputs_ui.cursor();
1612        let (height, height_outer) = heights.get(out_pin.id.output);
1613
1614        let margin = (height_outer - height) / 2.0;
1615        let outer_rect = cursor.with_max_y(cursor.top() + height_outer);
1616        let inner_rect = outer_rect.shrink2(vec2(0.0, margin));
1617
1618        let builder = UiBuilder::new().layout(pin_layout).max_rect(inner_rect);
1619
1620        outputs_ui.scope_builder(builder, |pin_ui| {
1621            // Allocate space for pin shape.
1622            if let Some(output_spacing) = output_spacing {
1623                let min = pin_ui.next_widget_position();
1624                pin_ui.advance_cursor_after_rect(Rect::from_min_size(
1625                    min,
1626                    vec2(output_spacing, pin_size),
1627                ));
1628            }
1629
1630            let y0 = pin_ui.max_rect().min.y;
1631            let y1 = pin_ui.max_rect().max.y;
1632
1633            // Show output content
1634            let snarl_pin = viewer.show_output(out_pin, pin_ui, snarl);
1635            if !snarl.nodes.contains(node.0) {
1636                // If removed
1637                return;
1638            }
1639
1640            let pin_rect = snarl_pin.pin_rect(
1641                output_x,
1642                min_pin_y_top.max(y0),
1643                min_pin_y_bottom.max(y1),
1644                pin_size,
1645            );
1646
1647            pin_ui.set_clip_rect(snarl_clip_rect);
1648
1649            let r = pin_ui.interact(pin_rect, pin_ui.next_auto_id(), Sense::click_and_drag());
1650
1651            pin_ui.skip_ahead_auto_ids(1);
1652
1653            if r.clicked_by(PointerButton::Secondary) {
1654                if snarl_state.has_new_wires() {
1655                    snarl_state.remove_new_wire_out(out_pin.id);
1656                } else {
1657                    viewer.drop_outputs(out_pin, snarl);
1658                    if !snarl.nodes.contains(node.0) {
1659                        // If removed
1660                        return;
1661                    }
1662                }
1663            }
1664            if r.drag_started_by(PointerButton::Primary) {
1665                if modifiers.command {
1666                    snarl_state.start_new_wires_in(&out_pin.remotes);
1667
1668                    if !modifiers.shift {
1669                        snarl.drop_outputs(out_pin.id);
1670                        if !snarl.nodes.contains(node.0) {
1671                            // If removed
1672                            return;
1673                        }
1674                    }
1675                } else {
1676                    snarl_state.start_new_wire_out(out_pin.id);
1677                }
1678            }
1679
1680            if r.drag_stopped() {
1681                drag_released = true;
1682            }
1683
1684            let mut visual_pin_rect = r.rect;
1685
1686            if r.contains_pointer() {
1687                if snarl_state.has_new_wires_out() {
1688                    if modifiers.shift && !modifiers.command {
1689                        snarl_state.add_new_wire_out(out_pin.id);
1690                    }
1691                    if !modifiers.shift && modifiers.command {
1692                        snarl_state.remove_new_wire_out(out_pin.id);
1693                    }
1694                }
1695                pin_hovered = Some(AnyPin::Out(out_pin.id));
1696                visual_pin_rect = visual_pin_rect.scale_from_center(1.2);
1697            }
1698
1699            let wire_info =
1700                snarl_pin.draw(style, pin_ui.style(), visual_pin_rect, pin_ui.painter());
1701
1702            output_positions.insert(
1703                out_pin.id,
1704                PinResponse {
1705                    pos: r.rect.center(),
1706                    wire_color: wire_info.color,
1707                    wire_style: wire_info.style,
1708                },
1709            );
1710
1711            new_heights.push(pin_ui.min_rect().height());
1712
1713            pin_ui.expand_to_include_y(outer_rect.bottom());
1714        });
1715    }
1716    let final_rect = outputs_ui.min_rect();
1717    node_ui.expand_to_include_rect(final_rect.intersect(payload_clip_rect));
1718
1719    DrawPinsResponse {
1720        drag_released,
1721        pin_hovered,
1722        final_rect,
1723        new_heights,
1724    }
1725}
1726
1727#[allow(clippy::too_many_arguments)]
1728fn draw_body<T, V>(
1729    snarl: &mut Snarl<T>,
1730    viewer: &mut V,
1731    node: NodeId,
1732    inputs: &[InPin],
1733    outputs: &[OutPin],
1734    ui: &mut Ui,
1735    body_rect: Rect,
1736    payload_clip_rect: Rect,
1737    _snarl_state: &SnarlState,
1738) -> DrawBodyResponse
1739where
1740    V: SnarlViewer<T>,
1741{
1742    let mut body_ui = ui.new_child(
1743        UiBuilder::new()
1744            .max_rect(body_rect.round_ui())
1745            .layout(Layout::left_to_right(Align::Min))
1746            .id_salt("body"),
1747    );
1748
1749    body_ui.shrink_clip_rect(payload_clip_rect);
1750
1751    viewer.show_body(node, inputs, outputs, &mut body_ui, snarl);
1752
1753    let final_rect = body_ui.min_rect();
1754    ui.expand_to_include_rect(final_rect.intersect(payload_clip_rect));
1755    // node_state.set_body_width(body_size.x);
1756
1757    DrawBodyResponse { final_rect }
1758}
1759
1760//First step for split big function to parts
1761/// Draw one node. Return Pins info
1762#[inline]
1763#[allow(clippy::too_many_lines)]
1764#[allow(clippy::too_many_arguments)]
1765fn draw_node<T, V>(
1766    snarl: &mut Snarl<T>,
1767    ui: &mut Ui,
1768    node: NodeId,
1769    viewer: &mut V,
1770    snarl_state: &mut SnarlState,
1771    style: &SnarlStyle,
1772    snarl_id: Id,
1773    input_positions: &mut HashMap<InPinId, PinResponse>,
1774    modifiers: Modifiers,
1775    output_positions: &mut HashMap<OutPinId, PinResponse>,
1776) -> Option<DrawNodeResponse>
1777where
1778    V: SnarlViewer<T>,
1779{
1780    let Node {
1781        pos,
1782        open,
1783        ref value,
1784    } = snarl.nodes[node.0];
1785
1786    // Collect pins
1787    let inputs_count = viewer.inputs(value);
1788    let outputs_count = viewer.outputs(value);
1789
1790    let inputs = (0..inputs_count)
1791        .map(|idx| InPin::new(snarl, InPinId { node, input: idx }))
1792        .collect::<Vec<_>>();
1793
1794    let outputs = (0..outputs_count)
1795        .map(|idx| OutPin::new(snarl, OutPinId { node, output: idx }))
1796        .collect::<Vec<_>>();
1797
1798    let node_pos = pos.round_ui();
1799
1800    // Generate persistent id for the node.
1801    let node_id = snarl_id.with(("snarl-node", node));
1802
1803    let openness = ui.ctx().animate_bool(node_id, open);
1804
1805    let mut node_state = NodeState::load(ui.ctx(), node_id, ui.spacing());
1806
1807    let node_rect = node_state.node_rect(node_pos, openness);
1808
1809    let mut node_to_top = None;
1810    let mut node_moved = None;
1811    let mut drag_released = false;
1812    let mut pin_hovered = None;
1813
1814    let node_frame = viewer.node_frame(
1815        style.get_node_frame(ui.style()),
1816        node,
1817        &inputs,
1818        &outputs,
1819        snarl,
1820    );
1821
1822    let header_frame = viewer.header_frame(
1823        style.get_header_frame(ui.style()),
1824        node,
1825        &inputs,
1826        &outputs,
1827        snarl,
1828    );
1829
1830    // Rect for node + frame margin.
1831    let node_frame_rect = node_rect + node_frame.total_margin();
1832
1833    if snarl_state.selected_nodes().contains(&node) {
1834        let select_style = style.get_select_style(ui.style());
1835
1836        let select_rect = node_frame_rect + select_style.margin;
1837
1838        ui.painter().rect(
1839            select_rect,
1840            select_style.rounding,
1841            select_style.fill,
1842            select_style.stroke,
1843            StrokeKind::Inside,
1844        );
1845    }
1846
1847    // Size of the pin.
1848    // Side of the square or diameter of the circle.
1849    let pin_size = style.get_pin_size(ui.style()).max(0.0);
1850
1851    let pin_placement = style.get_pin_placement();
1852
1853    let header_drag_space = style.get_header_drag_space(ui.style()).max(Vec2::ZERO);
1854
1855    // Interact with node frame.
1856    let r = ui.interact(
1857        node_frame_rect,
1858        node_id.with("frame"),
1859        Sense::click_and_drag(),
1860    );
1861
1862    if !modifiers.shift && !modifiers.command && r.dragged_by(PointerButton::Primary) {
1863        node_moved = Some((node, r.drag_delta()));
1864    }
1865
1866    if r.clicked_by(PointerButton::Primary) || r.dragged_by(PointerButton::Primary) {
1867        if modifiers.shift {
1868            snarl_state.select_one_node(modifiers.command, node);
1869        } else if modifiers.command {
1870            snarl_state.deselect_one_node(node);
1871        }
1872    }
1873
1874    if r.clicked() || r.dragged() {
1875        node_to_top = Some(node);
1876    }
1877
1878    if viewer.has_node_menu(&snarl.nodes[node.0].value) {
1879        r.context_menu(|ui| {
1880            viewer.show_node_menu(node, &inputs, &outputs, ui, snarl);
1881        });
1882    }
1883
1884    if !snarl.nodes.contains(node.0) {
1885        node_state.clear(ui.ctx());
1886        // If removed
1887        return None;
1888    }
1889
1890    if viewer.has_on_hover_popup(&snarl.nodes[node.0].value) {
1891        r.on_hover_ui_at_pointer(|ui| {
1892            viewer.show_on_hover_popup(node, &inputs, &outputs, ui, snarl);
1893        });
1894    }
1895
1896    if !snarl.nodes.contains(node.0) {
1897        node_state.clear(ui.ctx());
1898        // If removed
1899        return None;
1900    }
1901
1902    let node_ui = &mut ui.new_child(
1903        UiBuilder::new()
1904            .max_rect(node_frame_rect.round_ui())
1905            .layout(Layout::top_down(Align::Center))
1906            .id_salt(node_id),
1907    );
1908
1909    let mut new_pins_size = Vec2::ZERO;
1910
1911    let r = node_frame.show(node_ui, |ui| {
1912        if viewer.has_node_style(node, &inputs, &outputs, snarl) {
1913            viewer.apply_node_style(ui.style_mut(), node, &inputs, &outputs, snarl);
1914        }
1915
1916        // Input pins' center side by X axis.
1917        let input_x = match pin_placement {
1918            PinPlacement::Inside => pin_size.mul_add(
1919                0.5,
1920                node_frame_rect.left() + node_frame.inner_margin.leftf(),
1921            ),
1922            PinPlacement::Edge => node_frame_rect.left(),
1923            PinPlacement::Outside { margin } => {
1924                pin_size.mul_add(-0.5, node_frame_rect.left() - margin)
1925            }
1926        };
1927
1928        // Input pins' spacing required.
1929        let input_spacing = match pin_placement {
1930            PinPlacement::Inside => Some(pin_size),
1931            PinPlacement::Edge => Some(
1932                pin_size
1933                    .mul_add(0.5, -node_frame.inner_margin.leftf())
1934                    .max(0.0),
1935            ),
1936            PinPlacement::Outside { .. } => None,
1937        };
1938
1939        // Output pins' center side by X axis.
1940        let output_x = match pin_placement {
1941            PinPlacement::Inside => pin_size.mul_add(
1942                -0.5,
1943                node_frame_rect.right() - node_frame.inner_margin.rightf(),
1944            ),
1945            PinPlacement::Edge => node_frame_rect.right(),
1946            PinPlacement::Outside { margin } => {
1947                pin_size.mul_add(0.5, node_frame_rect.right() + margin)
1948            }
1949        };
1950
1951        // Output pins' spacing required.
1952        let output_spacing = match pin_placement {
1953            PinPlacement::Inside => Some(pin_size),
1954            PinPlacement::Edge => Some(
1955                pin_size
1956                    .mul_add(0.5, -node_frame.inner_margin.rightf())
1957                    .max(0.0),
1958            ),
1959            PinPlacement::Outside { .. } => None,
1960        };
1961
1962        // Input/output pin block
1963
1964        if (openness < 1.0 && open) || (openness > 0.0 && !open) {
1965            ui.ctx().request_repaint();
1966        }
1967
1968        // Pins are placed under the header and must not go outside of the header frame.
1969        let payload_rect = Rect::from_min_max(
1970            pos2(
1971                node_rect.min.x,
1972                node_rect.min.y
1973                    + node_state.header_height()
1974                    + header_frame.total_margin().bottom
1975                    + ui.spacing().item_spacing.y
1976                    - node_state.payload_offset(openness),
1977            ),
1978            node_rect.max,
1979        );
1980
1981        let node_layout =
1982            viewer.node_layout(style.get_node_layout(), node, &inputs, &outputs, snarl);
1983
1984        let payload_clip_rect =
1985            Rect::from_min_max(node_rect.min, pos2(node_rect.max.x, f32::INFINITY));
1986
1987        let pins_rect = match node_layout.kind {
1988            NodeLayoutKind::Coil => {
1989                // Show input pins.
1990                let r = draw_inputs(
1991                    snarl,
1992                    viewer,
1993                    node,
1994                    &inputs,
1995                    pin_size,
1996                    style,
1997                    ui,
1998                    payload_rect,
1999                    payload_clip_rect,
2000                    input_x,
2001                    node_rect.min.y,
2002                    node_rect.min.y + node_state.header_height(),
2003                    input_spacing,
2004                    snarl_state,
2005                    modifiers,
2006                    input_positions,
2007                    node_layout.input_heights(&node_state),
2008                );
2009
2010                let new_input_heights = r.new_heights;
2011
2012                drag_released |= r.drag_released;
2013
2014                if r.pin_hovered.is_some() {
2015                    pin_hovered = r.pin_hovered;
2016                }
2017
2018                let inputs_rect = r.final_rect;
2019                let inputs_size = inputs_rect.size();
2020
2021                if !snarl.nodes.contains(node.0) {
2022                    // If removed
2023                    return;
2024                }
2025
2026                // Show output pins.
2027
2028                let r = draw_outputs(
2029                    snarl,
2030                    viewer,
2031                    node,
2032                    &outputs,
2033                    pin_size,
2034                    style,
2035                    ui,
2036                    payload_rect,
2037                    payload_clip_rect,
2038                    output_x,
2039                    node_rect.min.y,
2040                    node_rect.min.y + node_state.header_height(),
2041                    output_spacing,
2042                    snarl_state,
2043                    modifiers,
2044                    output_positions,
2045                    node_layout.output_heights(&node_state),
2046                );
2047
2048                let new_output_heights = r.new_heights;
2049
2050                drag_released |= r.drag_released;
2051
2052                if r.pin_hovered.is_some() {
2053                    pin_hovered = r.pin_hovered;
2054                }
2055
2056                let outputs_rect = r.final_rect;
2057                let outputs_size = outputs_rect.size();
2058
2059                if !snarl.nodes.contains(node.0) {
2060                    // If removed
2061                    return;
2062                }
2063
2064                node_state.set_input_heights(new_input_heights);
2065                node_state.set_output_heights(new_output_heights);
2066
2067                new_pins_size = vec2(
2068                    inputs_size.x + outputs_size.x + ui.spacing().item_spacing.x,
2069                    f32::max(inputs_size.y, outputs_size.y),
2070                );
2071
2072                let mut pins_rect = inputs_rect.union(outputs_rect);
2073
2074                // Show body if there's one.
2075                if viewer.has_body(&snarl.nodes.get(node.0).unwrap().value) {
2076                    let body_rect = Rect::from_min_max(
2077                        pos2(
2078                            inputs_rect.right() + ui.spacing().item_spacing.x,
2079                            payload_rect.top(),
2080                        ),
2081                        pos2(
2082                            outputs_rect.left() - ui.spacing().item_spacing.x,
2083                            payload_rect.bottom(),
2084                        ),
2085                    );
2086
2087                    let r = draw_body(
2088                        snarl,
2089                        viewer,
2090                        node,
2091                        &inputs,
2092                        &outputs,
2093                        ui,
2094                        body_rect,
2095                        payload_clip_rect,
2096                        snarl_state,
2097                    );
2098
2099                    new_pins_size.x += r.final_rect.width() + ui.spacing().item_spacing.x;
2100                    new_pins_size.y = f32::max(new_pins_size.y, r.final_rect.height());
2101
2102                    pins_rect = pins_rect.union(body_rect);
2103
2104                    if !snarl.nodes.contains(node.0) {
2105                        // If removed
2106                        return;
2107                    }
2108                }
2109
2110                pins_rect
2111            }
2112            NodeLayoutKind::Sandwich => {
2113                // Show input pins.
2114
2115                let r = draw_inputs(
2116                    snarl,
2117                    viewer,
2118                    node,
2119                    &inputs,
2120                    pin_size,
2121                    style,
2122                    ui,
2123                    payload_rect,
2124                    payload_clip_rect,
2125                    input_x,
2126                    node_rect.min.y,
2127                    node_rect.min.y + node_state.header_height(),
2128                    input_spacing,
2129                    snarl_state,
2130                    modifiers,
2131                    input_positions,
2132                    node_layout.input_heights(&node_state),
2133                );
2134
2135                let new_input_heights = r.new_heights;
2136
2137                drag_released |= r.drag_released;
2138
2139                if r.pin_hovered.is_some() {
2140                    pin_hovered = r.pin_hovered;
2141                }
2142
2143                let inputs_rect = r.final_rect;
2144
2145                new_pins_size = inputs_rect.size();
2146
2147                let mut next_y = inputs_rect.bottom() + ui.spacing().item_spacing.y;
2148
2149                if !snarl.nodes.contains(node.0) {
2150                    // If removed
2151                    return;
2152                }
2153
2154                let mut pins_rect = inputs_rect;
2155
2156                // Show body if there's one.
2157                if viewer.has_body(&snarl.nodes.get(node.0).unwrap().value) {
2158                    let body_rect = payload_rect.intersect(Rect::everything_below(next_y));
2159
2160                    let r = draw_body(
2161                        snarl,
2162                        viewer,
2163                        node,
2164                        &inputs,
2165                        &outputs,
2166                        ui,
2167                        body_rect,
2168                        payload_clip_rect,
2169                        snarl_state,
2170                    );
2171
2172                    let body_rect = r.final_rect;
2173
2174                    new_pins_size.x = f32::max(new_pins_size.x, body_rect.width());
2175                    new_pins_size.y += body_rect.height() + ui.spacing().item_spacing.y;
2176
2177                    if !snarl.nodes.contains(node.0) {
2178                        // If removed
2179                        return;
2180                    }
2181
2182                    pins_rect = pins_rect.union(body_rect);
2183                    next_y = body_rect.bottom() + ui.spacing().item_spacing.y;
2184                }
2185
2186                // Show output pins.
2187
2188                let outputs_rect = payload_rect.intersect(Rect::everything_below(next_y));
2189
2190                let r = draw_outputs(
2191                    snarl,
2192                    viewer,
2193                    node,
2194                    &outputs,
2195                    pin_size,
2196                    style,
2197                    ui,
2198                    outputs_rect,
2199                    payload_clip_rect,
2200                    output_x,
2201                    node_rect.min.y,
2202                    node_rect.min.y + node_state.header_height(),
2203                    output_spacing,
2204                    snarl_state,
2205                    modifiers,
2206                    output_positions,
2207                    node_layout.output_heights(&node_state),
2208                );
2209
2210                let new_output_heights = r.new_heights;
2211
2212                drag_released |= r.drag_released;
2213
2214                if r.pin_hovered.is_some() {
2215                    pin_hovered = r.pin_hovered;
2216                }
2217
2218                let outputs_rect = r.final_rect;
2219
2220                if !snarl.nodes.contains(node.0) {
2221                    // If removed
2222                    return;
2223                }
2224
2225                node_state.set_input_heights(new_input_heights);
2226                node_state.set_output_heights(new_output_heights);
2227
2228                new_pins_size.x = f32::max(new_pins_size.x, outputs_rect.width());
2229                new_pins_size.y += outputs_rect.height() + ui.spacing().item_spacing.y;
2230
2231                pins_rect = pins_rect.union(outputs_rect);
2232
2233                pins_rect
2234            }
2235            NodeLayoutKind::FlippedSandwich => {
2236                // Show input pins.
2237
2238                let outputs_rect = payload_rect;
2239                let r = draw_outputs(
2240                    snarl,
2241                    viewer,
2242                    node,
2243                    &outputs,
2244                    pin_size,
2245                    style,
2246                    ui,
2247                    outputs_rect,
2248                    payload_clip_rect,
2249                    output_x,
2250                    node_rect.min.y,
2251                    node_rect.min.y + node_state.header_height(),
2252                    output_spacing,
2253                    snarl_state,
2254                    modifiers,
2255                    output_positions,
2256                    node_layout.output_heights(&node_state),
2257                );
2258
2259                let new_output_heights = r.new_heights;
2260
2261                drag_released |= r.drag_released;
2262
2263                if r.pin_hovered.is_some() {
2264                    pin_hovered = r.pin_hovered;
2265                }
2266
2267                let outputs_rect = r.final_rect;
2268
2269                new_pins_size = outputs_rect.size();
2270
2271                let mut next_y = outputs_rect.bottom() + ui.spacing().item_spacing.y;
2272
2273                if !snarl.nodes.contains(node.0) {
2274                    // If removed
2275                    return;
2276                }
2277
2278                let mut pins_rect = outputs_rect;
2279
2280                // Show body if there's one.
2281                if viewer.has_body(&snarl.nodes.get(node.0).unwrap().value) {
2282                    let body_rect = payload_rect.intersect(Rect::everything_below(next_y));
2283
2284                    let r = draw_body(
2285                        snarl,
2286                        viewer,
2287                        node,
2288                        &inputs,
2289                        &outputs,
2290                        ui,
2291                        body_rect,
2292                        payload_clip_rect,
2293                        snarl_state,
2294                    );
2295
2296                    let body_rect = r.final_rect;
2297
2298                    new_pins_size.x = f32::max(new_pins_size.x, body_rect.width());
2299                    new_pins_size.y += body_rect.height() + ui.spacing().item_spacing.y;
2300
2301                    if !snarl.nodes.contains(node.0) {
2302                        // If removed
2303                        return;
2304                    }
2305
2306                    pins_rect = pins_rect.union(body_rect);
2307                    next_y = body_rect.bottom() + ui.spacing().item_spacing.y;
2308                }
2309
2310                // Show output pins.
2311
2312                let inputs_rect = payload_rect.intersect(Rect::everything_below(next_y));
2313
2314                let r = draw_inputs(
2315                    snarl,
2316                    viewer,
2317                    node,
2318                    &inputs,
2319                    pin_size,
2320                    style,
2321                    ui,
2322                    inputs_rect,
2323                    payload_clip_rect,
2324                    input_x,
2325                    node_rect.min.y,
2326                    node_rect.min.y + node_state.header_height(),
2327                    input_spacing,
2328                    snarl_state,
2329                    modifiers,
2330                    input_positions,
2331                    node_layout.input_heights(&node_state),
2332                );
2333
2334                let new_input_heights = r.new_heights;
2335
2336                drag_released |= r.drag_released;
2337
2338                if r.pin_hovered.is_some() {
2339                    pin_hovered = r.pin_hovered;
2340                }
2341
2342                let inputs_rect = r.final_rect;
2343
2344                if !snarl.nodes.contains(node.0) {
2345                    // If removed
2346                    return;
2347                }
2348
2349                node_state.set_input_heights(new_input_heights);
2350                node_state.set_output_heights(new_output_heights);
2351
2352                new_pins_size.x = f32::max(new_pins_size.x, inputs_rect.width());
2353                new_pins_size.y += inputs_rect.height() + ui.spacing().item_spacing.y;
2354
2355                pins_rect = pins_rect.union(inputs_rect);
2356
2357                pins_rect
2358            }
2359        };
2360
2361        if viewer.has_footer(&snarl.nodes[node.0].value) {
2362            let footer_rect = Rect::from_min_max(
2363                pos2(
2364                    node_rect.left(),
2365                    pins_rect.bottom() + ui.spacing().item_spacing.y,
2366                ),
2367                pos2(node_rect.right(), node_rect.bottom()),
2368            );
2369
2370            let mut footer_ui = ui.new_child(
2371                UiBuilder::new()
2372                    .max_rect(footer_rect.round_ui())
2373                    .layout(Layout::left_to_right(Align::Min))
2374                    .id_salt("footer"),
2375            );
2376            footer_ui.shrink_clip_rect(payload_clip_rect);
2377
2378            viewer.show_footer(node, &inputs, &outputs, &mut footer_ui, snarl);
2379
2380            let final_rect = footer_ui.min_rect();
2381            ui.expand_to_include_rect(final_rect.intersect(payload_clip_rect));
2382            let footer_size = final_rect.size();
2383
2384            new_pins_size.x = f32::max(new_pins_size.x, footer_size.x);
2385            new_pins_size.y += footer_size.y + ui.spacing().item_spacing.y;
2386
2387            if !snarl.nodes.contains(node.0) {
2388                // If removed
2389                return;
2390            }
2391        }
2392
2393        // Render header frame.
2394        let mut header_rect = Rect::NAN;
2395
2396        let mut header_frame_rect = Rect::NAN; //node_rect + header_frame.total_margin();
2397
2398        // Show node's header
2399        let header_ui: &mut Ui = &mut ui.new_child(
2400            UiBuilder::new()
2401                .max_rect(node_rect.round_ui() + header_frame.total_margin())
2402                .layout(Layout::top_down(Align::Center))
2403                .id_salt("header"),
2404        );
2405
2406        header_frame.show(header_ui, |ui: &mut Ui| {
2407            ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
2408                if style.get_collapsible() {
2409                    let (_, r) = ui.allocate_exact_size(
2410                        vec2(ui.spacing().icon_width, ui.spacing().icon_width),
2411                        Sense::click(),
2412                    );
2413                    paint_default_icon(ui, openness, &r);
2414
2415                    if r.clicked_by(PointerButton::Primary) {
2416                        // Toggle node's openness.
2417                        snarl.open_node(node, !open);
2418                    }
2419                }
2420
2421                ui.allocate_exact_size(header_drag_space, Sense::hover());
2422
2423                viewer.show_header(node, &inputs, &outputs, ui, snarl);
2424
2425                header_rect = ui.min_rect();
2426            });
2427
2428            header_frame_rect = header_rect + header_frame.total_margin();
2429
2430            ui.advance_cursor_after_rect(Rect::from_min_max(
2431                header_rect.min,
2432                pos2(
2433                    f32::max(header_rect.max.x, node_rect.max.x),
2434                    header_rect.min.y,
2435                ),
2436            ));
2437        });
2438
2439        ui.expand_to_include_rect(header_rect);
2440        let header_size = header_rect.size();
2441        node_state.set_header_height(header_size.y);
2442
2443        node_state.set_size(vec2(
2444            f32::max(header_size.x, new_pins_size.x),
2445            header_size.y
2446                + header_frame.total_margin().bottom
2447                + ui.spacing().item_spacing.y
2448                + new_pins_size.y,
2449        ));
2450    });
2451
2452    if !snarl.nodes.contains(node.0) {
2453        ui.ctx().request_repaint();
2454        node_state.clear(ui.ctx());
2455        // If removed
2456        return None;
2457    }
2458
2459    viewer.final_node_rect(node, r.response.rect, ui, snarl);
2460
2461    node_state.store(ui.ctx());
2462    Some(DrawNodeResponse {
2463        node_moved,
2464        node_to_top,
2465        drag_released,
2466        pin_hovered,
2467        final_rect: r.response.rect,
2468    })
2469}
2470
2471const fn mix_colors(a: Color32, b: Color32) -> Color32 {
2472    #![allow(clippy::cast_possible_truncation)]
2473
2474    Color32::from_rgba_premultiplied(
2475        u8::midpoint(a.r(), b.r()),
2476        u8::midpoint(a.g(), b.g()),
2477        u8::midpoint(a.b(), b.b()),
2478        u8::midpoint(a.a(), b.a()),
2479    )
2480}
2481
2482// fn mix_colors(mut colors: impl Iterator<Item = Color32>) -> Option<Color32> {
2483//     let color = colors.next()?;
2484
2485//     let mut r = color.r() as u32;
2486//     let mut g = color.g() as u32;
2487//     let mut b = color.b() as u32;
2488//     let mut a = color.a() as u32;
2489//     let mut w = 1;
2490
2491//     for c in colors {
2492//         r += c.r() as u32;
2493//         g += c.g() as u32;
2494//         b += c.b() as u32;
2495//         a += c.a() as u32;
2496//         w += 1;
2497//     }
2498
2499//     Some(Color32::from_rgba_premultiplied(
2500//         (r / w) as u8,
2501//         (g / w) as u8,
2502//         (b / w) as u8,
2503//         (a / w) as u8,
2504//     ))
2505// }
2506
2507// fn mix_sizes(mut sizes: impl Iterator<Item = f32>) -> Option<f32> {
2508//     let mut size = sizes.next()?;
2509//     let mut w = 1;
2510
2511//     for s in sizes {
2512//         size += s;
2513//         w += 1;
2514//     }
2515
2516//     Some(size / w as f32)
2517// }
2518
2519// fn mix_strokes(mut strokes: impl Iterator<Item = Stroke>) -> Option<Stroke> {
2520//     let stoke = strokes.next()?;
2521
2522//     let mut width = stoke.width;
2523//     let mut r = stoke.color.r() as u32;
2524//     let mut g = stoke.color.g() as u32;
2525//     let mut b = stoke.color.b() as u32;
2526//     let mut a = stoke.color.a() as u32;
2527
2528//     let mut w = 1;
2529
2530//     for s in strokes {
2531//         width += s.width;
2532//         r += s.color.r() as u32;
2533//         g += s.color.g() as u32;
2534//         b += s.color.b() as u32;
2535//         a += s.color.a() as u32;
2536//         w += 1;
2537//     }
2538
2539//     Some(Stroke {
2540//         width: width / w as f32,
2541//         color: Color32::from_rgba_premultiplied(
2542//             (r / w) as u8,
2543//             (g / w) as u8,
2544//             (b / w) as u8,
2545//             (a / w) as u8,
2546//         ),
2547//     })
2548// }
2549
2550impl<T> Snarl<T> {
2551    /// Render [`Snarl`] using given viewer and style into the [`Ui`].
2552    #[inline]
2553    pub fn show<V>(&mut self, viewer: &mut V, style: &SnarlStyle, id_salt: impl Hash, ui: &mut Ui)
2554    where
2555        V: SnarlViewer<T>,
2556    {
2557        show_snarl(
2558            ui.make_persistent_id(id_salt),
2559            *style,
2560            Vec2::ZERO,
2561            Vec2::INFINITY,
2562            self,
2563            viewer,
2564            ui,
2565        );
2566    }
2567}
2568
2569#[inline]
2570fn clamp_scale(to_global: &mut TSTransform, min_scale: f32, max_scale: f32, ui_rect: Rect) {
2571    if to_global.scaling >= min_scale && to_global.scaling <= max_scale {
2572        return;
2573    }
2574
2575    let new_scaling = to_global.scaling.clamp(min_scale, max_scale);
2576    *to_global = scale_transform_around(to_global, new_scaling, ui_rect.center());
2577}
2578
2579#[inline]
2580#[must_use]
2581fn transform_matching_points(from: Pos2, to: Pos2, scaling: f32) -> TSTransform {
2582    TSTransform {
2583        scaling,
2584        translation: to.to_vec2() - from.to_vec2() * scaling,
2585    }
2586}
2587
2588#[inline]
2589#[must_use]
2590fn scale_transform_around(transform: &TSTransform, scaling: f32, point: Pos2) -> TSTransform {
2591    let from = (point - transform.translation) / transform.scaling;
2592    transform_matching_points(from, point, scaling)
2593}
2594
2595#[test]
2596const fn snarl_style_is_send_sync() {
2597    const fn is_send_sync<T: Send + Sync>() {}
2598    is_send_sync::<SnarlStyle>();
2599}