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 nodes_bb = Rect::NOTHING;
858  let mut node_rects = Vec::new();
859
860  for node_idx in draw_order {
861    if !treeize.nodes.contains(node_idx.0) {
862      continue;
863    }
864
865    // show_node(node_idx);
866    let response = draw_node(
867      treeize,
868      &mut ui,
869      node_idx,
870      viewer,
871      &mut treeize_state,
872      &style,
873      treeize_id,
874      &mut input_info,
875      modifiers,
876      &mut output_info,
877    );
878
879    if let Some(response) = response {
880      if let Some(v) = response.node_to_top {
881        node_to_top = Some(v);
882      }
883      if let Some(v) = response.node_moved {
884        node_moved = Some(v);
885      }
886      if let Some(v) = response.pin_hovered {
887        pin_hovered = Some(v);
888      }
889      drag_released |= response.drag_released;
890
891      nodes_bb = nodes_bb.union(response.final_rect);
892      if rect_selection_ended.is_some() {
893        node_rects.push((node_idx, response.final_rect));
894      }
895    }
896  }
897
898  let mut hovered_wire = None;
899  let mut wire_shapes = Vec::new();
900
901  // Draw and interact with wires
902  for wire in treeize.wires.iter() {
903    let Some(from_r) = output_info.get(&wire.out_pin) else {
904      continue;
905    };
906    let Some(to_r) = input_info.get(&wire.in_pin) else {
907      continue;
908    };
909
910    if !treeize_state.has_new_wires() && treeize_resp.contains_pointer() && hovered_wire.is_none() {
911      // Try to find hovered wire
912      // If not dragging new wire
913      // And not hovering over item above.
914
915      if let Some(latest_pos) = latest_pos {
916        let wire_hit = hit_wire(
917          ui.ctx(),
918          WireId::Connected { treeize_id, out_pin: wire.out_pin, in_pin: wire.in_pin },
919          wire_frame_size,
920          style.get_upscale_wire_frame(),
921          style.get_downscale_wire_frame(),
922          from_r.pos,
923          to_r.pos,
924          latest_pos,
925          wire_width.max(2.0),
926          pick_wire_style(from_r.wire_style, to_r.wire_style),
927        );
928
929        if wire_hit {
930          hovered_wire = Some(wire);
931        }
932      }
933    }
934
935    let color = mix_colors(from_r.wire_color, to_r.wire_color);
936
937    let mut draw_width = wire_width;
938    if hovered_wire == Some(wire) {
939      draw_width *= 1.5;
940    }
941
942    draw_wire(
943      &ui,
944      WireId::Connected { treeize_id, out_pin: wire.out_pin, in_pin: wire.in_pin },
945      &mut wire_shapes,
946      wire_frame_size,
947      style.get_upscale_wire_frame(),
948      style.get_downscale_wire_frame(),
949      from_r.pos,
950      to_r.pos,
951      Stroke::new(draw_width, color),
952      wire_threshold,
953      pick_wire_style(from_r.wire_style, to_r.wire_style),
954    );
955  }
956
957  if let Some(select_rect) = rect_selection_ended {
958    let select_nodes = node_rects.into_iter().filter_map(|(id, rect)| {
959      let select = if style.get_select_rect_contained() {
960        select_rect.contains_rect(rect)
961      } else {
962        select_rect.intersects(rect)
963      };
964
965      if select { Some(id) } else { None }
966    });
967
968    if modifiers.command {
969      treeize_state.deselect_many_nodes(select_nodes);
970    } else {
971      treeize_state.select_many_nodes(!modifiers.shift, select_nodes);
972    }
973  }
974
975  if let Some(select_rect) = treeize_state.rect_selection() {
976    ui.painter().rect(
977      select_rect,
978      0.0,
979      style.get_select_fill(ui.style()),
980      style.get_select_stroke(ui.style()),
981      StrokeKind::Inside,
982    );
983  }
984
985  // If right button is clicked while new wire is being dragged, cancel it.
986  // This is to provide way to 'not open' the link graph node menu, but just
987  // releasing the new wire to empty space.
988  //
989  // This uses `button_down` directly, instead of `clicked_by` to improve
990  // responsiveness of the cancel action.
991  if treeize_state.has_new_wires() && ui.input(|x| x.pointer.button_down(PointerButton::Secondary))
992  {
993    let _ = treeize_state.take_new_wires();
994    treeize_resp.flags.remove(Flags::CLICKED);
995  }
996
997  // Do centering unless no nodes are present.
998  if let Some(center_signal) = center_signal
999    && center_signal
1000    && nodes_bb.is_finite()
1001  {
1002    let nodes_bb = nodes_bb.expand(100.0);
1003    treeize_state.look_at(nodes_bb, ui_rect, min_scale, max_scale);
1004  }
1005
1006  if modifiers.command && treeize_resp.clicked_by(PointerButton::Primary) {
1007    treeize_state.deselect_all_nodes();
1008  }
1009
1010  // Wire end position will be overridden when link graph menu is opened.
1011  let mut wire_end_pos = latest_pos.unwrap_or_else(|| treeize_resp.rect.center());
1012
1013  if drag_released {
1014    let new_wires = treeize_state.take_new_wires();
1015    if new_wires.is_some() {
1016      ui.ctx().request_repaint();
1017    }
1018    match (new_wires, pin_hovered) {
1019      (Some(NewWires::In(in_pins)), Some(AnyPin::Out(out_pin))) => {
1020        for in_pin in in_pins {
1021          viewer.connect(&OutPin::new(treeize, out_pin), &InPin::new(treeize, in_pin), treeize);
1022        }
1023      }
1024      (Some(NewWires::Out(out_pins)), Some(AnyPin::In(in_pin))) => {
1025        for out_pin in out_pins {
1026          viewer.connect(&OutPin::new(treeize, out_pin), &InPin::new(treeize, in_pin), treeize);
1027        }
1028      }
1029      (Some(new_wires), None) if treeize_resp.hovered() => {
1030        let pins = match &new_wires {
1031          NewWires::In(x) => AnyPins::In(x),
1032          NewWires::Out(x) => AnyPins::Out(x),
1033        };
1034
1035        if viewer.has_dropped_wire_menu(pins, treeize) {
1036          // A wire is dropped without connecting to a pin.
1037          // Show context menu for the wire drop.
1038          treeize_state.set_new_wires_menu(new_wires);
1039
1040          // Force open context menu.
1041          treeize_resp.flags.insert(Flags::LONG_TOUCHED);
1042        }
1043      }
1044      _ => {}
1045    }
1046  }
1047
1048  if let Some(interact_pos) = ui.ctx().input(|i| i.pointer.interact_pos()) {
1049    if let Some(new_wires) = treeize_state.take_new_wires_menu() {
1050      let pins = match &new_wires {
1051        NewWires::In(x) => AnyPins::In(x),
1052        NewWires::Out(x) => AnyPins::Out(x),
1053      };
1054
1055      if viewer.has_dropped_wire_menu(pins, treeize) {
1056        treeize_resp.context_menu(|ui| {
1057          let pins = match &new_wires {
1058            NewWires::In(x) => AnyPins::In(x),
1059            NewWires::Out(x) => AnyPins::Out(x),
1060          };
1061
1062          let menu_pos = from_global * ui.cursor().min;
1063
1064          // Override wire end position when the wire-drop context menu is opened.
1065          wire_end_pos = menu_pos;
1066
1067          // The context menu is opened as *link* graph menu.
1068          viewer.show_dropped_wire_menu(menu_pos, ui, pins, treeize);
1069
1070          // Even though menu could be closed in `show_dropped_wire_menu`,
1071          // we need to revert the new wires here, because menu state is inaccessible.
1072          // Next frame context menu won't be shown and wires will be removed.
1073          treeize_state.set_new_wires_menu(new_wires);
1074        });
1075      }
1076    } else if viewer.has_graph_menu(interact_pos, treeize) {
1077      treeize_resp.context_menu(|ui| {
1078        let menu_pos = from_global * ui.cursor().min;
1079
1080        viewer.show_graph_menu(menu_pos, ui, treeize);
1081      });
1082    }
1083  }
1084
1085  match treeize_state.new_wires() {
1086    None => {}
1087    Some(NewWires::In(in_pins)) => {
1088      for &in_pin in in_pins {
1089        let from_pos = wire_end_pos;
1090        let to_r = &input_info[&in_pin];
1091
1092        draw_wire(
1093          &ui,
1094          WireId::NewInput { treeize_id, in_pin },
1095          &mut wire_shapes,
1096          wire_frame_size,
1097          style.get_upscale_wire_frame(),
1098          style.get_downscale_wire_frame(),
1099          from_pos,
1100          to_r.pos,
1101          Stroke::new(wire_width, to_r.wire_color),
1102          wire_threshold,
1103          to_r.wire_style,
1104        );
1105      }
1106    }
1107    Some(NewWires::Out(out_pins)) => {
1108      for &out_pin in out_pins {
1109        let from_r = &output_info[&out_pin];
1110        let to_pos = wire_end_pos;
1111
1112        draw_wire(
1113          &ui,
1114          WireId::NewOutput { treeize_id, out_pin },
1115          &mut wire_shapes,
1116          wire_frame_size,
1117          style.get_upscale_wire_frame(),
1118          style.get_downscale_wire_frame(),
1119          from_r.pos,
1120          to_pos,
1121          Stroke::new(wire_width, from_r.wire_color),
1122          wire_threshold,
1123          from_r.wire_style,
1124        );
1125      }
1126    }
1127  }
1128
1129  match wire_shape_idx {
1130    None => {
1131      ui.painter().add(Shape::Vec(wire_shapes));
1132    }
1133    Some(idx) => {
1134      ui.painter().set(idx, Shape::Vec(wire_shapes));
1135    }
1136  }
1137
1138  ui.advance_cursor_after_rect(Rect::from_min_size(treeize_resp.rect.min, Vec2::ZERO));
1139
1140  if let Some(node) = node_to_top
1141    && treeize.nodes.contains(node.0)
1142  {
1143    treeize_state.node_to_top(node);
1144  }
1145
1146  if let Some((node, delta)) = node_moved
1147    && treeize.nodes.contains(node.0)
1148  {
1149    ui.ctx().request_repaint();
1150    if treeize_state.selected_nodes().contains(&node) {
1151      for node in treeize_state.selected_nodes() {
1152        let node = &mut treeize.nodes[node.0];
1153        node.pos += delta;
1154      }
1155    } else {
1156      let node = &mut treeize.nodes[node.0];
1157      node.pos += delta;
1158    }
1159  }
1160
1161  treeize_state.store(treeize, ui.ctx());
1162
1163  if let Some(layout_signal) = layout_signal
1164    && layout_signal.layout_signal
1165  {
1166    layout_with_viewer(treeize, viewer, layout_signal.layout_config, ui.ctx(), treeize_id);
1167  }
1168
1169  treeize_resp
1170}
1171
1172#[allow(clippy::too_many_arguments)]
1173#[allow(clippy::too_many_lines)]
1174fn draw_inputs<T, V>(
1175  treeize: &mut Treeize<T>,
1176  viewer: &mut V,
1177  node: NodeId,
1178  inputs: &[InPin],
1179  pin_size: f32,
1180  style: &TreeizeStyle,
1181  node_ui: &mut Ui,
1182  inputs_rect: Rect,
1183  payload_clip_rect: Rect,
1184  input_y: f32,
1185  min_pin_x_left: f32,
1186  min_pin_x_right: f32,
1187  treeize_state: &mut TreeizeState,
1188  modifiers: Modifiers,
1189  input_positions: &mut HashMap<InPinId, PinResponse>,
1190  heights: Heights,
1191) -> DrawPinsResponse
1192where
1193  V: TreeizeViewer<T>,
1194{
1195  let mut drag_released = false;
1196  let mut pin_hovered = None;
1197
1198  // Input pins on the left.
1199  let mut inputs_ui = node_ui.new_child(
1200    UiBuilder::new()
1201      .max_rect(inputs_rect.round_ui())
1202      .layout(Layout::top_down(Align::Min))
1203      .id_salt("inputs"),
1204  );
1205
1206  let treeize_clip_rect = node_ui.clip_rect();
1207  inputs_ui.shrink_clip_rect(payload_clip_rect);
1208
1209  let pin_layout = Layout::left_to_right(Align::Min);
1210
1211  for in_pin in inputs {
1212    // Show input pin.
1213    let cursor = inputs_ui.cursor();
1214    let (height, height_outer) = heights.get(in_pin.id.input);
1215
1216    let margin = (height_outer - height) / 2.0;
1217    let outer_rect = cursor.with_max_y(cursor.top() + height_outer);
1218    let inner_rect = outer_rect.shrink2(vec2(0.0, margin));
1219
1220    let builder = UiBuilder::new().layout(pin_layout).max_rect(inner_rect);
1221
1222    inputs_ui.scope_builder(builder, |pin_ui| {
1223      let x0 = pin_ui.max_rect().min.x;
1224      let x1 = pin_ui.max_rect().max.x;
1225
1226      // Show input content
1227      let treeize_pin = viewer.show_input(in_pin, pin_ui, treeize);
1228      if !treeize.nodes.contains(node.0) {
1229        // If removed
1230        return;
1231      }
1232
1233      let pin_rect =
1234        treeize_pin.pin_rect(min_pin_x_left.max(x0), min_pin_x_right.max(x1), input_y, pin_size);
1235
1236      // Interact with pin shape.
1237      pin_ui.set_clip_rect(treeize_clip_rect);
1238
1239      let r = pin_ui.interact(pin_rect, pin_ui.next_auto_id(), Sense::click_and_drag());
1240
1241      pin_ui.skip_ahead_auto_ids(1);
1242
1243      if r.clicked_by(PointerButton::Secondary) {
1244        if treeize_state.has_new_wires() {
1245          treeize_state.remove_new_wire_in(in_pin.id);
1246        } else {
1247          viewer.drop_inputs(in_pin, treeize);
1248          if !treeize.nodes.contains(node.0) {
1249            // If removed
1250            return;
1251          }
1252        }
1253      }
1254      if r.drag_started_by(PointerButton::Primary) {
1255        if modifiers.command {
1256          treeize_state.start_new_wires_out(&in_pin.remotes);
1257          if !modifiers.shift {
1258            treeize.drop_inputs(in_pin.id);
1259            if !treeize.nodes.contains(node.0) {
1260              // If removed
1261              return;
1262            }
1263          }
1264        } else {
1265          treeize_state.start_new_wire_in(in_pin.id);
1266        }
1267      }
1268
1269      if r.drag_stopped() {
1270        drag_released = true;
1271      }
1272
1273      let mut visual_pin_rect = r.rect;
1274
1275      if r.contains_pointer() {
1276        if treeize_state.has_new_wires_in() {
1277          if modifiers.shift && !modifiers.command {
1278            treeize_state.add_new_wire_in(in_pin.id);
1279          }
1280          if !modifiers.shift && modifiers.command {
1281            treeize_state.remove_new_wire_in(in_pin.id);
1282          }
1283        }
1284        pin_hovered = Some(AnyPin::In(in_pin.id));
1285        visual_pin_rect = visual_pin_rect.scale_from_center(1.2);
1286      }
1287
1288      let wire_info = treeize_pin.draw(style, pin_ui.style(), visual_pin_rect, pin_ui.painter());
1289
1290      input_positions.insert(
1291        in_pin.id,
1292        PinResponse {
1293          pos: r.rect.center(),
1294          wire_color: wire_info.color,
1295          wire_style: wire_info.style,
1296        },
1297      );
1298
1299      pin_ui.expand_to_include_y(outer_rect.bottom());
1300    });
1301  }
1302
1303  let final_rect = inputs_ui.min_rect();
1304  node_ui.expand_to_include_rect(final_rect.intersect(payload_clip_rect));
1305
1306  DrawPinsResponse { drag_released, pin_hovered }
1307}
1308
1309#[allow(clippy::too_many_arguments)]
1310#[allow(clippy::too_many_lines)]
1311fn draw_outputs<T, V>(
1312  treeize: &mut Treeize<T>,
1313  viewer: &mut V,
1314  node: NodeId,
1315  outputs: &[OutPin],
1316  pin_size: f32,
1317  style: &TreeizeStyle,
1318  node_ui: &mut Ui,
1319  outputs_rect: Rect,
1320  payload_clip_rect: Rect,
1321  output_y: f32,
1322  min_pin_y_left: f32,
1323  min_pin_y_right: f32,
1324  treeize_state: &mut TreeizeState,
1325  modifiers: Modifiers,
1326  output_positions: &mut HashMap<OutPinId, PinResponse>,
1327  heights: Heights,
1328) -> DrawPinsResponse
1329where
1330  V: TreeizeViewer<T>,
1331{
1332  let mut drag_released = false;
1333  let mut pin_hovered = None;
1334
1335  let mut outputs_ui = node_ui.new_child(
1336    UiBuilder::new()
1337      .max_rect(outputs_rect.round_ui())
1338      .layout(Layout::top_down(Align::Max))
1339      .id_salt("outputs"),
1340  );
1341
1342  let treeize_clip_rect = node_ui.clip_rect();
1343  outputs_ui.shrink_clip_rect(payload_clip_rect);
1344
1345  let pin_layout = Layout::right_to_left(Align::Min);
1346
1347  // Output pins on the right.
1348  for out_pin in outputs {
1349    // Show output pin.
1350    let cursor = outputs_ui.cursor();
1351    let (height, height_outer) = heights.get(out_pin.id.output);
1352
1353    let margin = (height_outer - height) / 2.0;
1354    let outer_rect = cursor.with_max_y(cursor.top() + height_outer);
1355    let inner_rect = outer_rect.shrink2(vec2(0.0, margin));
1356
1357    let builder = UiBuilder::new().layout(pin_layout).max_rect(inner_rect);
1358
1359    outputs_ui.scope_builder(builder, |pin_ui| {
1360      let x0 = pin_ui.max_rect().min.x;
1361      let x1 = pin_ui.max_rect().max.x;
1362
1363      // Show output content
1364      let treeize_pin = viewer.show_output(out_pin, pin_ui, treeize);
1365      if !treeize.nodes.contains(node.0) {
1366        // If removed
1367        return;
1368      }
1369
1370      let pin_rect =
1371        treeize_pin.pin_rect(min_pin_y_left.max(x0), min_pin_y_right.max(x1), output_y, pin_size);
1372
1373      pin_ui.set_clip_rect(treeize_clip_rect);
1374
1375      let r = pin_ui.interact(pin_rect, pin_ui.next_auto_id(), Sense::click_and_drag());
1376
1377      pin_ui.skip_ahead_auto_ids(1);
1378
1379      if r.clicked_by(PointerButton::Secondary) {
1380        if treeize_state.has_new_wires() {
1381          treeize_state.remove_new_wire_out(out_pin.id);
1382        } else {
1383          viewer.drop_outputs(out_pin, treeize);
1384          if !treeize.nodes.contains(node.0) {
1385            // If removed
1386            return;
1387          }
1388        }
1389      }
1390      if r.drag_started_by(PointerButton::Primary) {
1391        if modifiers.command {
1392          treeize_state.start_new_wires_in(&out_pin.remotes);
1393
1394          if !modifiers.shift {
1395            treeize.drop_outputs(out_pin.id);
1396            if !treeize.nodes.contains(node.0) {
1397              // If removed
1398              return;
1399            }
1400          }
1401        } else {
1402          treeize_state.start_new_wire_out(out_pin.id);
1403        }
1404      }
1405
1406      if r.drag_stopped() {
1407        drag_released = true;
1408      }
1409
1410      let mut visual_pin_rect = r.rect;
1411
1412      if r.contains_pointer() {
1413        if treeize_state.has_new_wires_out() {
1414          if modifiers.shift && !modifiers.command {
1415            treeize_state.add_new_wire_out(out_pin.id);
1416          }
1417          if !modifiers.shift && modifiers.command {
1418            treeize_state.remove_new_wire_out(out_pin.id);
1419          }
1420        }
1421        pin_hovered = Some(AnyPin::Out(out_pin.id));
1422        visual_pin_rect = visual_pin_rect.scale_from_center(1.2);
1423      }
1424
1425      let wire_info = treeize_pin.draw(style, pin_ui.style(), visual_pin_rect, pin_ui.painter());
1426
1427      output_positions.insert(
1428        out_pin.id,
1429        PinResponse {
1430          pos: r.rect.center(),
1431          wire_color: wire_info.color,
1432          wire_style: wire_info.style,
1433        },
1434      );
1435
1436      pin_ui.expand_to_include_y(outer_rect.bottom());
1437    });
1438  }
1439  let final_rect = outputs_ui.min_rect();
1440  node_ui.expand_to_include_rect(final_rect.intersect(payload_clip_rect));
1441
1442  DrawPinsResponse { drag_released, pin_hovered }
1443}
1444
1445#[allow(clippy::too_many_arguments)]
1446fn draw_body<T, V>(
1447  treeize: &mut Treeize<T>,
1448  viewer: &mut V,
1449  node: NodeId,
1450  ui: &mut Ui,
1451  body_rect: Rect,
1452  payload_clip_rect: Rect,
1453  _treeize_state: &TreeizeState,
1454) -> DrawBodyResponse
1455where
1456  V: TreeizeViewer<T>,
1457{
1458  let mut body_ui = ui.new_child(
1459    UiBuilder::new()
1460      .max_rect(body_rect.round_ui())
1461      .layout(Layout::left_to_right(Align::Min))
1462      .id_salt("body"),
1463  );
1464
1465  body_ui.shrink_clip_rect(payload_clip_rect);
1466
1467  viewer.show_body(node, &mut body_ui, treeize);
1468
1469  let final_rect = body_ui.min_rect();
1470  ui.expand_to_include_rect(final_rect.intersect(payload_clip_rect));
1471  // node_state.set_body_width(body_size.x);
1472
1473  DrawBodyResponse { final_rect }
1474}
1475
1476//First step for split big function to parts
1477/// Draw one node. Return Pins info
1478#[inline]
1479#[allow(clippy::too_many_lines)]
1480#[allow(clippy::too_many_arguments)]
1481fn draw_node<T, V>(
1482  treeize: &mut Treeize<T>,
1483  ui: &mut Ui,
1484  node: NodeId,
1485  viewer: &mut V,
1486  treeize_state: &mut TreeizeState,
1487  style: &TreeizeStyle,
1488  treeize_id: Id,
1489  input_positions: &mut HashMap<InPinId, PinResponse>,
1490  modifiers: Modifiers,
1491  output_positions: &mut HashMap<OutPinId, PinResponse>,
1492) -> Option<DrawNodeResponse>
1493where
1494  V: TreeizeViewer<T>,
1495{
1496  let Node { pos, open, ref value } = treeize.nodes[node.0];
1497
1498  let inputs_count = usize::from(viewer.has_input(value));
1499  let outputs_count = usize::from(viewer.has_output(value));
1500
1501  let inputs = (0..inputs_count)
1502    .map(|idx| InPin::new(treeize, InPinId { node, input: idx }))
1503    .collect::<Vec<_>>();
1504
1505  let outputs = (0..outputs_count)
1506    .map(|idx| OutPin::new(treeize, OutPinId { node, output: idx }))
1507    .collect::<Vec<_>>();
1508
1509  let node_pos = pos.round_ui();
1510
1511  // Generate persistent id for the node.
1512  let node_id = treeize_id.with(("treeize-node", node));
1513
1514  let openness = ui.ctx().animate_bool(node_id, open);
1515
1516  let mut node_state = NodeState::load(ui.ctx(), node_id, ui.spacing());
1517
1518  let node_rect = node_state.node_rect(node_pos, openness);
1519
1520  let mut node_to_top = None;
1521  let mut node_moved = None;
1522  let mut drag_released = false;
1523  let mut pin_hovered = None;
1524
1525  let node_frame =
1526    viewer.node_frame(style.get_node_frame(ui.style()), node, &inputs, &outputs, treeize);
1527
1528  let header_frame =
1529    viewer.header_frame(style.get_header_frame(ui.style()), node, &inputs, &outputs, treeize);
1530
1531  // Rect for node + frame margin.
1532  let node_frame_rect = node_rect + node_frame.total_margin();
1533
1534  if treeize_state.selected_nodes().contains(&node) {
1535    let select_style = style.get_select_style(ui.style());
1536
1537    let select_rect = node_frame_rect + select_style.margin;
1538
1539    ui.painter().rect(
1540      select_rect,
1541      select_style.rounding,
1542      select_style.fill,
1543      select_style.stroke,
1544      StrokeKind::Inside,
1545    );
1546  }
1547
1548  // Size of the pin.
1549  // Side of the square or diameter of the circle.
1550  let pin_size = style.get_pin_size(ui.style()).max(0.0);
1551
1552  let pin_placement = style.get_pin_placement();
1553
1554  let header_drag_space = style.get_header_drag_space(ui.style()).max(Vec2::ZERO);
1555
1556  // Interact with node frame.
1557  let r = ui.interact(node_frame_rect, node_id.with("frame"), Sense::click_and_drag());
1558
1559  if !modifiers.shift && !modifiers.command && r.dragged_by(PointerButton::Primary) {
1560    node_moved = Some((node, r.drag_delta()));
1561  }
1562
1563  if r.clicked_by(PointerButton::Primary) || r.dragged_by(PointerButton::Primary) {
1564    if modifiers.shift {
1565      treeize_state.select_one_node(modifiers.command, node);
1566    } else if modifiers.command {
1567      treeize_state.deselect_one_node(node);
1568    }
1569  }
1570
1571  if r.clicked() || r.dragged() {
1572    node_to_top = Some(node);
1573  }
1574
1575  if viewer.has_node_menu(&treeize.nodes[node.0].value) {
1576    r.context_menu(|ui| {
1577      viewer.show_node_menu(node, &inputs, &outputs, ui, treeize);
1578    });
1579  }
1580
1581  if !treeize.nodes.contains(node.0) {
1582    node_state.clear(ui.ctx());
1583    // If removed
1584    return None;
1585  }
1586
1587  if viewer.has_on_hover_popup(&treeize.nodes[node.0].value) {
1588    r.on_hover_ui_at_pointer(|ui| {
1589      viewer.show_on_hover_popup(node, &inputs, &outputs, ui, treeize);
1590    });
1591  }
1592
1593  if !treeize.nodes.contains(node.0) {
1594    node_state.clear(ui.ctx());
1595    // If removed
1596    return None;
1597  }
1598
1599  let node_ui = &mut ui.new_child(
1600    UiBuilder::new()
1601      .max_rect(node_frame_rect.round_ui())
1602      .layout(Layout::top_down(Align::Center))
1603      .id_salt(node_id),
1604  );
1605
1606  let r = node_frame.show(node_ui, |ui| {
1607    if viewer.has_node_style(node, &inputs, &outputs, treeize) {
1608      viewer.apply_node_style(ui.style_mut(), node, &inputs, &outputs, treeize);
1609    }
1610
1611    // Input pins' center side by Y axis.
1612    let input_y = match pin_placement {
1613      PinPlacement::Inside => {
1614        pin_size.mul_add(0.5, node_frame_rect.top() + node_frame.inner_margin.topf())
1615      }
1616      PinPlacement::Edge => node_frame_rect.top(),
1617      PinPlacement::Outside { margin } => pin_size.mul_add(-0.5, node_frame_rect.top() - margin),
1618    };
1619
1620    // Output pins' center side by Y axis.
1621    let output_y = match pin_placement {
1622      PinPlacement::Inside => {
1623        pin_size.mul_add(-0.5, node_frame_rect.bottom() - node_frame.inner_margin.bottomf())
1624      }
1625      PinPlacement::Edge => node_frame_rect.bottom(),
1626      PinPlacement::Outside { margin } => pin_size.mul_add(0.5, node_frame_rect.bottom() + margin),
1627    };
1628
1629    // Input/output pin block
1630
1631    if (openness < 1.0 && open) || (openness > 0.0 && !open) {
1632      ui.ctx().request_repaint();
1633    }
1634
1635    let has_body = viewer.has_body(&treeize.nodes.get(node.0).unwrap().value);
1636
1637    let payload_rect_y = if has_body {
1638      node_rect.min.y
1639        + node_state.header_height()
1640        + header_frame.total_margin().bottom
1641        + ui.spacing().item_spacing.y
1642        - node_state.payload_offset(openness)
1643    } else {
1644      node_rect.min.y
1645    };
1646    // Pins are placed under the header and must not go outside of the header frame.
1647    let payload_rect = Rect::from_min_max(pos2(node_rect.min.x, payload_rect_y), node_rect.max);
1648
1649    let node_layout = viewer.node_layout(style.get_node_layout(), node, &inputs, &outputs, treeize);
1650
1651    let payload_clip_rect = Rect::from_min_max(node_rect.min, pos2(node_rect.max.x, f32::INFINITY));
1652
1653    let body_rect = if has_body {
1654      let body_rect = Rect::from_min_max(
1655        pos2(node_rect.left(), payload_rect.top()),
1656        pos2(node_rect.right(), payload_rect.bottom()),
1657      );
1658      let r = draw_body(treeize, viewer, node, ui, body_rect, payload_clip_rect, treeize_state);
1659
1660      r.final_rect
1661    } else {
1662      Rect::ZERO
1663    };
1664
1665    if !treeize.nodes.contains(node.0) {
1666      // If removed
1667      return;
1668    }
1669
1670    // Render header frame.
1671    let mut header_rect = Rect::NAN;
1672
1673    let mut header_frame_rect = Rect::NAN; //node_rect + header_frame.total_margin();
1674
1675    // Show node's header
1676    let header_ui: &mut Ui = &mut ui.new_child(
1677      UiBuilder::new()
1678        .max_rect(node_rect.round_ui() + header_frame.total_margin())
1679        .layout(Layout::top_down(Align::Center))
1680        .id_salt("header"),
1681    );
1682
1683    header_frame.show(header_ui, |ui: &mut Ui| {
1684      ui.with_layout(Layout::left_to_right(Align::Min), |ui| {
1685        if style.get_collapsible() && has_body {
1686          let (_, r) = ui.allocate_exact_size(
1687            vec2(ui.spacing().icon_width, ui.spacing().icon_width),
1688            Sense::click(),
1689          );
1690          paint_default_icon(ui, openness, &r);
1691
1692          if r.clicked_by(PointerButton::Primary) {
1693            // Toggle node's openness.
1694            treeize.open_node(node, !open);
1695          }
1696        }
1697
1698        if has_body {
1699          ui.allocate_exact_size(header_drag_space, Sense::hover());
1700          viewer.show_header(node, ui, treeize);
1701        } else {
1702          ui.allocate_exact_size(vec2(1.0, 0.0), Sense::hover());
1703          viewer.show_header(node, ui, treeize);
1704          ui.allocate_exact_size(vec2(1.0, 0.0), Sense::hover());
1705        }
1706
1707        header_rect = ui.min_rect();
1708      });
1709
1710      header_frame_rect = header_rect + header_frame.total_margin();
1711
1712      ui.advance_cursor_after_rect(Rect::from_min_max(
1713        header_rect.min,
1714        pos2(f32::max(header_rect.max.x, node_rect.max.x), header_rect.min.y),
1715      ));
1716    });
1717
1718    if !treeize.nodes.contains(node.0) {
1719      // If removed
1720      return;
1721    }
1722
1723    ui.expand_to_include_rect(header_rect);
1724    let header_size = header_rect.size();
1725    node_state.set_header_height(header_size.y);
1726
1727    match node_layout.kind {
1728      NodeLayoutKind::Compact => {
1729        let r = draw_inputs(
1730          treeize,
1731          viewer,
1732          node,
1733          &inputs,
1734          pin_size,
1735          style,
1736          ui,
1737          payload_rect,
1738          payload_clip_rect,
1739          input_y,
1740          header_rect.min.x,
1741          header_rect.max.x,
1742          treeize_state,
1743          modifiers,
1744          input_positions,
1745          node_layout.input_heights(&node_state),
1746        );
1747        drag_released |= r.drag_released;
1748        if r.pin_hovered.is_some() {
1749          pin_hovered = r.pin_hovered;
1750        }
1751
1752        let r = draw_outputs(
1753          treeize,
1754          viewer,
1755          node,
1756          &outputs,
1757          pin_size,
1758          style,
1759          ui,
1760          payload_rect,
1761          payload_clip_rect,
1762          output_y,
1763          header_rect.min.x,
1764          header_rect.max.x,
1765          treeize_state,
1766          modifiers,
1767          output_positions,
1768          node_layout.output_heights(&node_state),
1769        );
1770        drag_released |= r.drag_released;
1771        if r.pin_hovered.is_some() {
1772          pin_hovered = r.pin_hovered;
1773        }
1774      }
1775    }
1776
1777    let node_size_y = if has_body {
1778      header_size.y + header_frame.total_margin().bottom + body_rect.height()
1779    } else {
1780      header_size.y
1781    };
1782
1783    node_state.set_size(vec2(f32::max(header_size.x, body_rect.width()), node_size_y));
1784  });
1785
1786  if !treeize.nodes.contains(node.0) {
1787    ui.ctx().request_repaint();
1788    node_state.clear(ui.ctx());
1789    // If removed
1790    return None;
1791  }
1792
1793  viewer.final_node_rect(node, r.response.rect, ui, treeize);
1794
1795  node_state.store(ui.ctx());
1796  Some(DrawNodeResponse {
1797    node_moved,
1798    node_to_top,
1799    drag_released,
1800    pin_hovered,
1801    final_rect: r.response.rect,
1802  })
1803}
1804
1805const fn mix_colors(a: Color32, b: Color32) -> Color32 {
1806  #![allow(clippy::cast_possible_truncation)]
1807
1808  Color32::from_rgba_premultiplied(
1809    u8::midpoint(a.r(), b.r()),
1810    u8::midpoint(a.g(), b.g()),
1811    u8::midpoint(a.b(), b.b()),
1812    u8::midpoint(a.a(), b.a()),
1813  )
1814}
1815
1816impl<T> Treeize<T> {
1817  /// Render [`Treeize`] using given viewer and style into the [`Ui`].
1818  #[inline]
1819  pub fn show<V>(&mut self, viewer: &mut V, style: &TreeizeStyle, id_salt: impl Hash, ui: &mut Ui)
1820  where
1821    V: TreeizeViewer<T>,
1822  {
1823    show_treeize(
1824      ui.make_persistent_id(id_salt),
1825      *style,
1826      Vec2::ZERO,
1827      Vec2::INFINITY,
1828      self,
1829      viewer,
1830      ui,
1831      None,
1832      None,
1833    );
1834  }
1835}
1836
1837#[inline]
1838fn clamp_scale(to_global: &mut TSTransform, min_scale: f32, max_scale: f32, ui_rect: Rect) {
1839  if to_global.scaling >= min_scale && to_global.scaling <= max_scale {
1840    return;
1841  }
1842
1843  let new_scaling = to_global.scaling.clamp(min_scale, max_scale);
1844  *to_global = scale_transform_around(to_global, new_scaling, ui_rect.center());
1845}
1846
1847#[inline]
1848#[must_use]
1849fn transform_matching_points(from: Pos2, to: Pos2, scaling: f32) -> TSTransform {
1850  TSTransform { scaling, translation: to.to_vec2() - from.to_vec2() * scaling }
1851}
1852
1853#[inline]
1854#[must_use]
1855fn scale_transform_around(transform: &TSTransform, scaling: f32, point: Pos2) -> TSTransform {
1856  let from = (point - transform.translation) / transform.scaling;
1857  transform_matching_points(from, point, scaling)
1858}
1859
1860#[test]
1861const fn treeize_style_is_send_sync() {
1862  const fn is_send_sync<T: Send + Sync>() {}
1863  is_send_sync::<TreeizeStyle>();
1864}