egui_treeize/
ui.rs

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