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