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