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