1use 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#[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 #[default]
60 Compact,
61}
62
63#[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 pub kind: NodeLayoutKind,
72
73 pub min_pin_row_height: f32,
75
76 pub equal_pin_row_heights: bool,
80}
81
82impl NodeLayout {
83 #[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#[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 pub margin: Margin,
163
164 pub rounding: CornerRadius,
166
167 pub fill: Color32,
169
170 pub stroke: Stroke,
172}
173
174#[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 #[default]
181 Inside,
182
183 Edge,
185
186 Outside {
188 margin: f32,
190 },
191}
192
193#[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 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
201 pub node_layout: Option<NodeLayout>,
202
203 #[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 #[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 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
225 pub header_drag_space: Option<Vec2>,
226
227 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
231 pub collapsible: Option<bool>,
232
233 #[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 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
240 pub pin_fill: Option<Color32>,
241
242 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
244 pub pin_stroke: Option<Stroke>,
245
246 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
248 pub pin_shape: Option<PinShape>,
249
250 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
252 pub pin_placement: Option<PinPlacement>,
253
254 #[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 #[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 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
266 pub downscale_wire_frame: Option<bool>,
267
268 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
270 pub upscale_wire_frame: Option<bool>,
271
272 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
274 pub wire_style: Option<WireStyle>,
275
276 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
278 pub wire_layer: Option<WireLayer>,
279
280 #[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 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
290 pub bg_pattern: Option<BackgroundPattern>,
291
292 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
295 pub bg_pattern_stroke: Option<Stroke>,
296
297 #[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 #[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 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
309 pub select_stoke: Option<Stroke>,
310
311 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
313 pub select_fill: Option<Color32>,
314
315 pub select_rect_contained: Option<bool>,
319
320 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
322 pub select_style: Option<SelectionStyle>,
323
324 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none", default))]
327 pub crisp_magnified_text: Option<bool>,
328
329 #[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 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 #[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#[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
596pub struct TreeizeLayoutSignal {
598 pub layout_signal: bool,
600 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 #[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 #[inline]
632 #[must_use]
633 pub const fn id(mut self, id: Id) -> Self {
634 self.id = Some(id);
635 self
636 }
637
638 #[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 #[inline]
652 #[must_use]
653 pub const fn style(mut self, style: TreeizeStyle) -> Self {
654 self.style = style;
655 self
656 }
657
658 #[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 #[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 #[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 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 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 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 ui.ctx().set_transform_layer(treeize_layer_id, to_global);
801
802 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 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 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 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 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 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 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 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 treeize_state.set_new_wires_menu(new_wires);
1039
1040 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 wire_end_pos = menu_pos;
1066
1067 viewer.show_dropped_wire_menu(menu_pos, ui, pins, treeize);
1069
1070 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 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 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 let treeize_pin = viewer.show_input(in_pin, pin_ui, treeize);
1228 if !treeize.nodes.contains(node.0) {
1229 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 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 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 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 for out_pin in outputs {
1349 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 let treeize_pin = viewer.show_output(out_pin, pin_ui, treeize);
1365 if !treeize.nodes.contains(node.0) {
1366 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 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 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 DrawBodyResponse { final_rect }
1474}
1475
1476#[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 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 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 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 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 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 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 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 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 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 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 return;
1668 }
1669
1670 let mut header_rect = Rect::NAN;
1672
1673 let mut header_frame_rect = Rect::NAN; 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 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 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 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 #[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}