1use std::collections::HashSet;
2
3use crate::color_hex_utils::*;
4use crate::utils::ColorUtils;
5
6use super::*;
7use egui::epaint::{CubicBezierShape, RectShape};
8use egui::*;
9
10pub type PortLocations = std::collections::HashMap<AnyParameterId, Pos2>;
11pub type NodeRects = std::collections::HashMap<NodeId, Rect>;
12
13const DISTANCE_TO_CONNECT: f32 = 10.0;
14
15#[derive(Clone, Debug)]
19pub enum NodeResponse<UserResponse: UserResponseTrait, NodeData: NodeDataTrait> {
20 ConnectEventStarted(NodeId, AnyParameterId),
21 ConnectEventEnded {
22 output: OutputId,
23 input: InputId,
24 },
25 CreatedNode(NodeId),
26 SelectNode(NodeId),
27 DeleteNodeUi(NodeId),
30 DeleteNodeFull {
34 node_id: NodeId,
35 node: Node<NodeData>,
36 },
37 DisconnectEvent {
38 output: OutputId,
39 input: InputId,
40 },
41 RaiseNode(NodeId),
43 MoveNode {
44 node: NodeId,
45 drag_delta: Vec2,
46 },
47 User(UserResponse),
48}
49
50#[derive(Clone, Debug)]
53pub struct GraphResponse<UserResponse: UserResponseTrait, NodeData: NodeDataTrait> {
54 pub node_responses: Vec<NodeResponse<UserResponse, NodeData>>,
57 pub cursor_in_editor: bool,
61 pub cursor_in_finder: bool,
63}
64
65impl<UserResponse: UserResponseTrait, NodeData: NodeDataTrait> Default
66 for GraphResponse<UserResponse, NodeData>
67{
68 fn default() -> Self {
69 Self {
70 node_responses: Default::default(),
71 cursor_in_editor: false,
72 cursor_in_finder: false,
73 }
74 }
75}
76
77pub struct GraphNodeWidget<'a, NodeData, DataType, ValueType> {
78 pub position: &'a mut Pos2,
79 pub orientation: &'a mut NodeOrientation,
80 pub graph: &'a mut Graph<NodeData, DataType, ValueType>,
81 pub port_locations: &'a mut PortLocations,
82 pub node_rects: &'a mut NodeRects,
83 pub node_id: NodeId,
84 pub ongoing_drag: Option<(NodeId, AnyParameterId)>,
85 pub selected: bool,
86 pub pan: egui::Vec2,
87}
88
89impl<NodeData, DataType, ValueType, NodeTemplate, UserResponse, UserState, CategoryType>
90 GraphEditorState<NodeData, DataType, ValueType, NodeTemplate, UserState>
91where
92 NodeData: NodeDataTrait<
93 Response = UserResponse,
94 UserState = UserState,
95 DataType = DataType,
96 ValueType = ValueType,
97 >,
98 UserResponse: UserResponseTrait,
99 ValueType:
100 WidgetValueTrait<Response = UserResponse, UserState = UserState, NodeData = NodeData>,
101 NodeTemplate: NodeTemplateTrait<
102 NodeData = NodeData,
103 DataType = DataType,
104 ValueType = ValueType,
105 UserState = UserState,
106 CategoryType = CategoryType,
107 >,
108 DataType: DataTypeTrait<UserState>,
109 CategoryType: CategoryTrait,
110{
111 #[must_use]
112 pub fn draw_graph_editor(
113 &mut self,
114 ui: &mut Ui,
115 all_kinds: impl NodeTemplateIter<Item = NodeTemplate>,
116 user_state: &mut UserState,
117 prepend_responses: Vec<NodeResponse<UserResponse, NodeData>>,
118 ) -> GraphResponse<UserResponse, NodeData> {
119 ui.set_clip_rect(ui.max_rect());
120 let clip_rect = ui.clip_rect();
121 if !self.pan_zoom.started {
123 self.zoom(ui, 1.0);
124 self.pan_zoom.started = true;
125 }
126
127 if ui.rect_contains_pointer(clip_rect) {
129 let scroll_delta = ui.input(|i| i.smooth_scroll_delta.y);
130 if scroll_delta != 0.0 {
131 let zoom_delta = (scroll_delta * 0.002).exp();
132 self.zoom(ui, zoom_delta);
133 }
134 }
135
136 let zoomed_style = self.pan_zoom.zoomed_style.clone();
138 let graph_response = show_zoomed(ui.style().clone(), zoomed_style, ui, |ui| {
139 self.draw_graph_editor_inside_zoom(ui, all_kinds, user_state, prepend_responses)
140 });
141
142 graph_response
143 }
144
145 pub fn reset_zoom(&mut self, ui: &Ui) {
147 let new_zoom = 1.0 / self.pan_zoom.zoom;
148 self.zoom(ui, new_zoom);
149 }
150
151 pub fn zoom(&mut self, ui: &Ui, zoom_delta: f32) {
154 let zoom_before = self.pan_zoom.zoom;
156 self.pan_zoom.zoom(ui.clip_rect(), ui.style(), zoom_delta);
157 if zoom_before != self.pan_zoom.zoom {
158 let actual_delta = self.pan_zoom.zoom / zoom_before;
159 self.update_node_positions_after_zoom(actual_delta);
160 }
161 }
162
163 fn update_node_positions_after_zoom(&mut self, zoom_delta: f32) {
164 let half_size = self.pan_zoom.clip_rect.size() / 2.0;
166 for (_id, node_pos) in self.node_positions.iter_mut() {
167 let local_pos = node_pos.to_vec2() - half_size + self.pan_zoom.pan;
169 let scaled_local_pos = (local_pos * zoom_delta).to_pos2();
171 *node_pos = scaled_local_pos + half_size - self.pan_zoom.pan;
173 }
175 }
176
177 fn draw_graph_editor_inside_zoom(
178 &mut self,
179 ui: &mut Ui,
180 all_kinds: impl NodeTemplateIter<Item = NodeTemplate>,
181 user_state: &mut UserState,
182 prepend_responses: Vec<NodeResponse<UserResponse, NodeData>>,
183 ) -> GraphResponse<UserResponse, NodeData> {
184 let editor_rect = ui.max_rect();
188 let resp = ui.allocate_rect(editor_rect, Sense::hover());
189
190 let cursor_pos = ui
191 .ctx()
192 .input(|i| i.pointer.hover_pos().unwrap_or(Pos2::ZERO));
193 let mut cursor_in_editor = resp.contains_pointer();
194 let mut cursor_in_finder = false;
195
196 let mut port_locations = PortLocations::new();
198 let mut node_rects = NodeRects::new();
199
200 let mut delayed_responses: Vec<NodeResponse<UserResponse, NodeData>> = prepend_responses;
203
204 let mut drag_started_on_background = false;
206 let mut drag_released_on_background = false;
207
208 debug_assert_eq!(
209 self.node_order.iter().copied().collect::<HashSet<_>>(),
210 self.graph.iter_nodes().collect::<HashSet<_>>(),
211 "The node_order field of the GraphEditorself was left in an \
212 inconsistent self. It has either more or less values than the graph."
213 );
214
215 let r = ui.allocate_rect(ui.min_rect(), Sense::click().union(Sense::drag()));
218 if r.drag_started() {
219 drag_started_on_background = true;
220 } else if r.drag_stopped() {
221 drag_released_on_background = true;
222 }
223
224 for node_id in self.node_order.iter().copied() {
226 let responses = GraphNodeWidget {
227 position: self.node_positions.get_mut(node_id).unwrap(),
228 orientation: self.node_orientations.get_mut(node_id).unwrap(),
229 graph: &mut self.graph,
230 port_locations: &mut port_locations,
231 node_rects: &mut node_rects,
232 node_id,
233 ongoing_drag: self.connection_in_progress,
234 selected: self.selected_nodes.contains(&node_id),
235 pan: self.pan_zoom.pan + editor_rect.min.to_vec2(),
236 }
237 .show(&self.pan_zoom, ui, user_state);
238
239 delayed_responses.extend(responses);
241 }
242
243 let mut should_close_node_finder = false;
245 if let Some(ref mut node_finder) = self.node_finder {
246 let mut node_finder_area = Area::new(Id::from("node_finder")).order(Order::Foreground);
247 if let Some(pos) = node_finder.position {
248 node_finder_area = node_finder_area.current_pos(pos);
249 }
250 node_finder_area.show(ui.ctx(), |ui| {
251 if let Some(node_kind) = node_finder.show(ui, all_kinds, user_state) {
252 let new_node = self.graph.add_node(
253 node_kind.node_graph_label(user_state),
254 node_kind.user_data(user_state),
255 |graph, node_id| node_kind.build_node(graph, user_state, node_id),
256 );
257 self.node_positions.insert(
258 new_node,
259 node_finder.position.unwrap_or(cursor_pos)
260 - self.pan_zoom.pan
261 - editor_rect.min.to_vec2(),
262 );
263 self.node_orientations
264 .insert(new_node, NodeOrientation::LeftToRight);
265 self.node_order.push(new_node);
266
267 should_close_node_finder = true;
268 delayed_responses.push(NodeResponse::CreatedNode(new_node));
269 }
270 let finder_rect = ui.min_rect();
271 if finder_rect.contains(cursor_pos) {
274 cursor_in_editor = true;
275 cursor_in_finder = true;
276 }
277 });
278 }
279 if should_close_node_finder {
280 self.node_finder = None;
281 }
282
283 fn port_control(param_id: &AnyParameterId, orientation: NodeOrientation) -> Vec2 {
285 match (param_id, orientation) {
286 (AnyParameterId::Input(_), NodeOrientation::LeftToRight) => -Vec2::X,
287 (AnyParameterId::Input(_), NodeOrientation::RightToLeft) => Vec2::X,
288 (AnyParameterId::Output(_), NodeOrientation::LeftToRight) => Vec2::X,
289 (AnyParameterId::Output(_), NodeOrientation::RightToLeft) => -Vec2::X,
290 }
291 }
292
293 if let Some((_, ref locator)) = self.connection_in_progress {
294 let port_type = self.graph.any_param_type(*locator).unwrap();
295 let connection_color = port_type.data_type_color(user_state);
296 let start_pos = port_locations[locator];
297
298 fn snap_to_ports<
300 NodeData,
301 UserState,
302 DataType: DataTypeTrait<UserState>,
303 ValueType,
304 Key: slotmap::Key + Into<AnyParameterId>,
305 Value,
306 >(
307 graph: &Graph<NodeData, DataType, ValueType>,
308 port_type: &DataType,
309 ports: &SlotMap<Key, Value>,
310 port_locations: &PortLocations,
311 node_orientations: &SecondaryMap<NodeId, NodeOrientation>,
312 cursor_pos: Pos2,
313 default_control: Vec2,
314 ) -> (Pos2, Vec2) {
315 ports
316 .iter()
317 .find_map(|(port_id, _)| {
318 let compatible_ports = graph
319 .any_param_type(port_id.into())
320 .map(|other| other == port_type)
321 .unwrap_or(false);
322
323 if compatible_ports {
324 port_locations.get(&port_id.into()).and_then(|port_pos| {
325 if port_pos.distance(cursor_pos) < DISTANCE_TO_CONNECT {
326 let param_id: AnyParameterId = port_id.into();
327 let dst_node_id = match param_id {
328 AnyParameterId::Output(id) => graph.get_output(id).node,
329 AnyParameterId::Input(id) => graph.get_input(id).node,
330 };
331 let dst_orientation = node_orientations[dst_node_id];
332 let dst_control = port_control(¶m_id, dst_orientation);
333
334 Some((*port_pos, dst_control))
335 } else {
336 None
337 }
338 })
339 } else {
340 None
341 }
342 })
343 .unwrap_or((cursor_pos, default_control))
344 }
345
346 let src_node_id = match locator {
348 AnyParameterId::Output(out_id) => self.graph.get_output(*out_id).node,
349 AnyParameterId::Input(in_id) => self.graph.get_input(*in_id).node,
350 };
351 let src_orientation = self.node_orientations[src_node_id];
352 let src_control = port_control(locator, src_orientation);
353
354 let (dst_pos, dst_control) = match locator {
356 AnyParameterId::Output(_) => snap_to_ports(
357 &self.graph,
358 port_type,
359 &self.graph.inputs,
360 &port_locations,
361 &self.node_orientations,
362 cursor_pos,
363 -src_control,
364 ),
365
366 AnyParameterId::Input(_) => snap_to_ports(
367 &self.graph,
368 port_type,
369 &self.graph.outputs,
370 &port_locations,
371 &self.node_orientations,
372 cursor_pos,
373 -src_control,
374 ),
375 };
376 draw_connection(
377 &self.pan_zoom,
378 ui.painter(),
379 start_pos,
380 src_control,
381 dst_pos,
382 dst_control,
383 connection_color,
384 );
385 }
386
387 for (input, output) in self.graph.iter_connections() {
388 let port_type = self
389 .graph
390 .any_param_type(AnyParameterId::Output(output))
391 .unwrap();
392 let connection_color = port_type.data_type_color(user_state);
393 let src_pos = port_locations[&AnyParameterId::Output(output)];
394 let dst_pos = port_locations[&AnyParameterId::Input(input)];
395 let src_id = self.graph.get_output(output).node;
396 let dst_id = self.graph.get_input(input).node;
397 let src_orientation = self.node_orientations[src_id];
398 let dst_orientation = self.node_orientations[dst_id];
399 let src_control = port_control(&output.into(), src_orientation);
400 let dst_control = port_control(&input.into(), dst_orientation);
401 draw_connection(
402 &self.pan_zoom,
403 ui.painter(),
404 src_pos,
405 src_control,
406 dst_pos,
407 dst_control,
408 connection_color,
409 );
410 }
411
412 let mut extra_responses: Vec<NodeResponse<UserResponse, NodeData>> = Vec::new();
417
418 for response in delayed_responses.iter() {
419 match response {
420 NodeResponse::ConnectEventStarted(node_id, port) => {
421 self.connection_in_progress = Some((*node_id, *port));
422 }
423 NodeResponse::ConnectEventEnded { input, output } => {
424 self.graph.add_connection(*output, *input)
425 }
426 NodeResponse::CreatedNode(_) => {
427 }
429 NodeResponse::SelectNode(node_id) => {
430 self.selected_nodes = Vec::from([*node_id]);
431 }
432 NodeResponse::DeleteNodeUi(node_id) => {
433 let (node, disc_events) = self.graph.remove_node(*node_id);
434 extra_responses.extend(
437 disc_events
438 .into_iter()
439 .map(|(input, output)| NodeResponse::DisconnectEvent { input, output }),
440 );
441 extra_responses.push(NodeResponse::DeleteNodeFull {
444 node_id: *node_id,
445 node,
446 });
447 self.node_positions.remove(*node_id);
448 self.selected_nodes.retain(|id| *id != *node_id);
450 self.node_order.retain(|id| *id != *node_id);
451 }
452 NodeResponse::DisconnectEvent { input, output } => {
453 let other_node = self.graph.get_output(*output).node;
454 self.graph.remove_connection(*input);
455 self.connection_in_progress =
456 Some((other_node, AnyParameterId::Output(*output)));
457 }
458 NodeResponse::RaiseNode(node_id) => {
459 let old_pos = self
460 .node_order
461 .iter()
462 .position(|id| *id == *node_id)
463 .expect("Node to be raised should be in `node_order`");
464 self.node_order.remove(old_pos);
465 self.node_order.push(*node_id);
466 }
467 NodeResponse::MoveNode { node, drag_delta } => {
468 self.node_positions[*node] += *drag_delta;
469 if self.selected_nodes.contains(node) && self.selected_nodes.len() > 1 {
471 for n in self.selected_nodes.iter().copied() {
472 if n != *node {
473 self.node_positions[n] += *drag_delta;
474 }
475 }
476 }
477 }
478 NodeResponse::User(_) => {
479 }
481 NodeResponse::DeleteNodeFull { .. } => {
482 unreachable!("The UI should never produce a DeleteNodeFull event.")
483 }
484 }
485 }
486
487 if let Some(box_start) = self.ongoing_box_selection {
489 let selection_rect = Rect::from_two_pos(cursor_pos, box_start);
490 let bg_color = Color32::from_rgba_unmultiplied(200, 200, 200, 20);
491 let stroke_color = Color32::from_rgba_unmultiplied(200, 200, 200, 180);
492 ui.painter().rect(
493 selection_rect,
494 2.0,
495 bg_color,
496 Stroke::new(3.0f32, stroke_color),
497 StrokeKind::Outside,
498 );
499
500 self.selected_nodes = node_rects
501 .into_iter()
502 .filter_map(|(node_id, rect)| {
503 if selection_rect.intersects(rect) {
504 Some(node_id)
505 } else {
506 None
507 }
508 })
509 .collect();
510 }
511
512 delayed_responses.extend(extra_responses);
516
517 let mouse = &ui.ctx().input(|i| i.pointer.clone());
521
522 if mouse.any_released() && self.connection_in_progress.is_some() {
523 self.connection_in_progress = None;
524 }
525
526 if mouse.secondary_released() && !cursor_in_finder {
527 self.node_finder = Some(NodeFinder::new_at(cursor_pos));
528 }
529 if ui.ctx().input(|i| i.key_pressed(Key::Escape)) {
530 self.node_finder = None;
531 }
532
533 if r.dragged() && ui.ctx().input(|i| i.pointer.middle_down()) {
534 self.pan_zoom.pan += ui.ctx().input(|i| i.pointer.delta());
535 }
536
537 if mouse.any_pressed() && !cursor_in_finder {
540 self.selected_nodes = Vec::new();
541 self.node_finder = None;
542 }
543
544 if drag_started_on_background && mouse.primary_down() {
545 self.ongoing_box_selection = Some(cursor_pos);
546 }
547 if mouse.primary_released() || drag_released_on_background {
548 self.ongoing_box_selection = None;
549 }
550
551 GraphResponse {
552 node_responses: delayed_responses,
553 cursor_in_editor,
554 cursor_in_finder,
555 }
556 }
557}
558
559fn draw_connection(
560 pan_zoom: &PanZoom,
561 painter: &Painter,
562 src_pos: Pos2,
563 src_control: Vec2,
564 dst_pos: Pos2,
565 dst_control: Vec2,
566 color: Color32,
567) {
568 let connection_stroke = egui::Stroke {
569 width: 5.0 * pan_zoom.zoom,
570 color,
571 };
572
573 let control_scale = ((dst_pos.x - src_pos.x) / 2.0).abs().max(30.0);
574 let src_control = src_pos + src_control * control_scale;
575 let dst_control = dst_pos + dst_control * control_scale;
576
577 let bezier = CubicBezierShape::from_points_stroke(
578 [src_pos, src_control, dst_control, dst_pos],
579 false,
580 Color32::TRANSPARENT,
581 connection_stroke,
582 );
583
584 painter.add(bezier);
585
586 let [r, g, b, a] = color.to_srgba_unmultiplied();
587 let wide_stroke = egui::Stroke {
588 width: 10.0,
589 color: Color32::from_rgba_unmultiplied(r / 2, g / 2, b / 2, a / 2),
590 };
591
592 let wide_bezier = CubicBezierShape::from_points_stroke(
593 [src_pos, src_control, dst_control, dst_pos],
594 false,
595 Color32::TRANSPARENT,
596 wide_stroke,
597 );
598
599 painter.add(wide_bezier);
600}
601
602#[derive(Clone, Copy, Debug)]
603struct OuterRectMemory(Rect);
604
605impl<NodeData, DataType, ValueType, UserResponse, UserState>
606 GraphNodeWidget<'_, NodeData, DataType, ValueType>
607where
608 NodeData: NodeDataTrait<
609 Response = UserResponse,
610 UserState = UserState,
611 DataType = DataType,
612 ValueType = ValueType,
613 >,
614 UserResponse: UserResponseTrait,
615 ValueType:
616 WidgetValueTrait<Response = UserResponse, UserState = UserState, NodeData = NodeData>,
617 DataType: DataTypeTrait<UserState>,
618{
619 pub const MAX_NODE_SIZE: [f32; 2] = [200.0, 200.0];
620
621 pub fn show(
622 self,
623 pan_zoom: &PanZoom,
624 ui: &mut Ui,
625 user_state: &mut UserState,
626 ) -> Vec<NodeResponse<UserResponse, NodeData>> {
627 let mut child_ui = ui.new_child(
628 UiBuilder::new()
629 .max_rect(Rect::from_min_size(
630 *self.position + self.pan,
631 Self::MAX_NODE_SIZE.into(),
632 ))
633 .layout(*ui.layout())
634 .id_salt(self.node_id),
635 );
636
637 Self::show_graph_node(self, pan_zoom, &mut child_ui, user_state)
638 }
639
640 fn show_graph_node(
643 self,
644 pan_zoom: &PanZoom,
645 ui: &mut Ui,
646 user_state: &mut UserState,
647 ) -> Vec<NodeResponse<UserResponse, NodeData>> {
648 let margin = egui::vec2(15.0, 5.0) * pan_zoom.zoom;
649 let mut responses = Vec::<NodeResponse<UserResponse, NodeData>>::new();
650
651 let background_color;
652 let text_color;
653 if ui.visuals().dark_mode {
654 background_color = color_from_hex("#3f3f3f").unwrap();
655 text_color = color_from_hex("#fefefe").unwrap();
656 } else {
657 background_color = color_from_hex("#ffffff").unwrap();
658 text_color = color_from_hex("#505050").unwrap();
659 }
660
661 ui.visuals_mut().widgets.noninteractive.fg_stroke =
662 Stroke::new(2.0 * pan_zoom.zoom, text_color);
663
664 let outline_shape = ui.painter().add(Shape::Noop);
666 let background_shape = ui.painter().add(Shape::Noop);
667
668 let mut outer_rect_bounds = ui.available_rect_before_wrap();
669 outer_rect_bounds.max.x =
671 outer_rect_bounds.min.x + outer_rect_bounds.width() * pan_zoom.zoom;
672 let mut inner_rect = outer_rect_bounds.shrink2(margin);
673
674 inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x);
676 inner_rect.max.y = inner_rect.max.y.max(inner_rect.min.y);
677
678 let mut child_ui = ui.new_child(UiBuilder::new().max_rect(inner_rect).layout(*ui.layout()));
679
680 let interaction_rect = ui
682 .ctx()
683 .memory_mut(|mem| {
684 mem.data
685 .get_temp::<OuterRectMemory>(child_ui.id())
686 .map(|stored| stored.0)
687 })
688 .unwrap_or(outer_rect_bounds);
689 let window_response = ui.interact(
692 interaction_rect,
693 Id::new((self.node_id, "window")),
694 Sense::click_and_drag(),
695 );
696
697 let mut title_height = 0.0;
698
699 let mut input_port_heights = vec![];
700 let mut output_port_heights = vec![];
701
702 child_ui.vertical(|ui| {
703 ui.horizontal(|ui| {
704 ui.add(
705 Label::new(
706 RichText::new(&self.graph[self.node_id].label)
707 .text_style(TextStyle::Button)
708 .color(text_color),
709 )
710 .selectable(false),
711 );
712 responses.extend(self.graph[self.node_id].user_data.top_bar_ui(
713 ui,
714 self.node_id,
715 self.graph,
716 user_state,
717 ));
718 ui.add_space(8.0 * pan_zoom.zoom); ui.add_space(4.0 * pan_zoom.zoom); ui.add_space(8.0 * pan_zoom.zoom); });
722 ui.add_space(margin.y);
723 title_height = ui.min_size().y;
724
725 let input_layout = match self.orientation {
727 NodeOrientation::LeftToRight => Layout::left_to_right(Align::default()),
728 NodeOrientation::RightToLeft => Layout::right_to_left(Align::default()),
729 };
730 let output_layout = match self.orientation {
731 NodeOrientation::LeftToRight => Layout::right_to_left(Align::default()),
732 NodeOrientation::RightToLeft => Layout::left_to_right(Align::default()),
733 };
734
735 let inputs = self.graph[self.node_id].inputs.clone();
736 for (param_name, param_id) in inputs {
737 if self.graph[param_id].shown_inline {
738 let height_before = ui.min_rect().bottom();
739 let mut value = std::mem::take(&mut self.graph[param_id].value);
747
748 ui.with_layout(input_layout, |ui| {
749 if self.graph.connection(param_id).is_some() {
750 let node_responses = value.value_widget_connected(
751 ¶m_name,
752 self.node_id,
753 ui,
754 user_state,
755 &self.graph[self.node_id].user_data,
756 );
757
758 responses.extend(node_responses.into_iter().map(NodeResponse::User));
759 } else {
760 let node_responses = value.value_widget(
761 ¶m_name,
762 self.node_id,
763 ui,
764 user_state,
765 &self.graph[self.node_id].user_data,
766 );
767
768 responses.extend(node_responses.into_iter().map(NodeResponse::User));
769 }
770 });
771
772 self.graph[param_id].value = value;
773
774 self.graph[self.node_id].user_data.separator(
775 ui,
776 self.node_id,
777 AnyParameterId::Input(param_id),
778 self.graph,
779 user_state,
780 );
781
782 let height_after = ui.min_rect().bottom();
783 input_port_heights.push((height_before + height_after) / 2.0);
784 }
785 }
786
787 let outputs = self.graph[self.node_id].outputs.clone();
788 for (param_name, param_id) in outputs {
789 let height_before = ui.min_rect().bottom();
790 ui.with_layout(output_layout, |ui| {
791 responses.extend(self.graph[self.node_id].user_data.output_ui(
792 ui,
793 self.node_id,
794 self.graph,
795 user_state,
796 ¶m_name,
797 ));
798 });
799
800 self.graph[self.node_id].user_data.separator(
801 ui,
802 self.node_id,
803 AnyParameterId::Output(param_id),
804 self.graph,
805 user_state,
806 );
807
808 let height_after = ui.min_rect().bottom();
809 output_port_heights.push((height_before + height_after) / 2.0);
810 }
811
812 responses.extend(self.graph[self.node_id].user_data.bottom_ui(
813 ui,
814 self.node_id,
815 self.graph,
816 user_state,
817 ));
818 });
819
820 let outer_rect = child_ui.min_rect().expand2(margin);
824 let port_left = outer_rect.left();
825 let port_right = outer_rect.right();
826
827 ui.ctx().memory_mut(|mem| {
829 mem.data
830 .insert_temp(child_ui.id(), OuterRectMemory(outer_rect))
831 });
832
833 #[allow(clippy::too_many_arguments)]
834 fn draw_port<NodeData, DataType, ValueType, UserResponse, UserState>(
835 pan_zoom: &PanZoom,
836 ui: &mut Ui,
837 graph: &Graph<NodeData, DataType, ValueType>,
838 node_id: NodeId,
839 user_state: &mut UserState,
840 port_pos: Pos2,
841 responses: &mut Vec<NodeResponse<UserResponse, NodeData>>,
842 param_id: AnyParameterId,
843 port_locations: &mut PortLocations,
844 ongoing_drag: Option<(NodeId, AnyParameterId)>,
845 is_connected_input: bool,
846 ) where
847 DataType: DataTypeTrait<UserState>,
848 UserResponse: UserResponseTrait,
849 NodeData: NodeDataTrait,
850 {
851 let port_type = graph.any_param_type(param_id).unwrap();
852
853 let port_rect = Rect::from_center_size(
854 port_pos,
855 egui::vec2(DISTANCE_TO_CONNECT * 2.0, DISTANCE_TO_CONNECT * 2.0) * pan_zoom.zoom,
856 );
857
858 let sense = if ongoing_drag.is_some() {
859 Sense::hover()
860 } else {
861 Sense::click_and_drag()
862 };
863
864 let resp = ui.allocate_rect(port_rect, sense);
865
866 let close_enough = if let Some(pointer_pos) = ui.ctx().pointer_hover_pos() {
868 port_rect.center().distance(pointer_pos) < DISTANCE_TO_CONNECT * pan_zoom.zoom
869 } else {
870 false
871 };
872
873 let port_color = if close_enough {
874 Color32::WHITE
875 } else {
876 port_type.data_type_color(user_state)
877 };
878 ui.painter().circle(
879 port_rect.center(),
880 5.0 * pan_zoom.zoom,
881 port_color,
882 Stroke::NONE,
883 );
884
885 if resp.drag_started() {
886 if is_connected_input {
887 let input = param_id.assume_input();
888 let corresp_output = graph
889 .connection(input)
890 .expect("Connection data should be valid");
891 responses.push(NodeResponse::DisconnectEvent {
892 input: param_id.assume_input(),
893 output: corresp_output,
894 });
895 } else {
896 responses.push(NodeResponse::ConnectEventStarted(node_id, param_id));
897 }
898 }
899
900 if let Some((origin_node, origin_param)) = ongoing_drag {
901 if origin_node != node_id {
902 if graph.any_param_type(origin_param).unwrap() == port_type
904 && close_enough
905 && ui.input(|i| i.pointer.any_released())
906 {
907 match (param_id, origin_param) {
908 (AnyParameterId::Input(input), AnyParameterId::Output(output))
909 | (AnyParameterId::Output(output), AnyParameterId::Input(input)) => {
910 responses.push(NodeResponse::ConnectEventEnded { input, output });
911 }
912 _ => { }
913 }
914 }
915 }
916 }
917
918 port_locations.insert(param_id, port_rect.center());
919 }
920
921 for ((_, param), port_height) in self.graph[self.node_id]
923 .inputs
924 .iter()
925 .zip(input_port_heights)
926 {
927 let should_draw = match self.graph[*param].kind() {
928 InputParamKind::ConnectionOnly => true,
929 InputParamKind::ConstantOnly => false,
930 InputParamKind::ConnectionOrConstant => true,
931 };
932
933 if should_draw {
934 let port_pos = match self.orientation {
935 NodeOrientation::LeftToRight => pos2(port_left, port_height),
936 NodeOrientation::RightToLeft => pos2(port_right, port_height),
937 };
938 draw_port(
939 pan_zoom,
940 ui,
941 self.graph,
942 self.node_id,
943 user_state,
944 port_pos,
945 &mut responses,
946 AnyParameterId::Input(*param),
947 self.port_locations,
948 self.ongoing_drag,
949 self.graph.connection(*param).is_some(),
950 );
951 }
952 }
953
954 for ((_, param), port_height) in self.graph[self.node_id]
956 .outputs
957 .iter()
958 .zip(output_port_heights)
959 {
960 let port_pos = match self.orientation {
961 NodeOrientation::LeftToRight => pos2(port_right, port_height),
962 NodeOrientation::RightToLeft => pos2(port_left, port_height),
963 };
964 draw_port(
965 pan_zoom,
966 ui,
967 self.graph,
968 self.node_id,
969 user_state,
970 port_pos,
971 &mut responses,
972 AnyParameterId::Output(*param),
973 self.port_locations,
974 self.ongoing_drag,
975 false,
976 );
977 }
978
979 let (shape, outline) = {
984 let rounding_radius = (4.0 * pan_zoom.zoom) as u8;
985 let corner_radius = CornerRadius::same(rounding_radius);
986
987 let titlebar_height = title_height + margin.y;
988 let titlebar_rect =
989 Rect::from_min_size(outer_rect.min, vec2(outer_rect.width(), titlebar_height));
990 let titlebar = Shape::Rect(RectShape {
991 blur_width: 0.0,
992 rect: titlebar_rect,
993 corner_radius,
994 fill: self.graph[self.node_id]
995 .user_data
996 .titlebar_color(ui, self.node_id, self.graph, user_state)
997 .unwrap_or_else(|| background_color.lighten(0.8)),
998 stroke: Stroke::NONE,
999 stroke_kind: StrokeKind::Inside,
1000 round_to_pixels: None,
1001 brush: None,
1002 angle: 0.0,
1003 });
1004
1005 let body_rect = Rect::from_min_size(
1006 outer_rect.min + vec2(0.0, titlebar_height - rounding_radius as f32),
1007 vec2(outer_rect.width(), outer_rect.height() - titlebar_height),
1008 );
1009 let body = Shape::Rect(RectShape {
1010 blur_width: 0.0,
1011 rect: body_rect,
1012 corner_radius: CornerRadius::ZERO,
1013 fill: background_color,
1014 stroke: Stroke::NONE,
1015 stroke_kind: StrokeKind::Inside,
1016 round_to_pixels: None,
1017 brush: None,
1018 angle: 0.0,
1019 });
1020
1021 let bottom_body_rect = Rect::from_min_size(
1022 body_rect.min + vec2(0.0, body_rect.height() - titlebar_height * 0.5),
1023 vec2(outer_rect.width(), titlebar_height),
1024 );
1025 let bottom_body = Shape::Rect(RectShape {
1026 blur_width: 0.0,
1027 rect: bottom_body_rect,
1028 corner_radius,
1029 fill: background_color,
1030 stroke: Stroke::NONE,
1031 stroke_kind: StrokeKind::Inside,
1032 round_to_pixels: None,
1033 brush: None,
1034 angle: 0.0,
1035 });
1036
1037 let node_rect = titlebar_rect.union(body_rect).union(bottom_body_rect);
1038 let outline = if self.selected {
1039 Shape::Rect(RectShape {
1040 blur_width: 0.0,
1041 rect: node_rect.expand(1.0 * pan_zoom.zoom),
1042 corner_radius,
1043 fill: Color32::WHITE.lighten(0.8),
1044 stroke: Stroke::NONE,
1045 stroke_kind: StrokeKind::Inside,
1046 round_to_pixels: None,
1047 brush: None,
1048 angle: 0.0,
1049 })
1050 } else {
1051 Shape::Noop
1052 };
1053
1054 self.node_rects.insert(self.node_id, node_rect);
1056
1057 (Shape::Vec(vec![titlebar, body, bottom_body]), outline)
1058 };
1059
1060 ui.painter().set(background_shape, shape);
1061 ui.painter().set(outline_shape, outline);
1062
1063 let can_flip =
1067 self.graph.nodes[self.node_id]
1068 .user_data
1069 .can_flip(self.node_id, self.graph, user_state);
1070
1071 if can_flip && Self::flip_button(pan_zoom, ui, outer_rect).clicked() {
1072 *self.orientation = self.orientation.flip();
1073 }
1074
1075 let can_delete = self.graph.nodes[self.node_id].user_data.can_delete(
1076 self.node_id,
1077 self.graph,
1078 user_state,
1079 );
1080
1081 if can_delete && Self::close_button(pan_zoom, ui, outer_rect).clicked() {
1082 responses.push(NodeResponse::DeleteNodeUi(self.node_id));
1083 };
1084
1085 let drag_delta = window_response.drag_delta();
1087 if drag_delta.length_sq() > 0.0 {
1088 responses.push(NodeResponse::MoveNode {
1089 node: self.node_id,
1090 drag_delta,
1091 });
1092 responses.push(NodeResponse::RaiseNode(self.node_id));
1093 }
1094
1095 if responses.is_empty() && window_response.clicked_by(PointerButton::Primary) {
1100 responses.push(NodeResponse::SelectNode(self.node_id));
1101 responses.push(NodeResponse::RaiseNode(self.node_id));
1102 }
1103
1104 responses
1105 }
1106
1107 fn close_button(pan_zoom: &PanZoom, ui: &mut Ui, node_rect: Rect) -> Response {
1108 let margin = 8.0 * pan_zoom.zoom;
1110 let size = 10.0 * pan_zoom.zoom;
1111 let stroke_width = 2.0;
1112 let offs = margin + size / 2.0;
1113
1114 let position = pos2(node_rect.right() - offs, node_rect.top() + offs);
1115 let rect = Rect::from_center_size(position, vec2(size, size));
1116 let resp = ui.allocate_rect(rect, Sense::click());
1117
1118 let dark_mode = ui.visuals().dark_mode;
1119 let color = if resp.clicked() {
1120 if dark_mode {
1121 color_from_hex("#ffffff").unwrap()
1122 } else {
1123 color_from_hex("#000000").unwrap()
1124 }
1125 } else if resp.hovered() {
1126 if dark_mode {
1127 color_from_hex("#dddddd").unwrap()
1128 } else {
1129 color_from_hex("#222222").unwrap()
1130 }
1131 } else {
1132 #[allow(clippy::collapsible_else_if)]
1133 if dark_mode {
1134 color_from_hex("#aaaaaa").unwrap()
1135 } else {
1136 color_from_hex("#555555").unwrap()
1137 }
1138 };
1139 let stroke = Stroke {
1140 width: stroke_width,
1141 color,
1142 };
1143
1144 ui.painter()
1145 .line_segment([rect.left_top(), rect.right_bottom()], stroke);
1146 ui.painter()
1147 .line_segment([rect.right_top(), rect.left_bottom()], stroke);
1148
1149 resp
1150 }
1151
1152 fn flip_button(pan_zoom: &PanZoom, ui: &mut Ui, node_rect: Rect) -> Response {
1153 let margin = 8.0 * pan_zoom.zoom;
1155 let size = 10.0 * pan_zoom.zoom;
1156 let stroke_width = 2.0;
1157 let offs = margin + size / 2.0;
1158
1159 let position = pos2(node_rect.right() - offs * 2.0 - 4.0, node_rect.top() + offs);
1160 let rect = Rect::from_center_size(position, vec2(size, size));
1161 let resp = ui.allocate_rect(rect, Sense::click());
1162
1163 let dark_mode = ui.visuals().dark_mode;
1164 let color = if resp.clicked() {
1165 if dark_mode {
1166 color_from_hex("#ffffff").unwrap()
1167 } else {
1168 color_from_hex("#000000").unwrap()
1169 }
1170 } else if resp.hovered() {
1171 if dark_mode {
1172 color_from_hex("#dddddd").unwrap()
1173 } else {
1174 color_from_hex("#222222").unwrap()
1175 }
1176 } else {
1177 #[allow(clippy::collapsible_else_if)]
1178 if dark_mode {
1179 color_from_hex("#aaaaaa").unwrap()
1180 } else {
1181 color_from_hex("#555555").unwrap()
1182 }
1183 };
1184 let stroke = Stroke {
1185 width: stroke_width,
1186 color,
1187 };
1188
1189 let lines = [
1190 [rect.left_center(), rect.right_center()],
1191 [
1192 rect.left_center(),
1193 rect.left_center().lerp(rect.center_top(), 0.5),
1194 ],
1195 [
1196 rect.left_center(),
1197 rect.left_center().lerp(rect.center_bottom(), 0.5),
1198 ],
1199 [
1200 rect.right_center(),
1201 rect.right_center().lerp(rect.center_top(), 0.5),
1202 ],
1203 [
1204 rect.right_center(),
1205 rect.right_center().lerp(rect.center_bottom(), 0.5),
1206 ],
1207 ];
1208
1209 for line in lines {
1210 ui.painter().line_segment(line, stroke);
1211 }
1212
1213 resp
1214 }
1215}