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