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