1use alloc::vec::Vec;
15use core::fmt;
16
17use azul_core::{
18 callbacks::{CoreCallback, CoreCallbackData, Update},
19 dom::{Dom, EventFilter, HoverEventFilter, IdOrClass, IdOrClass::Class, IdOrClassVec},
20 geom::{LogicalPosition, LogicalRect, LogicalSize, PhysicalSizeU32},
21 gl::Texture,
22 menu::{Menu, MenuItem, StringMenuItem},
23 refany::{OptionRefAny, RefAny},
24 resources::{ImageRef, RawImageFormat},
25 svg::{SvgPath, SvgPathElement, SvgStrokeStyle, TessellatedGPUSvgNode},
26 window::CursorPosition::InWindow,
27};
28#[allow(clippy::wildcard_imports)] use azul_css::{
30 dynamic_selector::{CssPropertyWithConditions, CssPropertyWithConditionsVec},
31 props::{
32 basic::*,
33 layout::*,
34 property::{CssProperty, *},
35 style::*,
36 },
37 *,
38};
39use azul_css::css::BoxOrStatic;
40
41use crate::{
42 callbacks::{Callback, CallbackInfo},
43 widgets::{
44 check_box::{CheckBox, CheckBoxOnToggleCallbackType, CheckBoxState},
45 color_input::{ColorInput, ColorInputOnValueChangeCallbackType, ColorInputState},
46 file_input::{FileInput, FileInputOnPathChangeCallbackType, FileInputState},
47 number_input::{NumberInput, NumberInputOnFocusLostCallbackType, NumberInputState},
48 text_input::{TextInput, TextInputOnFocusLostCallbackType, TextInputState},
49 },
50};
51
52#[derive(Debug, Clone)]
54#[repr(C)]
55pub struct NodeGraph {
56 pub node_types: NodeTypeIdInfoMapVec,
57 pub input_output_types: InputOutputTypeIdInfoMapVec,
58 pub nodes: NodeIdNodeMapVec,
59 pub allow_multiple_root_nodes: bool,
60 pub offset: LogicalPosition,
61 pub style: NodeGraphStyle,
62 pub callbacks: NodeGraphCallbacks,
63 pub add_node_str: AzString,
64 pub scale_factor: f32,
65}
66
67impl Default for NodeGraph {
68 fn default() -> Self {
69 Self {
70 node_types: NodeTypeIdInfoMapVec::from_const_slice(&[]),
71 input_output_types: InputOutputTypeIdInfoMapVec::from_const_slice(&[]),
72 nodes: NodeIdNodeMapVec::from_const_slice(&[]),
73 allow_multiple_root_nodes: false,
74 offset: LogicalPosition::zero(),
75 style: NodeGraphStyle::Default,
76 callbacks: NodeGraphCallbacks::default(),
77 add_node_str: AzString::from_const_str(""),
78 scale_factor: 1.0,
79 }
80 }
81}
82
83impl NodeGraph {
84 #[must_use] pub fn generate_unique_node_id(&self) -> NodeGraphNodeId {
86 NodeGraphNodeId {
87 inner: self
88 .nodes
89 .iter()
90 .map(|i| i.node_id.inner)
91 .max()
92 .unwrap_or(0)
93 .saturating_add(1),
94 }
95 }
96}
97
98#[derive(Debug, Clone)]
100#[repr(C)]
101pub struct NodeTypeIdInfoMap {
102 pub node_type_id: NodeTypeId,
103 pub node_type_info: NodeTypeInfo,
104}
105
106impl_option!(NodeTypeIdInfoMap, OptionNodeTypeIdInfoMap, copy = false, [Debug, Clone]);
107impl_vec!(NodeTypeIdInfoMap, NodeTypeIdInfoMapVec, NodeTypeIdInfoMapVecDestructor, NodeTypeIdInfoMapVecDestructorType, NodeTypeIdInfoMapVecSlice, OptionNodeTypeIdInfoMap);
108impl_vec_clone!(
109 NodeTypeIdInfoMap,
110 NodeTypeIdInfoMapVec,
111 NodeTypeIdInfoMapVecDestructor
112);
113impl_vec_mut!(NodeTypeIdInfoMap, NodeTypeIdInfoMapVec);
114impl_vec_debug!(NodeTypeIdInfoMap, NodeTypeIdInfoMapVec);
115
116#[derive(Debug, Clone)]
118#[repr(C)]
119pub struct InputOutputTypeIdInfoMap {
120 pub io_type_id: InputOutputTypeId,
121 pub io_info: InputOutputInfo,
122}
123
124impl_option!(InputOutputTypeIdInfoMap, OptionInputOutputTypeIdInfoMap, copy = false, [Debug, Clone]);
125impl_vec!(InputOutputTypeIdInfoMap, InputOutputTypeIdInfoMapVec, InputOutputTypeIdInfoMapVecDestructor, InputOutputTypeIdInfoMapVecDestructorType, InputOutputTypeIdInfoMapVecSlice, OptionInputOutputTypeIdInfoMap);
126impl_vec_clone!(
127 InputOutputTypeIdInfoMap,
128 InputOutputTypeIdInfoMapVec,
129 InputOutputTypeIdInfoMapVecDestructor
130);
131impl_vec_mut!(InputOutputTypeIdInfoMap, InputOutputTypeIdInfoMapVec);
132impl_vec_debug!(InputOutputTypeIdInfoMap, InputOutputTypeIdInfoMapVec);
133
134#[derive(Debug, Clone)]
136#[repr(C)]
137pub struct NodeIdNodeMap {
138 pub node_id: NodeGraphNodeId,
139 pub node: Node,
140}
141
142impl_option!(NodeIdNodeMap, OptionNodeIdNodeMap, copy = false, [Debug, Clone]);
143impl_vec!(NodeIdNodeMap, NodeIdNodeMapVec, NodeIdNodeMapVecDestructor, NodeIdNodeMapVecDestructorType, NodeIdNodeMapVecSlice, OptionNodeIdNodeMap);
144impl_vec_clone!(NodeIdNodeMap, NodeIdNodeMapVec, NodeIdNodeMapVecDestructor);
145impl_vec_mut!(NodeIdNodeMap, NodeIdNodeMapVec);
146impl_vec_debug!(NodeIdNodeMap, NodeIdNodeMapVec);
147
148#[derive(Debug, Copy, Clone)]
149#[repr(C)]
150pub enum NodeGraphStyle {
151 Default,
152 }
154
155#[derive(Default, Debug, Clone)]
157#[repr(C)]
158pub struct NodeGraphCallbacks {
159 pub on_node_added: OptionOnNodeAdded,
160 pub on_node_removed: OptionOnNodeRemoved,
161 pub on_node_dragged: OptionOnNodeDragged,
162 pub on_node_graph_dragged: OptionOnNodeGraphDragged,
163 pub on_node_connected: OptionOnNodeConnected,
164 pub on_node_input_disconnected: OptionOnNodeInputDisconnected,
165 pub on_node_output_disconnected: OptionOnNodeOutputDisconnected,
166 pub on_node_field_edited: OptionOnNodeFieldEdited,
167}
168
169pub type OnNodeAddedCallbackType = extern "C" fn(
170 refany: RefAny,
171 info: CallbackInfo,
172 new_node_type: NodeTypeId,
173 new_node_id: NodeGraphNodeId,
174 new_node_position: NodeGraphNodePosition,
175) -> Update;
176impl_widget_callback!(
177 OnNodeAdded,
178 OptionOnNodeAdded,
179 OnNodeAddedCallback,
180 OnNodeAddedCallbackType
181);
182
183pub type OnNodeRemovedCallbackType =
184 extern "C" fn(refany: RefAny, info: CallbackInfo, node_id_to_remove: NodeGraphNodeId) -> Update;
185impl_widget_callback!(
186 OnNodeRemoved,
187 OptionOnNodeRemoved,
188 OnNodeRemovedCallback,
189 OnNodeRemovedCallbackType
190);
191
192pub type OnNodeGraphDraggedCallbackType =
193 extern "C" fn(refany: RefAny, info: CallbackInfo, drag_amount: GraphDragAmount) -> Update;
194impl_widget_callback!(
195 OnNodeGraphDragged,
196 OptionOnNodeGraphDragged,
197 OnNodeGraphDraggedCallback,
198 OnNodeGraphDraggedCallbackType
199);
200
201pub type OnNodeDraggedCallbackType = extern "C" fn(
202 refany: RefAny,
203 info: CallbackInfo,
204 node_dragged: NodeGraphNodeId,
205 drag_amount: NodeDragAmount,
206) -> Update;
207impl_widget_callback!(
208 OnNodeDragged,
209 OptionOnNodeDragged,
210 OnNodeDraggedCallback,
211 OnNodeDraggedCallbackType
212);
213
214pub type OnNodeConnectedCallbackType = extern "C" fn(
215 refany: RefAny,
216 info: CallbackInfo,
217 input: NodeGraphNodeId,
218 input_index: usize,
219 output: NodeGraphNodeId,
220 output_index: usize,
221) -> Update;
222impl_widget_callback!(
223 OnNodeConnected,
224 OptionOnNodeConnected,
225 OnNodeConnectedCallback,
226 OnNodeConnectedCallbackType
227);
228
229pub type OnNodeInputDisconnectedCallbackType = extern "C" fn(
230 refany: RefAny,
231 info: CallbackInfo,
232 input: NodeGraphNodeId,
233 input_index: usize,
234) -> Update;
235impl_widget_callback!(
236 OnNodeInputDisconnected,
237 OptionOnNodeInputDisconnected,
238 OnNodeInputDisconnectedCallback,
239 OnNodeInputDisconnectedCallbackType
240);
241
242pub type OnNodeOutputDisconnectedCallbackType = extern "C" fn(
243 refany: RefAny,
244 info: CallbackInfo,
245 output: NodeGraphNodeId,
246 output_index: usize,
247) -> Update;
248impl_widget_callback!(
249 OnNodeOutputDisconnected,
250 OptionOnNodeOutputDisconnected,
251 OnNodeOutputDisconnectedCallback,
252 OnNodeOutputDisconnectedCallbackType
253);
254
255pub type OnNodeFieldEditedCallbackType = extern "C" fn(
256 refany: RefAny,
257 info: CallbackInfo,
258 node_id: NodeGraphNodeId,
259 field_id: usize,
260 node_type: NodeTypeId,
261 new_value: NodeTypeFieldValue,
262) -> Update;
263impl_widget_callback!(
264 OnNodeFieldEdited,
265 OptionOnNodeFieldEdited,
266 OnNodeFieldEditedCallback,
267 OnNodeFieldEditedCallbackType
268);
269
270#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
272#[repr(C)]
273pub struct InputOutputTypeId {
274 pub inner: u64,
275}
276
277impl_option!(InputOutputTypeId, OptionInputOutputTypeId, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
278impl_vec!(InputOutputTypeId, InputOutputTypeIdVec, InputOutputTypeIdVecDestructor, InputOutputTypeIdVecDestructorType, InputOutputTypeIdVecSlice, OptionInputOutputTypeId);
279impl_vec_clone!(
280 InputOutputTypeId,
281 InputOutputTypeIdVec,
282 InputOutputTypeIdVecDestructor
283);
284impl_vec_mut!(InputOutputTypeId, InputOutputTypeIdVec);
285impl_vec_debug!(InputOutputTypeId, InputOutputTypeIdVec);
286
287#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
289#[repr(C)]
290pub struct NodeTypeId {
291 pub inner: u64,
292}
293
294#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
296#[repr(C)]
297pub struct NodeGraphNodeId {
298 pub inner: u64,
299}
300
301#[derive(Debug, Clone)]
303#[repr(C)]
304pub struct Node {
305 pub node_type: NodeTypeId,
306 pub position: NodeGraphNodePosition,
307 pub fields: NodeTypeFieldVec,
308 pub connect_in: InputConnectionVec,
309 pub connect_out: OutputConnectionVec,
310}
311
312#[derive(Debug, Clone)]
314#[repr(C)]
315pub struct NodeTypeField {
316 pub key: AzString,
317 pub value: NodeTypeFieldValue,
318}
319
320impl_option!(NodeTypeField, OptionNodeTypeField, copy = false, [Debug, Clone]);
321impl_vec!(NodeTypeField, NodeTypeFieldVec, NodeTypeFieldVecDestructor, NodeTypeFieldVecDestructorType, NodeTypeFieldVecSlice, OptionNodeTypeField);
322impl_vec_clone!(NodeTypeField, NodeTypeFieldVec, NodeTypeFieldVecDestructor);
323impl_vec_debug!(NodeTypeField, NodeTypeFieldVec);
324impl_vec_mut!(NodeTypeField, NodeTypeFieldVec);
325
326#[derive(Debug, Clone)]
328#[repr(C, u8)]
329pub enum NodeTypeFieldValue {
330 TextInput(AzString),
331 NumberInput(f32),
332 CheckBox(bool),
333 ColorInput(ColorU),
334 FileInput(OptionString),
335}
336
337#[derive(Debug, Clone)]
339#[repr(C)]
340pub struct InputConnection {
341 pub input_index: usize,
342 pub connects_to: OutputNodeAndIndexVec,
343}
344
345impl_option!(InputConnection, OptionInputConnection, copy = false, [Debug, Clone]);
346impl_vec!(InputConnection, InputConnectionVec, InputConnectionVecDestructor, InputConnectionVecDestructorType, InputConnectionVecSlice, OptionInputConnection);
347impl_vec_clone!(
348 InputConnection,
349 InputConnectionVec,
350 InputConnectionVecDestructor
351);
352impl_vec_debug!(InputConnection, InputConnectionVec);
353impl_vec_mut!(InputConnection, InputConnectionVec);
354
355#[derive(Copy, Debug, Clone)]
357#[repr(C)]
358pub struct OutputNodeAndIndex {
359 pub node_id: NodeGraphNodeId,
360 pub output_index: usize,
361}
362
363impl_option!(OutputNodeAndIndex, OptionOutputNodeAndIndex, copy = false, [Debug, Clone]);
364impl_vec!(OutputNodeAndIndex, OutputNodeAndIndexVec, OutputNodeAndIndexVecDestructor, OutputNodeAndIndexVecDestructorType, OutputNodeAndIndexVecSlice, OptionOutputNodeAndIndex);
365impl_vec_clone!(
366 OutputNodeAndIndex,
367 OutputNodeAndIndexVec,
368 OutputNodeAndIndexVecDestructor
369);
370impl_vec_debug!(OutputNodeAndIndex, OutputNodeAndIndexVec);
371impl_vec_mut!(OutputNodeAndIndex, OutputNodeAndIndexVec);
372
373#[derive(Debug, Clone)]
375#[repr(C)]
376pub struct OutputConnection {
377 pub output_index: usize,
378 pub connects_to: InputNodeAndIndexVec,
379}
380
381impl_option!(OutputConnection, OptionOutputConnection, copy = false, [Debug, Clone]);
382impl_vec!(OutputConnection, OutputConnectionVec, OutputConnectionVecDestructor, OutputConnectionVecDestructorType, OutputConnectionVecSlice, OptionOutputConnection);
383impl_vec_clone!(
384 OutputConnection,
385 OutputConnectionVec,
386 OutputConnectionVecDestructor
387);
388impl_vec_debug!(OutputConnection, OutputConnectionVec);
389impl_vec_mut!(OutputConnection, OutputConnectionVec);
390
391#[derive(Copy, Debug, Clone, PartialEq, Eq)]
393#[repr(C)]
394pub struct InputNodeAndIndex {
395 pub node_id: NodeGraphNodeId,
396 pub input_index: usize,
397}
398
399impl_option!(InputNodeAndIndex, OptionInputNodeAndIndex, copy = false, [Debug, Clone]);
400impl_vec!(InputNodeAndIndex, InputNodeAndIndexVec, InputNodeAndIndexVecDestructor, InputNodeAndIndexVecDestructorType, InputNodeAndIndexVecSlice, OptionInputNodeAndIndex);
401impl_vec_clone!(
402 InputNodeAndIndex,
403 InputNodeAndIndexVec,
404 InputNodeAndIndexVecDestructor
405);
406impl_vec_debug!(InputNodeAndIndex, InputNodeAndIndexVec);
407impl_vec_mut!(InputNodeAndIndex, InputNodeAndIndexVec);
408
409#[derive(Debug, Clone)]
411#[repr(C)]
412pub struct NodeTypeInfo {
413 pub is_root: bool,
415 pub node_type_name: AzString,
417 pub inputs: InputOutputTypeIdVec,
419 pub outputs: InputOutputTypeIdVec,
421}
422
423#[derive(Debug, Clone)]
425#[repr(C)]
426pub struct InputOutputInfo {
427 pub data_type: AzString,
429 pub color: ColorU,
431}
432
433#[derive(Debug, Copy, Clone)]
436#[repr(C)]
437pub struct NodeGraphNodePosition {
438 pub x: f32,
440 pub y: f32,
442}
443
444#[derive(Debug, Copy, Clone, PartialEq, Eq)]
445#[repr(C)]
446pub enum NodeGraphError {
447 NodeMimeTypeMismatch,
450 NodeInvalidIndex,
452 NodeInvalidNode,
454 NoRootNode,
456}
457
458impl fmt::Display for NodeGraphError {
459 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460 use self::NodeGraphError::{NodeMimeTypeMismatch, NodeInvalidIndex, NodeInvalidNode, NoRootNode};
461 match self {
462 NodeMimeTypeMismatch => write!(f, "MIME type mismatch"),
463 NodeInvalidIndex => write!(f, "Invalid node index"),
464 NodeInvalidNode => write!(f, "Invalid node"),
465 NoRootNode => write!(f, "No root node found"),
466 }
467 }
468}
469
470#[derive(Debug, Copy, Clone, PartialEq)]
472#[repr(C)]
473pub struct GraphDragAmount {
474 pub x: f32,
475 pub y: f32,
476}
477
478#[derive(Debug, Copy, Clone, PartialEq)]
480#[repr(C)]
481pub struct NodeDragAmount {
482 pub x: f32,
483 pub y: f32,
484}
485
486impl NodeGraph {
487 #[must_use]
488 pub fn swap_with_default(&mut self) -> Self {
489 let mut default = Self::default();
490 ::core::mem::swap(&mut default, self);
491 default
492 }
493
494 fn connect_input_output(
513 &mut self,
514 input_node_id: NodeGraphNodeId,
515 input_index: usize,
516 output_node_id: NodeGraphNodeId,
517 output_index: usize,
518 ) -> Result<(), NodeGraphError> {
519 self.verify_nodetype_match(output_node_id, output_index, input_node_id, input_index)?;
521
522 if let Some(input_node) = self
524 .nodes
525 .as_mut()
526 .iter_mut()
527 .find(|i| i.node_id == input_node_id)
528 {
529 if let Some(position) = input_node
530 .node
531 .connect_in
532 .as_ref()
533 .iter()
534 .position(|i| i.input_index == input_index)
535 {
536 input_node.node.connect_in.as_mut()[position]
537 .connects_to
538 .push(OutputNodeAndIndex {
539 node_id: output_node_id,
540 output_index,
541 });
542 } else {
543 input_node.node.connect_in.push(InputConnection {
544 input_index,
545 connects_to: vec![OutputNodeAndIndex {
546 node_id: output_node_id,
547 output_index,
548 }]
549 .into(),
550 });
551 }
552 } else {
553 return Err(NodeGraphError::NodeInvalidNode);
554 }
555
556 if let Some(output_node) = self
558 .nodes
559 .as_mut()
560 .iter_mut()
561 .find(|i| i.node_id == output_node_id)
562 {
563 if let Some(position) = output_node
564 .node
565 .connect_out
566 .as_ref()
567 .iter()
568 .position(|i| i.output_index == output_index)
569 {
570 output_node.node.connect_out.as_mut()[position]
571 .connects_to
572 .push(InputNodeAndIndex {
573 node_id: input_node_id,
574 input_index,
575 });
576 } else {
577 output_node.node.connect_out.push(OutputConnection {
578 output_index,
579 connects_to: vec![InputNodeAndIndex {
580 node_id: input_node_id,
581 input_index,
582 }]
583 .into(),
584 });
585 }
586 } else {
587 return Err(NodeGraphError::NodeInvalidNode);
588 }
589
590 Ok(())
591 }
592
593 fn disconnect_input(
610 &mut self,
611 input_node_id: NodeGraphNodeId,
612 input_index: usize,
613 ) -> Result<(), NodeGraphError> {
614 let output_connections = {
615 let input_node = self
616 .nodes
617 .as_ref()
618 .iter()
619 .find(|i| i.node_id == input_node_id)
620 .ok_or(NodeGraphError::NodeInvalidNode)?;
621
622 match input_node
623 .node
624 .connect_in
625 .iter()
626 .find(|i| i.input_index == input_index)
627 {
628 None => return Ok(()),
629 Some(s) => s.connects_to.clone(),
630 }
631 };
632
633 for OutputNodeAndIndex {
635 node_id,
636 output_index,
637 } in output_connections.as_ref()
638 {
639 let output_node_id = *node_id;
640 let output_index = *output_index;
641
642 self.verify_nodetype_match(
644 output_node_id,
645 output_index,
646 input_node_id,
647 input_index,
648 )?;
649
650 if let Some(input_node) = self
653 .nodes
654 .as_mut()
655 .iter_mut()
656 .find(|i| i.node_id == input_node_id)
657 {
658 if let Some(position) = input_node
659 .node
660 .connect_in
661 .iter()
662 .position(|i| i.input_index == input_index)
663 {
664 input_node.node.connect_in.remove(position);
665 }
666 } else {
667 return Err(NodeGraphError::NodeInvalidNode);
668 }
669
670 if let Some(output_node) = self
671 .nodes
672 .as_mut()
673 .iter_mut()
674 .find(|i| i.node_id == output_node_id)
675 {
676 if let Some(position) = output_node
677 .node
678 .connect_out
679 .iter()
680 .position(|i| i.output_index == output_index)
681 {
682 output_node.node.connect_out.remove(position);
683 }
684 } else {
685 return Err(NodeGraphError::NodeInvalidNode);
686 }
687 }
688
689 Ok(())
690 }
691
692 fn disconnect_output(
707 &mut self,
708 output_node_id: NodeGraphNodeId,
709 output_index: usize,
710 ) -> Result<(), NodeGraphError> {
711 let input_connections = {
712 let output_node = self
713 .nodes
714 .as_ref()
715 .iter()
716 .find(|i| i.node_id == output_node_id)
717 .ok_or(NodeGraphError::NodeInvalidNode)?;
718
719 match output_node
720 .node
721 .connect_out
722 .iter()
723 .find(|i| i.output_index == output_index)
724 {
725 None => return Ok(()),
726 Some(s) => s.connects_to.clone(),
727 }
728 };
729
730 for InputNodeAndIndex {
731 node_id,
732 input_index,
733 } in &input_connections
734 {
735 let input_node_id = *node_id;
736 let input_index = *input_index;
737
738 self.verify_nodetype_match(
740 output_node_id,
741 output_index,
742 input_node_id,
743 input_index,
744 )?;
745
746 if let Some(output_node) = self
747 .nodes
748 .as_mut()
749 .iter_mut()
750 .find(|i| i.node_id == output_node_id)
751 {
752 if let Some(position) = output_node
753 .node
754 .connect_out
755 .iter()
756 .position(|i| i.output_index == output_index)
757 {
758 output_node.node.connect_out.remove(position);
759 }
760 } else {
761 return Err(NodeGraphError::NodeInvalidNode);
762 }
763
764 if let Some(input_node) = self
765 .nodes
766 .as_mut()
767 .iter_mut()
768 .find(|i| i.node_id == input_node_id)
769 {
770 if let Some(position) = input_node
771 .node
772 .connect_in
773 .iter()
774 .position(|i| i.input_index == input_index)
775 {
776 input_node.node.connect_in.remove(position);
777 }
778 } else {
779 return Err(NodeGraphError::NodeInvalidNode);
780 }
781 }
782
783 Ok(())
784 }
785
786 fn verify_nodetype_match(
788 &self,
789 output_node_id: NodeGraphNodeId,
790 output_index: usize,
791 input_node_id: NodeGraphNodeId,
792 input_index: usize,
793 ) -> Result<(), NodeGraphError> {
794 let output_node = self
795 .nodes
796 .iter()
797 .find(|i| i.node_id == output_node_id)
798 .ok_or(NodeGraphError::NodeInvalidNode)?;
799
800 let output_node_type = self
801 .node_types
802 .iter()
803 .find(|i| i.node_type_id == output_node.node.node_type)
804 .ok_or(NodeGraphError::NodeInvalidNode)?;
805
806 let output_type = output_node_type
807 .node_type_info
808 .outputs
809 .as_ref()
810 .get(output_index)
811 .copied()
812 .ok_or(NodeGraphError::NodeInvalidIndex)?;
813
814 let input_node = self
815 .nodes
816 .iter()
817 .find(|i| i.node_id == input_node_id)
818 .ok_or(NodeGraphError::NodeInvalidNode)?;
819
820 let input_node_type = self
821 .node_types
822 .iter()
823 .find(|i| i.node_type_id == input_node.node.node_type)
824 .ok_or(NodeGraphError::NodeInvalidNode)?;
825
826 let input_type = input_node_type
827 .node_type_info
828 .inputs
829 .as_ref()
830 .get(input_index)
831 .copied()
832 .ok_or(NodeGraphError::NodeInvalidIndex)?;
833
834 if input_type != output_type {
836 return Err(NodeGraphError::NodeMimeTypeMismatch);
837 }
838
839 Ok(())
840 }
841
842 #[allow(clippy::too_many_lines)] #[must_use] pub fn dom(self) -> Dom {
844 static NODEGRAPH_CLASS: &[IdOrClass] = &[Class(AzString::from_const_str("nodegraph"))];
845
846 static NODEGRAPH_BACKGROUND: &[StyleBackgroundContent] = &[StyleBackgroundContent::Image(
847 AzString::from_const_str("nodegraph-background"),
848 )];
849
850 static NODEGRAPH_NODES_CONTAINER_CLASS: &[IdOrClass] =
851 &[Class(AzString::from_const_str("nodegraph-nodes-container"))];
852
853 static NODEGRAPH_NODES_CONTAINER_PROPS: &[CssPropertyWithConditions] = &[
854 CssPropertyWithConditions::simple(CssProperty::flex_grow(LayoutFlexGrow::const_new(1))),
855 CssPropertyWithConditions::simple(CssProperty::position(LayoutPosition::Absolute)),
856 ];
857
858 let nodegraph_wrapper_props = vec![
859 CssPropertyWithConditions::simple(CssProperty::overflow_x(LayoutOverflow::Hidden)),
860 CssPropertyWithConditions::simple(CssProperty::overflow_y(LayoutOverflow::Hidden)),
861 CssPropertyWithConditions::simple(CssProperty::flex_grow(LayoutFlexGrow::const_new(1))),
862 CssPropertyWithConditions::simple(CssProperty::background_content(
863 StyleBackgroundContentVec::from_const_slice(NODEGRAPH_BACKGROUND),
864 )),
865 CssPropertyWithConditions::simple(CssProperty::background_repeat(
866 vec![StyleBackgroundRepeat::PatternRepeat].into(),
867 )),
868 CssPropertyWithConditions::simple(CssProperty::background_position(
869 vec![StyleBackgroundPosition {
870 horizontal: BackgroundPositionHorizontal::Exact(PixelValue::const_px(0)),
871 vertical: BackgroundPositionVertical::Exact(PixelValue::const_px(0)),
872 }]
873 .into(),
874 )),
875 ];
876
877 let nodegraph_props = vec![
878 CssPropertyWithConditions::simple(CssProperty::overflow_x(LayoutOverflow::Hidden)),
879 CssPropertyWithConditions::simple(CssProperty::overflow_y(LayoutOverflow::Hidden)),
880 CssPropertyWithConditions::simple(CssProperty::flex_grow(LayoutFlexGrow::const_new(1))),
881 CssPropertyWithConditions::simple(CssProperty::position(LayoutPosition::Relative)),
882 ];
883
884 let node_connection_marker = RefAny::new(NodeConnectionMarkerDataset {});
885
886 let node_graph_local_dataset = RefAny::new(NodeGraphLocalDataset {
887 node_graph: self.clone(), last_input_or_output_clicked: None,
889 active_node_being_dragged: None,
890 node_connection_marker: node_connection_marker.clone(),
891 callbacks: self.callbacks.clone(),
892 });
893
894 let context_menu = Menu::create(
895 vec![MenuItem::String(
896 StringMenuItem::create(self.add_node_str.clone()).with_children(
897 self.node_types
898 .iter()
899 .map(
900 |NodeTypeIdInfoMap {
901 node_type_id,
902 node_type_info,
903 }| {
904 let context_menu_local_dataset =
905 RefAny::new(ContextMenuEntryLocalDataset {
906 node_type: *node_type_id,
907 backref: node_graph_local_dataset.clone(),
909 });
910
911 MenuItem::String(
912 StringMenuItem::create(
913 node_type_info.node_type_name.clone(),
914 )
915 .with_callback(
916 context_menu_local_dataset,
917 nodegraph_context_menu_click as usize,
918 ),
919 )
920 },
921 )
922 .collect::<Vec<_>>()
923 .into(),
924 ),
925 )]
926 .into(),
927 );
928
929 Dom::create_div()
930 .with_css_props(nodegraph_wrapper_props.into())
931 .with_context_menu(context_menu)
932 .with_children(
933 vec![Dom::create_div()
934 .with_ids_and_classes(IdOrClassVec::from_const_slice(NODEGRAPH_CLASS))
935 .with_css_props(nodegraph_props.into())
936 .with_callbacks(
937 vec![
938 CoreCallbackData {
939 event: EventFilter::Hover(HoverEventFilter::MouseOver),
940 refany: node_graph_local_dataset.clone(),
941 callback: CoreCallback {
942 cb: nodegraph_drag_graph_or_nodes as usize,
943 ctx: OptionRefAny::None,
944 },
945 },
946 CoreCallbackData {
947 event: EventFilter::Hover(HoverEventFilter::LeftMouseUp),
948 refany: node_graph_local_dataset.clone(),
949 callback: CoreCallback {
950 cb: nodegraph_unset_active_node as usize,
951 ctx: OptionRefAny::None,
952 },
953 },
954 ]
955 .into(),
956 )
957 .with_children({
958 vec![
959 render_connections(&self, node_connection_marker),
961 self.nodes
963 .iter()
964 .filter_map(|NodeIdNodeMap { node_id, node }| {
965 let node_type_info = self
966 .node_types
967 .iter()
968 .find(|i| i.node_type_id == node.node_type)?;
969 let node_local_dataset = NodeLocalDataset {
970 node_id: *node_id,
971 backref: node_graph_local_dataset.clone(),
972 };
973
974 Some(render_node(
975 node,
976 (self.offset.x, self.offset.y),
977 &node_type_info.node_type_info,
978 node_local_dataset,
979 self.scale_factor,
980 ))
981 })
982 .collect::<Dom>()
983 .with_ids_and_classes(IdOrClassVec::from_const_slice(
984 NODEGRAPH_NODES_CONTAINER_CLASS,
985 ))
986 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
987 NODEGRAPH_NODES_CONTAINER_PROPS,
988 )),
989 ]
990 .into()
991 })]
992 .into(),
993 )
994 .with_dataset(Some(node_graph_local_dataset).into())
995 }
996}
997
998struct NodeGraphLocalDataset {
1001 node_graph: NodeGraph,
1002 last_input_or_output_clicked: Option<(NodeGraphNodeId, InputOrOutput)>,
1003 active_node_being_dragged: Option<(NodeGraphNodeId, RefAny)>,
1005 node_connection_marker: RefAny, callbacks: NodeGraphCallbacks,
1007}
1008
1009struct ContextMenuEntryLocalDataset {
1010 node_type: NodeTypeId,
1011 backref: RefAny, }
1013
1014struct NodeConnectionMarkerDataset {}
1015
1016struct NodeLocalDataset {
1017 node_id: NodeGraphNodeId,
1018 backref: RefAny, }
1020
1021#[derive(Debug, Copy, Clone)]
1022enum InputOrOutput {
1023 Input(usize),
1024 Output(usize),
1025}
1026
1027struct NodeInputOutputLocalDataset {
1028 io_id: InputOrOutput,
1029 backref: RefAny, }
1031
1032struct NodeFieldLocalDataset {
1033 field_idx: usize,
1034 backref: RefAny, }
1036
1037#[derive(Copy, Clone)]
1038struct ConnectionLocalDataset {
1039 out_node_id: NodeGraphNodeId,
1040 out_idx: usize,
1041 in_node_id: NodeGraphNodeId,
1042 in_idx: usize,
1043 swap_vert: bool,
1044 swap_horz: bool,
1045 color: ColorU,
1046}
1047
1048#[allow(clippy::float_cmp)] #[allow(clippy::too_many_lines)] fn render_node(
1051 node: &Node,
1052 graph_offset: (f32, f32),
1053 node_info: &NodeTypeInfo,
1054 mut node_local_dataset: NodeLocalDataset,
1055 scale_factor: f32,
1056) -> Dom {
1057 use azul_core::dom::{
1058 CssPropertyWithConditions, CssPropertyWithConditionsVec, Dom, DomVec, IdOrClass,
1059 IdOrClass::Class, IdOrClassVec,
1060 };
1061 #[allow(clippy::wildcard_imports)] use azul_css::*;
1063
1064 const STRING_9416190750059025162: AzString = AzString::from_const_str("Material Icons");
1065 const STRING_16146701490593874959: AzString = AzString::from_const_str("system:ui");
1066 const STYLE_BACKGROUND_CONTENT_524016094839686509_ITEMS: &[StyleBackgroundContent] =
1067 &[StyleBackgroundContent::Color(ColorU {
1068 r: 34,
1069 g: 34,
1070 b: 34,
1071 a: 255,
1072 })];
1073 const STYLE_BACKGROUND_CONTENT_10430246856047584562_ITEMS: &[StyleBackgroundContent] =
1074 &[StyleBackgroundContent::LinearGradient(LinearGradient {
1075 direction: Direction::FromTo(DirectionCorners {
1076 dir_from: DirectionCorner::Left,
1077 dir_to: DirectionCorner::Right,
1078 }),
1079 extend_mode: ExtendMode::Clamp,
1080 stops: NormalizedLinearColorStopVec::from_const_slice(
1081 LINEAR_COLOR_STOP_4373556077110009258_ITEMS,
1082 ),
1083 })];
1084 const STYLE_BACKGROUND_CONTENT_11535310356736632656_ITEMS: &[StyleBackgroundContent] =
1085 &[StyleBackgroundContent::RadialGradient(RadialGradient {
1086 shape: Shape::Ellipse,
1087 extend_mode: ExtendMode::Clamp,
1088 position: StyleBackgroundPosition {
1089 horizontal: BackgroundPositionHorizontal::Left,
1090 vertical: BackgroundPositionVertical::Top,
1091 },
1092 size: RadialGradientSize::FarthestCorner,
1093 stops: NormalizedLinearColorStopVec::from_const_slice(
1094 LINEAR_COLOR_STOP_15596411095679453272_ITEMS,
1095 ),
1096 })];
1097 const STYLE_BACKGROUND_CONTENT_11936041127084538304_ITEMS: &[StyleBackgroundContent] =
1098 &[StyleBackgroundContent::LinearGradient(LinearGradient {
1099 direction: Direction::FromTo(DirectionCorners {
1100 dir_from: DirectionCorner::Right,
1101 dir_to: DirectionCorner::Left,
1102 }),
1103 extend_mode: ExtendMode::Clamp,
1104 stops: NormalizedLinearColorStopVec::from_const_slice(
1105 LINEAR_COLOR_STOP_4373556077110009258_ITEMS,
1106 ),
1107 })];
1108 const STYLE_BACKGROUND_CONTENT_15813232491335471489_ITEMS: &[StyleBackgroundContent] =
1109 &[StyleBackgroundContent::Color(ColorU {
1110 r: 0,
1111 g: 0,
1112 b: 0,
1113 a: 85,
1114 })];
1115 const STYLE_BACKGROUND_CONTENT_17648039690071193942_ITEMS: &[StyleBackgroundContent] =
1116 &[StyleBackgroundContent::LinearGradient(LinearGradient {
1117 direction: Direction::FromTo(DirectionCorners {
1118 dir_from: DirectionCorner::Top,
1119 dir_to: DirectionCorner::Bottom,
1120 }),
1121 extend_mode: ExtendMode::Clamp,
1122 stops: NormalizedLinearColorStopVec::from_const_slice(
1123 LINEAR_COLOR_STOP_7397113864565941600_ITEMS,
1124 ),
1125 })];
1126 const STYLE_TRANSFORM_347117342922946953_ITEMS: &[StyleTransform] =
1127 &[StyleTransform::Translate(StyleTransformTranslate2D {
1128 x: PixelValue::const_px(200),
1129 y: PixelValue::const_px(100),
1130 })];
1131 const STYLE_TRANSFORM_14683950870521466298_ITEMS: &[StyleTransform] =
1132 &[StyleTransform::Translate(StyleTransformTranslate2D {
1133 x: PixelValue::const_px(240),
1134 y: PixelValue::const_px(-10),
1135 })];
1136 const STYLE_FONT_FAMILY_8122988506401935406_ITEMS: &[StyleFontFamily] =
1137 &[StyleFontFamily::System(STRING_16146701490593874959)];
1138 const STYLE_FONT_FAMILY_11383897783350685780_ITEMS: &[StyleFontFamily] =
1139 &[StyleFontFamily::System(STRING_9416190750059025162)];
1140 const LINEAR_COLOR_STOP_4373556077110009258_ITEMS: &[NormalizedLinearColorStop] = &[
1141 NormalizedLinearColorStop {
1142 offset: PercentageValue::const_new(20),
1143 color: ColorOrSystem::color(ColorU {
1144 r: 0,
1145 g: 0,
1146 b: 0,
1147 a: 204,
1148 }),
1149 },
1150 NormalizedLinearColorStop {
1151 offset: PercentageValue::const_new(100),
1152 color: ColorOrSystem::color(ColorU {
1153 r: 0,
1154 g: 0,
1155 b: 0,
1156 a: 0,
1157 }),
1158 },
1159 ];
1160 const LINEAR_COLOR_STOP_7397113864565941600_ITEMS: &[NormalizedLinearColorStop] = &[
1161 NormalizedLinearColorStop {
1162 offset: PercentageValue::const_new(0),
1163 color: ColorOrSystem::color(ColorU {
1164 r: 229,
1165 g: 57,
1166 b: 53,
1167 a: 255,
1168 }),
1169 },
1170 NormalizedLinearColorStop {
1171 offset: PercentageValue::const_new(100),
1172 color: ColorOrSystem::color(ColorU {
1173 r: 227,
1174 g: 93,
1175 b: 91,
1176 a: 255,
1177 }),
1178 },
1179 ];
1180 const LINEAR_COLOR_STOP_15596411095679453272_ITEMS: &[NormalizedLinearColorStop] = &[
1181 NormalizedLinearColorStop {
1182 offset: PercentageValue::const_new(0),
1183 color: ColorOrSystem::color(ColorU {
1184 r: 47,
1185 g: 49,
1186 b: 54,
1187 a: 255,
1188 }),
1189 },
1190 NormalizedLinearColorStop {
1191 offset: PercentageValue::const_new(50),
1192 color: ColorOrSystem::color(ColorU {
1193 r: 47,
1194 g: 49,
1195 b: 54,
1196 a: 255,
1197 }),
1198 },
1199 NormalizedLinearColorStop {
1200 offset: PercentageValue::const_new(100),
1201 color: ColorOrSystem::color(ColorU {
1202 r: 32,
1203 g: 34,
1204 b: 37,
1205 a: 255,
1206 }),
1207 },
1208 ];
1209
1210 const CSS_MATCH_10339190304804100510_PROPERTIES: &[CssPropertyWithConditions] = &[
1211 CssPropertyWithConditions::simple(CssProperty::Display(LayoutDisplayValue::Exact(
1213 LayoutDisplay::Flex,
1214 ))),
1215 CssPropertyWithConditions::simple(CssProperty::FlexDirection(
1216 LayoutFlexDirectionValue::Exact(LayoutFlexDirection::Column),
1217 )),
1218 CssPropertyWithConditions::simple(CssProperty::Left(LayoutLeftValue::Exact(LayoutLeft {
1219 inner: PixelValue::const_px(0),
1220 }))),
1221 CssPropertyWithConditions::simple(CssProperty::OverflowX(LayoutOverflowValue::Exact(
1222 LayoutOverflow::Visible,
1223 ))),
1224 CssPropertyWithConditions::simple(CssProperty::OverflowY(LayoutOverflowValue::Exact(
1225 LayoutOverflow::Visible,
1226 ))),
1227 CssPropertyWithConditions::simple(CssProperty::Position(LayoutPositionValue::Exact(
1228 LayoutPosition::Absolute,
1229 ))),
1230 ];
1231 const CSS_MATCH_10339190304804100510: CssPropertyWithConditionsVec =
1232 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_10339190304804100510_PROPERTIES);
1233
1234 const CSS_MATCH_11452431279102104133_PROPERTIES: &[CssPropertyWithConditions] = &[
1235 CssPropertyWithConditions::simple(CssProperty::FontFamily(StyleFontFamilyVecValue::Exact(
1237 StyleFontFamilyVec::from_const_slice(STYLE_FONT_FAMILY_8122988506401935406_ITEMS),
1238 ))),
1239 CssPropertyWithConditions::simple(CssProperty::FontSize(StyleFontSizeValue::Exact(
1240 StyleFontSize {
1241 inner: PixelValue::const_px(12),
1242 },
1243 ))),
1244 CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
1245 LayoutHeight::Px(PixelValue::const_px(15)),
1246 ))),
1247 CssPropertyWithConditions::simple(CssProperty::TextAlign(StyleTextAlignValue::Exact(
1248 StyleTextAlign::Right,
1249 ))),
1250 CssPropertyWithConditions::simple(CssProperty::Width(LayoutWidthValue::Exact(
1251 LayoutWidth::Px(PixelValue::const_px(100)),
1252 ))),
1253 ];
1254 const CSS_MATCH_11452431279102104133: CssPropertyWithConditionsVec =
1255 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_11452431279102104133_PROPERTIES);
1256
1257 const CSS_MATCH_1173826950760010563_PROPERTIES: &[CssPropertyWithConditions] = &[
1258 CssPropertyWithConditions::simple(CssProperty::BorderTopColor(
1260 StyleBorderTopColorValue::Exact(StyleBorderTopColor {
1261 inner: ColorU {
1262 r: 0,
1263 g: 131,
1264 b: 176,
1265 a: 119,
1266 },
1267 }),
1268 )),
1269 CssPropertyWithConditions::simple(CssProperty::BorderRightColor(
1270 StyleBorderRightColorValue::Exact(StyleBorderRightColor {
1271 inner: ColorU {
1272 r: 0,
1273 g: 131,
1274 b: 176,
1275 a: 119,
1276 },
1277 }),
1278 )),
1279 CssPropertyWithConditions::simple(CssProperty::BorderLeftColor(
1280 StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
1281 inner: ColorU {
1282 r: 0,
1283 g: 131,
1284 b: 176,
1285 a: 119,
1286 },
1287 }),
1288 )),
1289 CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
1290 StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
1291 inner: ColorU {
1292 r: 0,
1293 g: 131,
1294 b: 176,
1295 a: 119,
1296 },
1297 }),
1298 )),
1299 CssPropertyWithConditions::simple(CssProperty::BorderTopStyle(
1300 StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
1301 inner: BorderStyle::Solid,
1302 }),
1303 )),
1304 CssPropertyWithConditions::simple(CssProperty::BorderRightStyle(
1305 StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
1306 inner: BorderStyle::Solid,
1307 }),
1308 )),
1309 CssPropertyWithConditions::simple(CssProperty::BorderLeftStyle(
1310 StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
1311 inner: BorderStyle::Solid,
1312 }),
1313 )),
1314 CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
1315 StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
1316 inner: BorderStyle::Solid,
1317 }),
1318 )),
1319 CssPropertyWithConditions::simple(CssProperty::BorderTopWidth(
1320 LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
1321 inner: PixelValue::const_px(1),
1322 }),
1323 )),
1324 CssPropertyWithConditions::simple(CssProperty::BorderRightWidth(
1325 LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
1326 inner: PixelValue::const_px(1),
1327 }),
1328 )),
1329 CssPropertyWithConditions::simple(CssProperty::BorderLeftWidth(
1330 LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
1331 inner: PixelValue::const_px(1),
1332 }),
1333 )),
1334 CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
1335 LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
1336 inner: PixelValue::const_px(1),
1337 }),
1338 )),
1339 CssPropertyWithConditions::simple(CssProperty::AlignItems(LayoutAlignItemsValue::Exact(
1341 LayoutAlignItems::Center,
1342 ))),
1343 CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
1344 StyleBackgroundContentVecValue::Exact(StyleBackgroundContentVec::from_const_slice(
1345 STYLE_BACKGROUND_CONTENT_524016094839686509_ITEMS,
1346 )),
1347 )),
1348 CssPropertyWithConditions::simple(CssProperty::BorderTopColor(
1349 StyleBorderTopColorValue::Exact(StyleBorderTopColor {
1350 inner: ColorU {
1351 r: 54,
1352 g: 57,
1353 b: 63,
1354 a: 255,
1355 },
1356 }),
1357 )),
1358 CssPropertyWithConditions::simple(CssProperty::BorderRightColor(
1359 StyleBorderRightColorValue::Exact(StyleBorderRightColor {
1360 inner: ColorU {
1361 r: 54,
1362 g: 57,
1363 b: 63,
1364 a: 255,
1365 },
1366 }),
1367 )),
1368 CssPropertyWithConditions::simple(CssProperty::BorderLeftColor(
1369 StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
1370 inner: ColorU {
1371 r: 54,
1372 g: 57,
1373 b: 63,
1374 a: 255,
1375 },
1376 }),
1377 )),
1378 CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
1379 StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
1380 inner: ColorU {
1381 r: 54,
1382 g: 57,
1383 b: 63,
1384 a: 255,
1385 },
1386 }),
1387 )),
1388 CssPropertyWithConditions::simple(CssProperty::BorderTopStyle(
1389 StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
1390 inner: BorderStyle::Solid,
1391 }),
1392 )),
1393 CssPropertyWithConditions::simple(CssProperty::BorderRightStyle(
1394 StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
1395 inner: BorderStyle::Solid,
1396 }),
1397 )),
1398 CssPropertyWithConditions::simple(CssProperty::BorderLeftStyle(
1399 StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
1400 inner: BorderStyle::Solid,
1401 }),
1402 )),
1403 CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
1404 StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
1405 inner: BorderStyle::Solid,
1406 }),
1407 )),
1408 CssPropertyWithConditions::simple(CssProperty::BorderTopWidth(
1409 LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
1410 inner: PixelValue::const_px(1),
1411 }),
1412 )),
1413 CssPropertyWithConditions::simple(CssProperty::BorderRightWidth(
1414 LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
1415 inner: PixelValue::const_px(1),
1416 }),
1417 )),
1418 CssPropertyWithConditions::simple(CssProperty::BorderLeftWidth(
1419 LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
1420 inner: PixelValue::const_px(1),
1421 }),
1422 )),
1423 CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
1424 LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
1425 inner: PixelValue::const_px(1),
1426 }),
1427 )),
1428 CssPropertyWithConditions::simple(CssProperty::FlexGrow(LayoutFlexGrowValue::Exact(
1429 LayoutFlexGrow {
1430 inner: FloatValue::const_new(1),
1431 },
1432 ))),
1433 CssPropertyWithConditions::simple(CssProperty::TextAlign(StyleTextAlignValue::Exact(
1434 StyleTextAlign::Left,
1435 ))),
1436 ];
1437 const CSS_MATCH_1173826950760010563: CssPropertyWithConditionsVec =
1438 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_1173826950760010563_PROPERTIES);
1439
1440 const CSS_MATCH_1198521124955124418_PROPERTIES: &[CssPropertyWithConditions] = &[
1441 CssPropertyWithConditions::simple(CssProperty::AlignItems(LayoutAlignItemsValue::Exact(
1443 LayoutAlignItems::Center,
1444 ))),
1445 CssPropertyWithConditions::simple(CssProperty::FlexGrow(LayoutFlexGrowValue::Exact(
1446 LayoutFlexGrow {
1447 inner: FloatValue::const_new(1),
1448 },
1449 ))),
1450 CssPropertyWithConditions::simple(CssProperty::MaxWidth(LayoutMaxWidthValue::Exact(
1451 LayoutMaxWidth {
1452 inner: PixelValue::const_px(120),
1453 },
1454 ))),
1455 CssPropertyWithConditions::simple(CssProperty::PaddingLeft(LayoutPaddingLeftValue::Exact(
1456 LayoutPaddingLeft {
1457 inner: PixelValue::const_px(10),
1458 },
1459 ))),
1460 CssPropertyWithConditions::simple(CssProperty::TextAlign(StyleTextAlignValue::Exact(
1461 StyleTextAlign::Left,
1462 ))),
1463 ];
1464 const CSS_MATCH_1198521124955124418: CssPropertyWithConditionsVec =
1465 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_1198521124955124418_PROPERTIES);
1466
1467 const CSS_MATCH_12038890904436132038_PROPERTIES: &[CssPropertyWithConditions] = &[
1468 CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
1470 StyleBackgroundContentVecValue::Exact(StyleBackgroundContentVec::from_const_slice(
1471 STYLE_BACKGROUND_CONTENT_10430246856047584562_ITEMS,
1472 )),
1473 )),
1474 CssPropertyWithConditions::simple(CssProperty::PaddingLeft(LayoutPaddingLeftValue::Exact(
1475 LayoutPaddingLeft {
1476 inner: PixelValue::const_px(5),
1477 },
1478 ))),
1479 ];
1480 const CSS_MATCH_12038890904436132038: CssPropertyWithConditionsVec =
1481 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_12038890904436132038_PROPERTIES);
1482
1483 const CSS_MATCH_12400244273289328300_PROPERTIES: &[CssPropertyWithConditions] = &[
1484 CssPropertyWithConditions::simple(CssProperty::Display(LayoutDisplayValue::Exact(
1486 LayoutDisplay::Flex,
1487 ))),
1488 CssPropertyWithConditions::simple(CssProperty::FlexDirection(
1489 LayoutFlexDirectionValue::Exact(LayoutFlexDirection::Row),
1490 )),
1491 CssPropertyWithConditions::simple(CssProperty::MarginTop(LayoutMarginTopValue::Exact(
1492 LayoutMarginTop {
1493 inner: PixelValue::const_px(10),
1494 },
1495 ))),
1496 ];
1497 const CSS_MATCH_12400244273289328300: CssPropertyWithConditionsVec =
1498 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_12400244273289328300_PROPERTIES);
1499
1500 const CSS_MATCH_14906563417280941890_PROPERTIES: &[CssPropertyWithConditions] = &[
1501 CssPropertyWithConditions::simple(CssProperty::FlexGrow(LayoutFlexGrowValue::Exact(
1503 LayoutFlexGrow {
1504 inner: FloatValue::const_new(0),
1505 },
1506 ))),
1507 CssPropertyWithConditions::simple(CssProperty::OverflowX(LayoutOverflowValue::Exact(
1508 LayoutOverflow::Visible,
1509 ))),
1510 CssPropertyWithConditions::simple(CssProperty::OverflowY(LayoutOverflowValue::Exact(
1511 LayoutOverflow::Visible,
1512 ))),
1513 CssPropertyWithConditions::simple(CssProperty::Position(LayoutPositionValue::Exact(
1514 LayoutPosition::Relative,
1515 ))),
1516 CssPropertyWithConditions::simple(CssProperty::Width(LayoutWidthValue::Exact(
1517 LayoutWidth::Px(PixelValue::const_px(0)),
1518 ))),
1519 ];
1520 const CSS_MATCH_14906563417280941890: CssPropertyWithConditionsVec =
1521 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_14906563417280941890_PROPERTIES);
1522
1523 const CSS_MATCH_16946967739775705757_PROPERTIES: &[CssPropertyWithConditions] = &[
1524 CssPropertyWithConditions::simple(CssProperty::FlexGrow(LayoutFlexGrowValue::Exact(
1526 LayoutFlexGrow {
1527 inner: FloatValue::const_new(0),
1528 },
1529 ))),
1530 CssPropertyWithConditions::simple(CssProperty::OverflowX(LayoutOverflowValue::Exact(
1531 LayoutOverflow::Visible,
1532 ))),
1533 CssPropertyWithConditions::simple(CssProperty::OverflowY(LayoutOverflowValue::Exact(
1534 LayoutOverflow::Visible,
1535 ))),
1536 CssPropertyWithConditions::simple(CssProperty::Position(LayoutPositionValue::Exact(
1537 LayoutPosition::Relative,
1538 ))),
1539 CssPropertyWithConditions::simple(CssProperty::Width(LayoutWidthValue::Exact(
1540 LayoutWidth::Px(PixelValue::const_px(0)),
1541 ))),
1542 ];
1543 const CSS_MATCH_16946967739775705757: CssPropertyWithConditionsVec =
1544 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_16946967739775705757_PROPERTIES);
1545
1546 const CSS_MATCH_1739273067404038547_PROPERTIES: &[CssPropertyWithConditions] = &[
1547 CssPropertyWithConditions::simple(CssProperty::FontSize(StyleFontSizeValue::Exact(
1549 StyleFontSize {
1550 inner: PixelValue::const_px(18),
1551 },
1552 ))),
1553 CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
1554 LayoutHeight::Px(PixelValue::const_px(50)),
1555 ))),
1556 CssPropertyWithConditions::simple(CssProperty::PaddingLeft(LayoutPaddingLeftValue::Exact(
1557 LayoutPaddingLeft {
1558 inner: PixelValue::const_px(5),
1559 },
1560 ))),
1561 CssPropertyWithConditions::simple(CssProperty::PaddingTop(LayoutPaddingTopValue::Exact(
1562 LayoutPaddingTop {
1563 inner: PixelValue::const_px(10),
1564 },
1565 ))),
1566 ];
1567 const CSS_MATCH_1739273067404038547: CssPropertyWithConditionsVec =
1568 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_1739273067404038547_PROPERTIES);
1569
1570 const CSS_MATCH_2008162367868363199_PROPERTIES: &[CssPropertyWithConditions] = &[
1571 CssPropertyWithConditions::simple(CssProperty::FontFamily(StyleFontFamilyVecValue::Exact(
1573 StyleFontFamilyVec::from_const_slice(STYLE_FONT_FAMILY_8122988506401935406_ITEMS),
1574 ))),
1575 CssPropertyWithConditions::simple(CssProperty::FontSize(StyleFontSizeValue::Exact(
1576 StyleFontSize {
1577 inner: PixelValue::const_px(12),
1578 },
1579 ))),
1580 CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
1581 LayoutHeight::Px(PixelValue::const_px(15)),
1582 ))),
1583 CssPropertyWithConditions::simple(CssProperty::TextAlign(StyleTextAlignValue::Exact(
1584 StyleTextAlign::Left,
1585 ))),
1586 CssPropertyWithConditions::simple(CssProperty::Width(LayoutWidthValue::Exact(
1587 LayoutWidth::Px(PixelValue::const_px(100)),
1588 ))),
1589 ];
1590 const CSS_MATCH_2008162367868363199: CssPropertyWithConditionsVec =
1591 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_2008162367868363199_PROPERTIES);
1592
1593 const CSS_MATCH_2639191696846875011_PROPERTIES: &[CssPropertyWithConditions] = &[
1594 CssPropertyWithConditions::simple(CssProperty::FlexDirection(
1596 LayoutFlexDirectionValue::Exact(LayoutFlexDirection::Column),
1597 )),
1598 CssPropertyWithConditions::simple(CssProperty::PaddingTop(LayoutPaddingTopValue::Exact(
1599 LayoutPaddingTop {
1600 inner: PixelValue::const_px(3),
1601 },
1602 ))),
1603 CssPropertyWithConditions::simple(CssProperty::PaddingBottom(
1604 LayoutPaddingBottomValue::Exact(LayoutPaddingBottom {
1605 inner: PixelValue::const_px(3),
1606 }),
1607 )),
1608 CssPropertyWithConditions::simple(CssProperty::PaddingLeft(LayoutPaddingLeftValue::Exact(
1609 LayoutPaddingLeft {
1610 inner: PixelValue::const_px(5),
1611 },
1612 ))),
1613 CssPropertyWithConditions::simple(CssProperty::PaddingRight(
1614 LayoutPaddingRightValue::Exact(LayoutPaddingRight {
1615 inner: PixelValue::const_px(5),
1616 }),
1617 )),
1618 ];
1619 const CSS_MATCH_2639191696846875011: CssPropertyWithConditionsVec =
1620 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_2639191696846875011_PROPERTIES);
1621
1622 const CSS_MATCH_3354247437065914166_PROPERTIES: &[CssPropertyWithConditions] = &[
1623 CssPropertyWithConditions::simple(CssProperty::FlexDirection(
1625 LayoutFlexDirectionValue::Exact(LayoutFlexDirection::Row),
1626 )),
1627 CssPropertyWithConditions::simple(CssProperty::Position(LayoutPositionValue::Exact(
1628 LayoutPosition::Relative,
1629 ))),
1630 ];
1631 const CSS_MATCH_3354247437065914166: CssPropertyWithConditionsVec =
1632 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_3354247437065914166_PROPERTIES);
1633
1634 const CSS_MATCH_4700400755767504372_PROPERTIES: &[CssPropertyWithConditions] = &[
1635 CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
1637 StyleBackgroundContentVecValue::Exact(StyleBackgroundContentVec::from_const_slice(
1638 STYLE_BACKGROUND_CONTENT_11936041127084538304_ITEMS,
1639 )),
1640 )),
1641 CssPropertyWithConditions::simple(CssProperty::PaddingRight(
1642 LayoutPaddingRightValue::Exact(LayoutPaddingRight {
1643 inner: PixelValue::const_px(5),
1644 }),
1645 )),
1646 ];
1647 const CSS_MATCH_4700400755767504372: CssPropertyWithConditionsVec =
1648 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_4700400755767504372_PROPERTIES);
1649
1650 const CSS_MATCH_705881630351954657_PROPERTIES: &[CssPropertyWithConditions] = &[
1651 CssPropertyWithConditions::simple(CssProperty::Display(LayoutDisplayValue::Exact(
1653 LayoutDisplay::Flex,
1654 ))),
1655 CssPropertyWithConditions::simple(CssProperty::FlexDirection(
1656 LayoutFlexDirectionValue::Exact(LayoutFlexDirection::Column),
1657 )),
1658 CssPropertyWithConditions::simple(CssProperty::OverflowX(LayoutOverflowValue::Exact(
1659 LayoutOverflow::Visible,
1660 ))),
1661 CssPropertyWithConditions::simple(CssProperty::OverflowY(LayoutOverflowValue::Exact(
1662 LayoutOverflow::Visible,
1663 ))),
1664 CssPropertyWithConditions::simple(CssProperty::Position(LayoutPositionValue::Exact(
1665 LayoutPosition::Absolute,
1666 ))),
1667 CssPropertyWithConditions::simple(CssProperty::Right(LayoutRightValue::Exact(
1668 LayoutRight {
1669 inner: PixelValue::const_px(0),
1670 },
1671 ))),
1672 ];
1673 const CSS_MATCH_705881630351954657: CssPropertyWithConditionsVec =
1674 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_705881630351954657_PROPERTIES);
1675
1676 const CSS_MATCH_7395766480280098891_PROPERTIES: &[CssPropertyWithConditions] = &[
1677 CssPropertyWithConditions::simple(CssProperty::AlignItems(LayoutAlignItemsValue::Exact(
1679 LayoutAlignItems::Center,
1680 ))),
1681 CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
1682 StyleBackgroundContentVecValue::Exact(StyleBackgroundContentVec::from_const_slice(
1683 STYLE_BACKGROUND_CONTENT_17648039690071193942_ITEMS,
1684 )),
1685 )),
1686 CssPropertyWithConditions::simple(CssProperty::BorderTopColor(
1687 StyleBorderTopColorValue::Exact(StyleBorderTopColor {
1688 inner: ColorU {
1689 r: 255,
1690 g: 255,
1691 b: 255,
1692 a: 153,
1693 },
1694 }),
1695 )),
1696 CssPropertyWithConditions::simple(CssProperty::BorderRightColor(
1697 StyleBorderRightColorValue::Exact(StyleBorderRightColor {
1698 inner: ColorU {
1699 r: 255,
1700 g: 255,
1701 b: 255,
1702 a: 153,
1703 },
1704 }),
1705 )),
1706 CssPropertyWithConditions::simple(CssProperty::BorderLeftColor(
1707 StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
1708 inner: ColorU {
1709 r: 255,
1710 g: 255,
1711 b: 255,
1712 a: 153,
1713 },
1714 }),
1715 )),
1716 CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
1717 StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
1718 inner: ColorU {
1719 r: 255,
1720 g: 255,
1721 b: 255,
1722 a: 153,
1723 },
1724 }),
1725 )),
1726 CssPropertyWithConditions::simple(CssProperty::BorderTopStyle(
1727 StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
1728 inner: BorderStyle::Solid,
1729 }),
1730 )),
1731 CssPropertyWithConditions::simple(CssProperty::BorderRightStyle(
1732 StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
1733 inner: BorderStyle::Solid,
1734 }),
1735 )),
1736 CssPropertyWithConditions::simple(CssProperty::BorderLeftStyle(
1737 StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
1738 inner: BorderStyle::Solid,
1739 }),
1740 )),
1741 CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
1742 StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
1743 inner: BorderStyle::Solid,
1744 }),
1745 )),
1746 CssPropertyWithConditions::simple(CssProperty::BorderTopWidth(
1747 LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
1748 inner: PixelValue::const_px(1),
1749 }),
1750 )),
1751 CssPropertyWithConditions::simple(CssProperty::BorderRightWidth(
1752 LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
1753 inner: PixelValue::const_px(1),
1754 }),
1755 )),
1756 CssPropertyWithConditions::simple(CssProperty::BorderLeftWidth(
1757 LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
1758 inner: PixelValue::const_px(1),
1759 }),
1760 )),
1761 CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
1762 LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
1763 inner: PixelValue::const_px(1),
1764 }),
1765 )),
1766 CssPropertyWithConditions::simple(CssProperty::BoxShadowLeft(StyleBoxShadowValue::Exact(BoxOrStatic::Static(&
1767 StyleBoxShadow {
1768 offset_x: PixelValueNoPercent {
1769 inner: PixelValue::const_px(0),
1770 },
1771 offset_y: PixelValueNoPercent {
1772 inner: PixelValue::const_px(0),
1773 },
1774 color: ColorU {
1775 r: 229,
1776 g: 57,
1777 b: 53,
1778 a: 255,
1779 },
1780 blur_radius: PixelValueNoPercent {
1781 inner: PixelValue::const_px(2),
1782 },
1783 spread_radius: PixelValueNoPercent {
1784 inner: PixelValue::const_px(0),
1785 },
1786 clip_mode: BoxShadowClipMode::Outset,
1787 },
1788 )))),
1789 CssPropertyWithConditions::simple(CssProperty::BoxShadowRight(StyleBoxShadowValue::Exact(BoxOrStatic::Static(&
1790 StyleBoxShadow {
1791 offset_x: PixelValueNoPercent {
1792 inner: PixelValue::const_px(0),
1793 },
1794 offset_y: PixelValueNoPercent {
1795 inner: PixelValue::const_px(0),
1796 },
1797 color: ColorU {
1798 r: 229,
1799 g: 57,
1800 b: 53,
1801 a: 255,
1802 },
1803 blur_radius: PixelValueNoPercent {
1804 inner: PixelValue::const_px(2),
1805 },
1806 spread_radius: PixelValueNoPercent {
1807 inner: PixelValue::const_px(0),
1808 },
1809 clip_mode: BoxShadowClipMode::Outset,
1810 },
1811 )))),
1812 CssPropertyWithConditions::simple(CssProperty::BoxShadowTop(StyleBoxShadowValue::Exact(BoxOrStatic::Static(&
1813 StyleBoxShadow {
1814 offset_x: PixelValueNoPercent {
1815 inner: PixelValue::const_px(0),
1816 },
1817 offset_y: PixelValueNoPercent {
1818 inner: PixelValue::const_px(0),
1819 },
1820 color: ColorU {
1821 r: 229,
1822 g: 57,
1823 b: 53,
1824 a: 255,
1825 },
1826 blur_radius: PixelValueNoPercent {
1827 inner: PixelValue::const_px(2),
1828 },
1829 spread_radius: PixelValueNoPercent {
1830 inner: PixelValue::const_px(0),
1831 },
1832 clip_mode: BoxShadowClipMode::Outset,
1833 },
1834 )))),
1835 CssPropertyWithConditions::simple(CssProperty::BoxShadowBottom(
1836 StyleBoxShadowValue::Exact(BoxOrStatic::Static(&StyleBoxShadow {
1837 offset_x: PixelValueNoPercent {
1838 inner: PixelValue::const_px(0),
1839 },
1840 offset_y: PixelValueNoPercent {
1841 inner: PixelValue::const_px(0),
1842 },
1843 color: ColorU {
1844 r: 229,
1845 g: 57,
1846 b: 53,
1847 a: 255,
1848 },
1849 blur_radius: PixelValueNoPercent {
1850 inner: PixelValue::const_px(2),
1851 },
1852 spread_radius: PixelValueNoPercent {
1853 inner: PixelValue::const_px(0),
1854 },
1855 clip_mode: BoxShadowClipMode::Outset,
1856 })),
1857 )),
1858 CssPropertyWithConditions::simple(CssProperty::Cursor(StyleCursorValue::Exact(
1859 StyleCursor::Pointer,
1860 ))),
1861 CssPropertyWithConditions::simple(CssProperty::FontFamily(StyleFontFamilyVecValue::Exact(
1862 StyleFontFamilyVec::from_const_slice(STYLE_FONT_FAMILY_11383897783350685780_ITEMS),
1863 ))),
1864 CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
1865 LayoutHeight::Px(PixelValue::const_px(20)),
1866 ))),
1867 CssPropertyWithConditions::simple(CssProperty::Position(LayoutPositionValue::Exact(
1868 LayoutPosition::Absolute,
1869 ))),
1870 CssPropertyWithConditions::simple(CssProperty::TextAlign(StyleTextAlignValue::Exact(
1871 StyleTextAlign::Center,
1872 ))),
1873 CssPropertyWithConditions::simple(CssProperty::Transform(StyleTransformVecValue::Exact(
1874 StyleTransformVec::from_const_slice(STYLE_TRANSFORM_14683950870521466298_ITEMS),
1875 ))),
1876 CssPropertyWithConditions::simple(CssProperty::Width(LayoutWidthValue::Exact(
1877 LayoutWidth::Px(PixelValue::const_px(20)),
1878 ))),
1879 ];
1880 const CSS_MATCH_7395766480280098891: CssPropertyWithConditionsVec =
1881 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_7395766480280098891_PROPERTIES);
1882
1883 const CSS_MATCH_7432473243011547380_PROPERTIES: &[CssPropertyWithConditions] = &[
1884 CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
1886 StyleBackgroundContentVecValue::Exact(StyleBackgroundContentVec::from_const_slice(
1887 STYLE_BACKGROUND_CONTENT_15813232491335471489_ITEMS,
1888 )),
1889 )),
1890 CssPropertyWithConditions::simple(CssProperty::BoxShadowLeft(StyleBoxShadowValue::Exact(BoxOrStatic::Static(&
1891 StyleBoxShadow {
1892 offset_x: PixelValueNoPercent {
1893 inner: PixelValue::const_px(0),
1894 },
1895 offset_y: PixelValueNoPercent {
1896 inner: PixelValue::const_px(0),
1897 },
1898 color: ColorU {
1899 r: 0,
1900 g: 0,
1901 b: 0,
1902 a: 255,
1903 },
1904 blur_radius: PixelValueNoPercent {
1905 inner: PixelValue::const_px(4),
1906 },
1907 spread_radius: PixelValueNoPercent {
1908 inner: PixelValue::const_px(0),
1909 },
1910 clip_mode: BoxShadowClipMode::Inset,
1911 },
1912 )))),
1913 CssPropertyWithConditions::simple(CssProperty::BoxShadowRight(StyleBoxShadowValue::Exact(BoxOrStatic::Static(&
1914 StyleBoxShadow {
1915 offset_x: PixelValueNoPercent {
1916 inner: PixelValue::const_px(0),
1917 },
1918 offset_y: PixelValueNoPercent {
1919 inner: PixelValue::const_px(0),
1920 },
1921 color: ColorU {
1922 r: 0,
1923 g: 0,
1924 b: 0,
1925 a: 255,
1926 },
1927 blur_radius: PixelValueNoPercent {
1928 inner: PixelValue::const_px(4),
1929 },
1930 spread_radius: PixelValueNoPercent {
1931 inner: PixelValue::const_px(0),
1932 },
1933 clip_mode: BoxShadowClipMode::Inset,
1934 },
1935 )))),
1936 CssPropertyWithConditions::simple(CssProperty::BoxShadowTop(StyleBoxShadowValue::Exact(BoxOrStatic::Static(&
1937 StyleBoxShadow {
1938 offset_x: PixelValueNoPercent {
1939 inner: PixelValue::const_px(0),
1940 },
1941 offset_y: PixelValueNoPercent {
1942 inner: PixelValue::const_px(0),
1943 },
1944 color: ColorU {
1945 r: 0,
1946 g: 0,
1947 b: 0,
1948 a: 255,
1949 },
1950 blur_radius: PixelValueNoPercent {
1951 inner: PixelValue::const_px(4),
1952 },
1953 spread_radius: PixelValueNoPercent {
1954 inner: PixelValue::const_px(0),
1955 },
1956 clip_mode: BoxShadowClipMode::Inset,
1957 },
1958 )))),
1959 CssPropertyWithConditions::simple(CssProperty::BoxShadowBottom(
1960 StyleBoxShadowValue::Exact(BoxOrStatic::Static(&StyleBoxShadow {
1961 offset_x: PixelValueNoPercent {
1962 inner: PixelValue::const_px(0),
1963 },
1964 offset_y: PixelValueNoPercent {
1965 inner: PixelValue::const_px(0),
1966 },
1967 color: ColorU {
1968 r: 0,
1969 g: 0,
1970 b: 0,
1971 a: 255,
1972 },
1973 blur_radius: PixelValueNoPercent {
1974 inner: PixelValue::const_px(4),
1975 },
1976 spread_radius: PixelValueNoPercent {
1977 inner: PixelValue::const_px(0),
1978 },
1979 clip_mode: BoxShadowClipMode::Inset,
1980 })),
1981 )),
1982 CssPropertyWithConditions::simple(CssProperty::FlexGrow(LayoutFlexGrowValue::Exact(
1983 LayoutFlexGrow {
1984 inner: FloatValue::const_new(1),
1985 },
1986 ))),
1987 ];
1988 const CSS_MATCH_7432473243011547380: CssPropertyWithConditionsVec =
1989 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_7432473243011547380_PROPERTIES);
1990
1991 const CSS_MATCH_9863994880298313101_PROPERTIES: &[CssPropertyWithConditions] = &[
1992 CssPropertyWithConditions::simple(CssProperty::Display(LayoutDisplayValue::Exact(
1994 LayoutDisplay::Flex,
1995 ))),
1996 CssPropertyWithConditions::simple(CssProperty::FlexDirection(
1997 LayoutFlexDirectionValue::Exact(LayoutFlexDirection::Row),
1998 )),
1999 CssPropertyWithConditions::simple(CssProperty::MarginTop(LayoutMarginTopValue::Exact(
2000 LayoutMarginTop {
2001 inner: PixelValue::const_px(10),
2002 },
2003 ))),
2004 ];
2005 const CSS_MATCH_9863994880298313101: CssPropertyWithConditionsVec =
2006 CssPropertyWithConditionsVec::from_const_slice(CSS_MATCH_9863994880298313101_PROPERTIES);
2007
2008 let node_transform = StyleTransformTranslate2D {
2011 x: PixelValue::px(graph_offset.0 + node.position.x),
2012 y: PixelValue::px(graph_offset.1 + node.position.y),
2013 };
2014
2015 let inputs = node_info
2017 .inputs
2018 .iter()
2019 .filter_map(|io_id| {
2020 let node_graph_ref = node_local_dataset
2021 .backref
2022 .downcast_ref::<NodeGraphLocalDataset>()?;
2023 let io_info = node_graph_ref
2024 .node_graph
2025 .input_output_types
2026 .iter()
2027 .find(|i| i.io_type_id == *io_id)?;
2028 Some((
2029 io_info.io_info.data_type.clone(),
2030 io_info.io_info.color,
2031 ))
2032 })
2033 .collect::<Vec<_>>();
2034
2035 let outputs = node_info
2036 .outputs
2037 .iter()
2038 .filter_map(|io_id| {
2039 let node_graph_ref = node_local_dataset
2040 .backref
2041 .downcast_ref::<NodeGraphLocalDataset>()?;
2042 let io_info = node_graph_ref
2043 .node_graph
2044 .input_output_types
2045 .iter()
2046 .find(|i| i.io_type_id == *io_id)?;
2047 Some((
2048 io_info.io_info.data_type.clone(),
2049 io_info.io_info.color,
2050 ))
2051 })
2052 .collect::<Vec<_>>();
2053
2054 let node_local_dataset = RefAny::new(node_local_dataset);
2055
2056 Dom::create_div()
2057 .with_css_props(vec![
2058 CssPropertyWithConditions::simple(CssProperty::Position(LayoutPositionValue::Exact(
2059 LayoutPosition::Absolute,
2060 ))),
2061 ].into())
2062 .with_children(vec![
2063 Dom::create_div()
2064 .with_callbacks(vec![
2065 CoreCallbackData {
2066 event: EventFilter::Hover(HoverEventFilter::LeftMouseDown),
2067 refany: node_local_dataset.clone(),
2068 callback: CoreCallback { cb: nodegraph_set_active_node as usize, ctx: OptionRefAny::None },
2069 },
2070 ].into())
2071 .with_css_props(vec![
2072 CssPropertyWithConditions::simple(CssProperty::OverflowX(
2074 LayoutOverflowValue::Exact(LayoutOverflow::Visible)
2075 )),
2076 CssPropertyWithConditions::simple(CssProperty::Position(LayoutPositionValue::Exact(
2077 LayoutPosition::Relative,
2078 ))),
2079 CssPropertyWithConditions::simple(CssProperty::OverflowY(
2080 LayoutOverflowValue::Exact(LayoutOverflow::Visible)
2081 )),
2082 CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
2083 StyleBackgroundContentVecValue::Exact(StyleBackgroundContentVec::from_const_slice(
2084 STYLE_BACKGROUND_CONTENT_11535310356736632656_ITEMS,
2085 )),
2086 )),
2087 CssPropertyWithConditions::simple(CssProperty::BorderTopColor(
2088 StyleBorderTopColorValue::Exact(StyleBorderTopColor {
2089 inner: ColorU {
2090 r: 0,
2091 g: 180,
2092 b: 219,
2093 a: 255,
2094 },
2095 }),
2096 )),
2097 CssPropertyWithConditions::simple(CssProperty::BorderRightColor(
2098 StyleBorderRightColorValue::Exact(StyleBorderRightColor {
2099 inner: ColorU {
2100 r: 0,
2101 g: 180,
2102 b: 219,
2103 a: 255,
2104 },
2105 }),
2106 )),
2107 CssPropertyWithConditions::simple(CssProperty::BorderLeftColor(
2108 StyleBorderLeftColorValue::Exact(StyleBorderLeftColor {
2109 inner: ColorU {
2110 r: 0,
2111 g: 180,
2112 b: 219,
2113 a: 255,
2114 },
2115 }),
2116 )),
2117 CssPropertyWithConditions::simple(CssProperty::BorderBottomColor(
2118 StyleBorderBottomColorValue::Exact(StyleBorderBottomColor {
2119 inner: ColorU {
2120 r: 0,
2121 g: 180,
2122 b: 219,
2123 a: 255,
2124 },
2125 }),
2126 )),
2127 CssPropertyWithConditions::simple(CssProperty::BorderTopStyle(
2128 StyleBorderTopStyleValue::Exact(StyleBorderTopStyle {
2129 inner: BorderStyle::Solid,
2130 }),
2131 )),
2132 CssPropertyWithConditions::simple(CssProperty::BorderRightStyle(
2133 StyleBorderRightStyleValue::Exact(StyleBorderRightStyle {
2134 inner: BorderStyle::Solid,
2135 }),
2136 )),
2137 CssPropertyWithConditions::simple(CssProperty::BorderLeftStyle(
2138 StyleBorderLeftStyleValue::Exact(StyleBorderLeftStyle {
2139 inner: BorderStyle::Solid,
2140 }),
2141 )),
2142 CssPropertyWithConditions::simple(CssProperty::BorderBottomStyle(
2143 StyleBorderBottomStyleValue::Exact(StyleBorderBottomStyle {
2144 inner: BorderStyle::Solid,
2145 }),
2146 )),
2147 CssPropertyWithConditions::simple(CssProperty::BorderTopWidth(
2148 LayoutBorderTopWidthValue::Exact(LayoutBorderTopWidth {
2149 inner: PixelValue::const_px(1),
2150 }),
2151 )),
2152 CssPropertyWithConditions::simple(CssProperty::BorderRightWidth(
2153 LayoutBorderRightWidthValue::Exact(LayoutBorderRightWidth {
2154 inner: PixelValue::const_px(1),
2155 }),
2156 )),
2157 CssPropertyWithConditions::simple(CssProperty::BorderLeftWidth(
2158 LayoutBorderLeftWidthValue::Exact(LayoutBorderLeftWidth {
2159 inner: PixelValue::const_px(1),
2160 }),
2161 )),
2162 CssPropertyWithConditions::simple(CssProperty::BorderBottomWidth(
2163 LayoutBorderBottomWidthValue::Exact(LayoutBorderBottomWidth {
2164 inner: PixelValue::const_px(1),
2165 }),
2166 )),
2167 CssPropertyWithConditions::simple(CssProperty::BoxShadowLeft(StyleBoxShadowValue::Exact(BoxOrStatic::heap(
2168 StyleBoxShadow {
2169 offset_x: PixelValueNoPercent { inner: PixelValue::const_px(0) }, offset_y: PixelValueNoPercent { inner: PixelValue::const_px(0) },
2170 color: ColorU {
2171 r: 0,
2172 g: 131,
2173 b: 176,
2174 a: 119,
2175 },
2176 blur_radius: PixelValueNoPercent {
2177 inner: PixelValue::const_px(3),
2178 },
2179 spread_radius: PixelValueNoPercent {
2180 inner: PixelValue::const_px(0),
2181 },
2182 clip_mode: BoxShadowClipMode::Outset,
2183 },
2184 )))),
2185 CssPropertyWithConditions::simple(CssProperty::BoxShadowRight(StyleBoxShadowValue::Exact(BoxOrStatic::heap(
2186 StyleBoxShadow {
2187 offset_x: PixelValueNoPercent { inner: PixelValue::const_px(0) }, offset_y: PixelValueNoPercent { inner: PixelValue::const_px(0) },
2188 color: ColorU {
2189 r: 0,
2190 g: 131,
2191 b: 176,
2192 a: 119,
2193 },
2194 blur_radius: PixelValueNoPercent {
2195 inner: PixelValue::const_px(3),
2196 },
2197 spread_radius: PixelValueNoPercent {
2198 inner: PixelValue::const_px(0),
2199 },
2200 clip_mode: BoxShadowClipMode::Outset,
2201 },
2202 )))),
2203 CssPropertyWithConditions::simple(CssProperty::BoxShadowTop(StyleBoxShadowValue::Exact(BoxOrStatic::heap(
2204 StyleBoxShadow {
2205 offset_x: PixelValueNoPercent { inner: PixelValue::const_px(0) }, offset_y: PixelValueNoPercent { inner: PixelValue::const_px(0) },
2206 color: ColorU {
2207 r: 0,
2208 g: 131,
2209 b: 176,
2210 a: 119,
2211 },
2212 blur_radius: PixelValueNoPercent {
2213 inner: PixelValue::const_px(3),
2214 },
2215 spread_radius: PixelValueNoPercent {
2216 inner: PixelValue::const_px(0),
2217 },
2218 clip_mode: BoxShadowClipMode::Outset,
2219 },
2220 )))),
2221 CssPropertyWithConditions::simple(CssProperty::BoxShadowBottom(
2222 StyleBoxShadowValue::Exact(BoxOrStatic::heap(StyleBoxShadow {
2223 offset_x: PixelValueNoPercent { inner: PixelValue::const_px(0) }, offset_y: PixelValueNoPercent { inner: PixelValue::const_px(0) },
2224 color: ColorU {
2225 r: 0,
2226 g: 131,
2227 b: 176,
2228 a: 119,
2229 },
2230 blur_radius: PixelValueNoPercent {
2231 inner: PixelValue::const_px(3),
2232 },
2233 spread_radius: PixelValueNoPercent {
2234 inner: PixelValue::const_px(0),
2235 },
2236 clip_mode: BoxShadowClipMode::Outset,
2237 })),
2238 )),
2239 CssPropertyWithConditions::simple(CssProperty::TextColor(StyleTextColorValue::Exact(
2240 StyleTextColor {
2241 inner: ColorU {
2242 r: 255,
2243 g: 255,
2244 b: 255,
2245 a: 255,
2246 },
2247 },
2248 ))),
2249
2250 CssPropertyWithConditions::simple(CssProperty::Display(LayoutDisplayValue::Exact(
2251 LayoutDisplay::Block
2252 ))),
2253 CssPropertyWithConditions::simple(CssProperty::FontFamily(StyleFontFamilyVecValue::Exact(
2254 StyleFontFamilyVec::from_const_slice(STYLE_FONT_FAMILY_8122988506401935406_ITEMS),
2255 ))),
2256 CssPropertyWithConditions::simple(CssProperty::PaddingTop(LayoutPaddingTopValue::Exact(
2257 LayoutPaddingTop {
2258 inner: PixelValue::const_px(10),
2259 },
2260 ))),
2261 CssPropertyWithConditions::simple(CssProperty::PaddingBottom(
2262 LayoutPaddingBottomValue::Exact(LayoutPaddingBottom {
2263 inner: PixelValue::const_px(10),
2264 }),
2265 )),
2266 CssPropertyWithConditions::simple(CssProperty::PaddingLeft(LayoutPaddingLeftValue::Exact(
2267 LayoutPaddingLeft {
2268 inner: PixelValue::const_px(10),
2269 },
2270 ))),
2271 CssPropertyWithConditions::simple(CssProperty::PaddingRight(
2272 LayoutPaddingRightValue::Exact(LayoutPaddingRight {
2273 inner: PixelValue::const_px(10),
2274 }),
2275 )),
2276 CssPropertyWithConditions::simple(CssProperty::Transform(StyleTransformVecValue::Exact(
2277 if scale_factor == 1.0 {
2278 vec![
2279 StyleTransform::Translate(node_transform)
2280 ]
2281 } else {
2282 vec![
2283 StyleTransform::Translate(node_transform),
2284 StyleTransform::ScaleX(PercentageValue::new(scale_factor * 100.0)),
2285 StyleTransform::ScaleY(PercentageValue::new(scale_factor * 100.0)),
2286 ]
2287 }.into()
2288 ))),
2289 CssPropertyWithConditions::simple(CssProperty::Width(LayoutWidthValue::Exact(
2290 LayoutWidth::Px(PixelValue::const_px(250),),
2291 ))),
2292 ].into())
2293 .with_ids_and_classes({
2294 const IDS_AND_CLASSES_4480169002427296613: &[IdOrClass] =
2295 &[Class(AzString::from_const_str("node_graph_node"))];
2296 IdOrClassVec::from_const_slice(IDS_AND_CLASSES_4480169002427296613)
2297 })
2298 .with_children(DomVec::from_vec(vec![
2299 Dom::create_text(AzString::from_const_str("X"))
2300 .with_css_props(CSS_MATCH_7395766480280098891)
2301 .with_callbacks(vec![
2302 CoreCallbackData {
2303 event: EventFilter::Hover(HoverEventFilter::MouseUp),
2304 refany: node_local_dataset.clone(),
2305 callback: CoreCallback { cb: nodegraph_delete_node as usize, ctx: OptionRefAny::None },
2306 },
2307 ].into())
2308 .with_ids_and_classes({
2309 const IDS_AND_CLASSES_7122017923389407516: &[IdOrClass] =
2310 &[Class(AzString::from_const_str("node_close_button"))];
2311 IdOrClassVec::from_const_slice(IDS_AND_CLASSES_7122017923389407516)
2312 }),
2313 Dom::create_text(node_info.node_type_name.clone())
2314 .with_css_props(CSS_MATCH_1739273067404038547)
2315 .with_ids_and_classes({
2316 const IDS_AND_CLASSES_15777790571346582635: &[IdOrClass] =
2317 &[Class(AzString::from_const_str("node_label"))];
2318 IdOrClassVec::from_const_slice(IDS_AND_CLASSES_15777790571346582635)
2319 }),
2320 Dom::create_div()
2321 .with_css_props(CSS_MATCH_3354247437065914166)
2322 .with_ids_and_classes({
2323 const IDS_AND_CLASSES_5590500152394859708: &[IdOrClass] =
2324 &[Class(AzString::from_const_str("node_body"))];
2325 IdOrClassVec::from_const_slice(IDS_AND_CLASSES_5590500152394859708)
2326 })
2327 .with_children(DomVec::from_vec(vec![
2328 Dom::create_div()
2329 .with_css_props(CSS_MATCH_16946967739775705757)
2330 .with_ids_and_classes({
2331 const IDS_AND_CLASSES_3626404106673061698: &[IdOrClass] =
2332 &[Class(AzString::from_const_str("inputs"))];
2333 IdOrClassVec::from_const_slice(IDS_AND_CLASSES_3626404106673061698)
2334 })
2335 .with_children(DomVec::from_vec(vec![Dom::create_div()
2336 .with_css_props(CSS_MATCH_705881630351954657)
2337 .with_ids_and_classes({
2338 const IDS_AND_CLASSES_12825690349660780627: &[IdOrClass] =
2339 &[Class(AzString::from_const_str("node_input_wrapper"))];
2340 IdOrClassVec::from_const_slice(
2341 IDS_AND_CLASSES_12825690349660780627,
2342 )
2343 })
2344 .with_children(DomVec::from_vec(
2345 inputs
2346 .into_iter()
2347 .enumerate()
2348 .map(|(io_id, (input_label, input_color))| {
2349 use self::InputOrOutput::Input;
2350
2351 Dom::create_div()
2352 .with_css_props(CSS_MATCH_9863994880298313101)
2353 .with_ids_and_classes({
2354 const IDS_AND_CLASSES_5020681879750641508:
2355 &[IdOrClass] = &[Class(AzString::from_const_str(
2356 "node_input_container",
2357 ))];
2358 IdOrClassVec::from_const_slice(
2359 IDS_AND_CLASSES_5020681879750641508,
2360 )
2361 })
2362 .with_children(DomVec::from_vec(vec![
2363 Dom::create_div()
2364 .with_css_props(
2365 CSS_MATCH_4700400755767504372,
2366 )
2367 .with_ids_and_classes({
2368 const IDS_AND_CLASSES_9154857442066749879:
2369 &[IdOrClass] =
2370 &[Class(AzString::from_const_str(
2371 "node_input_connection_label_wrapper",
2372 ))];
2373 IdOrClassVec::from_const_slice(
2374 IDS_AND_CLASSES_9154857442066749879,
2375 )
2376 })
2377 .with_children(DomVec::from_vec(vec![Dom::create_text(
2378 input_label,
2379 )
2380 .with_css_props(
2381 CSS_MATCH_11452431279102104133,
2382 )
2383 .with_ids_and_classes({
2384 const IDS_AND_CLASSES_16291496011772407931:
2385 &[IdOrClass] =
2386 &[Class(AzString::from_const_str(
2387 "node_input_connection_label",
2388 ))];
2389 IdOrClassVec::from_const_slice(
2390 IDS_AND_CLASSES_16291496011772407931,
2391 )
2392 })])),
2393 Dom::create_div()
2394 .with_callbacks(vec![
2395 CoreCallbackData {
2396 event: EventFilter::Hover(HoverEventFilter::LeftMouseUp),
2397 refany: RefAny::new(NodeInputOutputLocalDataset {
2398 io_id: Input(io_id),
2399 backref: node_local_dataset.clone(),
2400 }),
2401 callback: CoreCallback { cb: nodegraph_input_output_connect as usize, ctx: OptionRefAny::None },
2402 },
2403 CoreCallbackData {
2404 event: EventFilter::Hover(HoverEventFilter::MiddleMouseUp),
2405 refany: RefAny::new(NodeInputOutputLocalDataset {
2406 io_id: Input(io_id),
2407 backref: node_local_dataset.clone(),
2408 }),
2409 callback: CoreCallback { cb: nodegraph_input_output_disconnect as usize, ctx: OptionRefAny::None },
2410 },
2411 ].into())
2412 .with_css_props(CssPropertyWithConditionsVec::from_vec(vec![
2413 CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
2415 StyleBackgroundContentVecValue::Exact(vec![StyleBackgroundContent::Color(input_color)].into()),
2416 )),
2417 CssPropertyWithConditions::simple(CssProperty::Cursor(StyleCursorValue::Exact(
2418 StyleCursor::Pointer,
2419 ))),
2420 CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
2421 LayoutHeight::Px(PixelValue::const_px(15),),
2422 ))),
2423 CssPropertyWithConditions::simple(CssProperty::Width(LayoutWidthValue::Exact(
2424 LayoutWidth::Px(PixelValue::const_px(15),),
2425 ))),
2426 ])
2427 )
2428 .with_ids_and_classes({
2429 const IDS_AND_CLASSES_2128818677168244823:
2430 &[IdOrClass] = &[Class(
2431 AzString::from_const_str("node_input"),
2432 )];
2433 IdOrClassVec::from_const_slice(
2434 IDS_AND_CLASSES_2128818677168244823,
2435 )
2436 }),
2437 ]))
2438 }).collect()
2439 ))
2440 ])),
2441 Dom::create_div()
2442 .with_css_props(CSS_MATCH_7432473243011547380)
2443 .with_ids_and_classes({
2444 const IDS_AND_CLASSES_746059979773622802: &[IdOrClass] =
2445 &[Class(AzString::from_const_str("node_content_wrapper"))];
2446 IdOrClassVec::from_const_slice(IDS_AND_CLASSES_746059979773622802)
2447 })
2448 .with_children({
2449
2450 let mut fields = Vec::new();
2451
2452 for (field_idx, field) in node.fields.iter().enumerate() {
2453
2454 let field_local_dataset = RefAny::new(NodeFieldLocalDataset {
2455 field_idx,
2456 backref: node_local_dataset.clone(),
2457 });
2458
2459 let div = Dom::create_div()
2460 .with_css_props(CSS_MATCH_2639191696846875011)
2461 .with_ids_and_classes({
2462 const IDS_AND_CLASSES_4413230059125905311: &[IdOrClass] =
2463 &[Class(AzString::from_const_str(
2464 "node_configuration_field_container",
2465 ))];
2466 IdOrClassVec::from_const_slice(
2467 IDS_AND_CLASSES_4413230059125905311,
2468 )
2469 })
2470 .with_children(DomVec::from_vec(vec![
2471 Dom::create_text(field.key.clone())
2472 .with_css_props(CSS_MATCH_1198521124955124418)
2473 .with_ids_and_classes({
2474 const IDS_AND_CLASSES_12334207996395559585:
2475 &[IdOrClass] =
2476 &[Class(AzString::from_const_str(
2477 "node_configuration_field_label",
2478 ))];
2479 IdOrClassVec::from_const_slice(
2480 IDS_AND_CLASSES_12334207996395559585,
2481 )
2482 }),
2483
2484 match &field.value {
2485 NodeTypeFieldValue::TextInput(initial_text) => {
2486 let cb: TextInputOnFocusLostCallbackType = nodegraph_on_textinput_focus_lost;
2487 TextInput::create()
2488 .with_text(initial_text.clone())
2489 .with_on_focus_lost(field_local_dataset, cb)
2490 .dom()
2491 },
2492 NodeTypeFieldValue::NumberInput(initial_value) => {
2493 let cb: NumberInputOnFocusLostCallbackType = nodegraph_on_numberinput_focus_lost;
2494 NumberInput::create(*initial_value)
2495 .with_on_focus_lost(field_local_dataset, cb)
2496 .dom()
2497 },
2498 NodeTypeFieldValue::CheckBox(initial_checked) => {
2499 let cb: CheckBoxOnToggleCallbackType = nodegraph_on_checkbox_value_changed;
2500 CheckBox::create(*initial_checked)
2501 .with_on_toggle(field_local_dataset, cb)
2502 .dom()
2503 },
2504 NodeTypeFieldValue::ColorInput(initial_color) => {
2505 let cb: ColorInputOnValueChangeCallbackType = nodegraph_on_colorinput_value_changed;
2506 ColorInput::create(*initial_color)
2507 .with_on_value_change(field_local_dataset, cb)
2508 .dom()
2509 },
2510 NodeTypeFieldValue::FileInput(file_path) => {
2511 let cb: FileInputOnPathChangeCallbackType = nodegraph_on_fileinput_button_clicked;
2512 FileInput::create(file_path.clone())
2513 .with_on_path_change(field_local_dataset, cb)
2514 .dom()
2515 },
2516 }
2517 ]));
2518
2519 fields.push(div);
2520 }
2521
2522 DomVec::from_vec(fields)
2523 }),
2524 Dom::create_div()
2525 .with_css_props(CSS_MATCH_14906563417280941890)
2526 .with_ids_and_classes({
2527 const IDS_AND_CLASSES_4737474624251936466: &[IdOrClass] =
2528 &[Class(AzString::from_const_str("outputs"))];
2529 IdOrClassVec::from_const_slice(IDS_AND_CLASSES_4737474624251936466)
2530 })
2531 .with_children(DomVec::from_vec(vec![Dom::create_div()
2532 .with_css_props(CSS_MATCH_10339190304804100510)
2533 .with_ids_and_classes({
2534 const IDS_AND_CLASSES_12883576328110161157: &[IdOrClass] =
2535 &[Class(AzString::from_const_str("node_output_wrapper"))];
2536 IdOrClassVec::from_const_slice(
2537 IDS_AND_CLASSES_12883576328110161157,
2538 )
2539 })
2540 .with_children(DomVec::from_vec(
2541 outputs
2542 .into_iter()
2543 .enumerate()
2544 .map(|(io_id, (output_label, output_color))| {
2545 use self::InputOrOutput::Output;
2546 Dom::create_div()
2547 .with_css_props(CSS_MATCH_12400244273289328300)
2548 .with_ids_and_classes({
2549 const IDS_AND_CLASSES_10917819668096233812:
2550 &[IdOrClass] = &[Class(AzString::from_const_str(
2551 "node_output_container",
2552 ))];
2553 IdOrClassVec::from_const_slice(
2554 IDS_AND_CLASSES_10917819668096233812,
2555 )
2556 })
2557 .with_children(DomVec::from_vec(vec![
2558 Dom::create_div()
2559 .with_callbacks(vec![
2560 CoreCallbackData {
2561 event: EventFilter::Hover(HoverEventFilter::LeftMouseUp),
2562 refany: RefAny::new(NodeInputOutputLocalDataset {
2563 io_id: Output(io_id),
2564 backref: node_local_dataset.clone(),
2565 }),
2566 callback: CoreCallback { cb: nodegraph_input_output_connect as usize, ctx: OptionRefAny::None },
2567 },
2568 CoreCallbackData {
2569 event: EventFilter::Hover(HoverEventFilter::MiddleMouseUp),
2570 refany: RefAny::new(NodeInputOutputLocalDataset {
2571 io_id: Output(io_id),
2572 backref: node_local_dataset.clone(),
2573 }),
2574 callback: CoreCallback { cb: nodegraph_input_output_disconnect as usize, ctx: OptionRefAny::None },
2575 },
2576 ].into())
2577 .with_css_props(
2578 CssPropertyWithConditionsVec::from_vec(vec![
2579 CssPropertyWithConditions::simple(CssProperty::BackgroundContent(
2581 StyleBackgroundContentVecValue::Exact(vec![
2582 StyleBackgroundContent::Color(output_color)
2583 ].into()),
2584 )),
2585 CssPropertyWithConditions::simple(CssProperty::Cursor(StyleCursorValue::Exact(
2586 StyleCursor::Pointer,
2587 ))),
2588 CssPropertyWithConditions::simple(CssProperty::Height(LayoutHeightValue::Exact(
2589 LayoutHeight::Px(PixelValue::const_px(15),),
2590 ))),
2591 CssPropertyWithConditions::simple(CssProperty::Width(LayoutWidthValue::Exact(
2592 LayoutWidth::Px(PixelValue::const_px(15),),
2593 ))),
2594 ])
2595 )
2596 .with_ids_and_classes({
2597 const IDS_AND_CLASSES_17632471664405317563:
2598 &[IdOrClass] = &[Class(
2599 AzString::from_const_str("node_output"),
2600 )];
2601 IdOrClassVec::from_const_slice(
2602 IDS_AND_CLASSES_17632471664405317563,
2603 )
2604 }),
2605 Dom::create_div()
2606 .with_css_props(
2607 CSS_MATCH_12038890904436132038,
2608 )
2609 .with_ids_and_classes({
2610 const IDS_AND_CLASSES_1667960214206134147:
2611 &[IdOrClass] =
2612 &[Class(AzString::from_const_str(
2613 "node_output_connection_label_wrapper",
2614 ))];
2615 IdOrClassVec::from_const_slice(
2616 IDS_AND_CLASSES_1667960214206134147,
2617 )
2618 })
2619 .with_children(DomVec::from_vec(vec![Dom::create_text(
2620 output_label,
2621 )
2622 .with_css_props(
2623 CSS_MATCH_2008162367868363199,
2624 )
2625 .with_ids_and_classes({
2626 const IDS_AND_CLASSES_2974914452796301884:
2627 &[IdOrClass] =
2628 &[Class(AzString::from_const_str(
2629 "node_output_connection_label",
2630 ))];
2631 IdOrClassVec::from_const_slice(
2632 IDS_AND_CLASSES_2974914452796301884,
2633 )
2634 })])),
2635 ]))
2636 }).collect()
2637 ))])),
2638 ])),
2639 ]))
2640 .with_dataset(Some(node_local_dataset).into())
2641 ].into())
2642}
2643
2644#[allow(clippy::too_many_lines)] fn render_connections(node_graph: &NodeGraph, root_marker_nodedata: RefAny) -> Dom {
2646 static NODEGRAPH_CONNECTIONS_CONTAINER_CLASS: &[IdOrClass] = &[Class(
2647 AzString::from_const_str("nodegraph-connections-container"),
2648 )];
2649
2650 static NODEGRAPH_CONNECTIONS_CONTAINER_PROPS: &[CssPropertyWithConditions] = &[
2651 CssPropertyWithConditions::simple(CssProperty::position(LayoutPosition::Absolute)),
2652 CssPropertyWithConditions::simple(CssProperty::flex_grow(LayoutFlexGrow::const_new(1))),
2653 ];
2654
2655 Dom::create_div()
2656 .with_ids_and_classes(IdOrClassVec::from_const_slice(
2657 NODEGRAPH_CONNECTIONS_CONTAINER_CLASS,
2658 ))
2659 .with_css_props(CssPropertyWithConditionsVec::from_const_slice(
2660 NODEGRAPH_CONNECTIONS_CONTAINER_PROPS,
2661 ))
2662 .with_dataset(Some(root_marker_nodedata).into())
2663 .with_children({
2664 let mut children = Vec::new();
2665
2666 for NodeIdNodeMap { node_id, node } in node_graph.nodes.as_ref() {
2667 let out_node_id = node_id;
2668 let node_type_info = match node_graph
2669 .node_types
2670 .iter()
2671 .find(|i| i.node_type_id == node.node_type)
2672 {
2673 Some(s) => &s.node_type_info,
2674 None => continue,
2675 };
2676
2677 for OutputConnection {
2678 output_index,
2679 connects_to,
2680 } in node.connect_out.as_ref()
2681 {
2682 let Some(output_type_id) = node_type_info.outputs.get(*output_index) else {
2683 continue;
2684 };
2685
2686 let output_color = match node_graph
2687 .input_output_types
2688 .iter()
2689 .find(|o| o.io_type_id == *output_type_id)
2690 {
2691 Some(s) => s.io_info.color,
2692 None => continue,
2693 };
2694
2695 for InputNodeAndIndex {
2696 node_id,
2697 input_index,
2698 } in connects_to.as_ref()
2699 {
2700 let in_node_id = node_id;
2701
2702 let mut cld = ConnectionLocalDataset {
2703 out_node_id: *out_node_id,
2704 out_idx: *output_index,
2705 in_node_id: *in_node_id,
2706 in_idx: *input_index,
2707 swap_vert: false,
2708 swap_horz: false,
2709 color: output_color,
2710 };
2711
2712 let Some((rect, swap_vert, swap_horz)) = get_rect(node_graph, cld) else {
2713 continue;
2714 };
2715
2716 cld.swap_vert = swap_vert;
2717 cld.swap_horz = swap_horz;
2718
2719 let cld_refany = RefAny::new(cld);
2720 let connection_div = Dom::create_image(ImageRef::callback(
2721 draw_connection as usize,
2722 cld_refany.clone(),
2723 ))
2724 .with_dataset(Some(cld_refany).into())
2725 .with_css_props(
2726 vec![
2727 CssPropertyWithConditions::simple(CssProperty::Transform(
2728 StyleTransformVecValue::Exact(
2729 vec![
2730 StyleTransform::Translate(StyleTransformTranslate2D {
2731 x: PixelValue::px(
2732 node_graph.offset.x + rect.origin.x,
2733 ),
2734 y: PixelValue::px(
2735 node_graph.offset.y + rect.origin.y,
2736 ),
2737 }),
2738 StyleTransform::ScaleX(PercentageValue::new(
2739 node_graph.scale_factor * 100.0,
2740 )),
2741 StyleTransform::ScaleY(PercentageValue::new(
2742 node_graph.scale_factor * 100.0,
2743 )),
2744 ]
2745 .into(),
2746 ),
2747 )),
2748 CssPropertyWithConditions::simple(CssProperty::Width(
2749 LayoutWidthValue::Exact(LayoutWidth::Px(PixelValue::px(
2750 rect.size.width,
2751 ))),
2752 )),
2753 CssPropertyWithConditions::simple(CssProperty::Height(
2754 LayoutHeightValue::Exact(LayoutHeight::Px(PixelValue::px(
2755 rect.size.height,
2756 ))),
2757 )),
2758 ]
2759 .into(),
2760 );
2761
2762 children.push(
2763 Dom::create_div()
2764 .with_css(
2765 "flex-grow: 1; position: absolute; overflow: hidden;",
2766 )
2767 .with_children(vec![connection_div].into()),
2768 );
2769 }
2770 }
2771 }
2772
2773 children.into()
2774 })
2775}
2776
2777#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] extern "C" fn draw_connection(mut refany: RefAny, _info: ()) -> ImageRef {
2779 let size = LogicalSize {
2782 width: 100.0,
2783 height: 100.0,
2784 };
2785
2786
2787 ImageRef::null_image(
2789 size.width as usize,
2790 size.height as usize,
2791 RawImageFormat::R8,
2792 Vec::new(),
2793 )
2794}
2795
2796const NODE_WIDTH: f32 = 250.0;
2797const V_OFFSET: f32 = 71.0;
2798const DIST_BETWEEN_NODES: f32 = 10.0;
2799const CONNECTION_DOT_HEIGHT: f32 = 15.0;
2800
2801#[allow(clippy::suboptimal_flops)] #[allow(clippy::cast_precision_loss)] fn get_rect(
2805 node_graph: &NodeGraph,
2806 connection: ConnectionLocalDataset,
2807) -> Option<(LogicalRect, bool, bool)> {
2808 let ConnectionLocalDataset {
2809 out_node_id,
2810 out_idx,
2811 in_node_id,
2812 in_idx,
2813 ..
2814 } = connection;
2815 let out_node = node_graph.nodes.iter().find(|i| i.node_id == out_node_id)?;
2816 let in_node = node_graph.nodes.iter().find(|i| i.node_id == in_node_id)?;
2817
2818 let x_out = out_node.node.position.x + NODE_WIDTH;
2819 let y_out = out_node.node.position.y
2820 + V_OFFSET
2821 + (out_idx as f32 * (DIST_BETWEEN_NODES + CONNECTION_DOT_HEIGHT));
2822
2823 let x_in = in_node.node.position.x;
2824 let y_in = in_node.node.position.y
2825 + V_OFFSET
2826 + (in_idx as f32 * (DIST_BETWEEN_NODES + CONNECTION_DOT_HEIGHT));
2827
2828 let should_swap_vertical = y_in > y_out;
2829 let should_swap_horizontal = x_in < x_out;
2830
2831 let width = (x_in - x_out).abs();
2832 let height = (y_in - y_out).abs() + CONNECTION_DOT_HEIGHT;
2833
2834 let x = x_in.min(x_out);
2835 let y = y_in.min(y_out);
2836
2837 Some((
2838 LogicalRect {
2839 size: LogicalSize { width, height },
2840 origin: LogicalPosition { x, y },
2841 },
2842 should_swap_vertical,
2843 should_swap_horizontal,
2844 ))
2845}
2846
2847extern "C" fn nodegraph_set_active_node(mut refany: RefAny, _info: CallbackInfo) -> Update {
2848 let data_clone = refany.clone();
2849 if let Some(mut refany) = refany.downcast_mut::<NodeLocalDataset>() {
2850 let node_id = refany.node_id;
2851 if let Some(mut backref) = refany.backref.downcast_mut::<NodeGraphLocalDataset>() {
2852 backref.active_node_being_dragged = Some((node_id, data_clone));
2853 }
2854 }
2855 Update::DoNothing
2856}
2857
2858extern "C" fn nodegraph_unset_active_node(mut refany: RefAny, _info: CallbackInfo) -> Update {
2859 if let Some(mut refany) = refany.downcast_mut::<NodeGraphLocalDataset>() {
2860 refany.active_node_being_dragged = None;
2861 }
2862 Update::DoNothing
2863}
2864
2865#[allow(clippy::float_cmp)] #[allow(clippy::too_many_lines)] #[allow(clippy::single_match_else)] extern "C" fn nodegraph_drag_graph_or_nodes(mut refany: RefAny, mut info: CallbackInfo) -> Update {
2870 let Some(mut refany) = refany.downcast_mut::<NodeGraphLocalDataset>() else {
2871 return Update::DoNothing;
2872 };
2873 let refany = &mut *refany;
2874
2875 let Some(prev) = info.get_previous_mouse_state() else {
2876 return Update::DoNothing;
2877 };
2878 let cur = info.get_current_mouse_state();
2879 if !(cur.left_down && prev.left_down) {
2880 return Update::DoNothing;
2882 }
2883
2884 let (InWindow(current_mouse_pos), InWindow(previous_mouse_pos)) =
2885 (cur.cursor_position, prev.cursor_position)
2886 else {
2887 return Update::DoNothing;
2888 };
2889
2890 let dx = (current_mouse_pos.x - previous_mouse_pos.x) * (1.0 / refany.node_graph.scale_factor);
2891 let dy = (current_mouse_pos.y - previous_mouse_pos.y) * (1.0 / refany.node_graph.scale_factor);
2892 let nodegraph_node = info.get_hit_node();
2893
2894 let should_update = match refany.active_node_being_dragged.clone() {
2895 Some((node_graph_node_id, data_marker)) => {
2897 let node_connection_marker = &mut refany.node_connection_marker;
2898
2899 let _nodegraph_node = info.get_hit_node();
2900 let result = match refany.callbacks.on_node_dragged.as_ref() {
2901 Some(OnNodeDragged { callback, refany }) => (callback.cb)(
2902 refany.clone(),
2903 info,
2904 node_graph_node_id,
2905 NodeDragAmount { x: dx, y: dy },
2906 ),
2907 None => Update::DoNothing,
2908 };
2909
2910 let node_position = match refany
2912 .node_graph
2913 .nodes
2914 .iter_mut()
2915 .find(|i| i.node_id == node_graph_node_id)
2916 {
2917 Some(s) => {
2918 s.node.position.x += dx;
2919 s.node.position.y += dy;
2920 s.node.position
2921 }
2922 None => return Update::DoNothing,
2923 };
2924
2925 let Some(visual_node_id) = info.get_node_id_of_root_dataset(data_marker) else {
2926 return Update::DoNothing;
2927 };
2928
2929 let node_transform = StyleTransformTranslate2D {
2930 x: PixelValue::px(node_position.x + refany.node_graph.offset.x),
2931 y: PixelValue::px(node_position.y + refany.node_graph.offset.y),
2932 };
2933
2934 info.set_css_property(
2935 visual_node_id,
2936 CssProperty::transform(
2937 if refany.node_graph.scale_factor == 1.0 {
2938 vec![StyleTransform::Translate(node_transform)]
2939 } else {
2940 vec![
2941 StyleTransform::Translate(node_transform),
2942 StyleTransform::ScaleX(PercentageValue::new(
2943 refany.node_graph.scale_factor * 100.0,
2944 )),
2945 StyleTransform::ScaleY(PercentageValue::new(
2946 refany.node_graph.scale_factor * 100.0,
2947 )),
2948 ]
2949 }
2950 .into(),
2951 ),
2952 );
2953
2954 let Some(connection_container_nodeid) =
2956 info.get_node_id_of_root_dataset(node_connection_marker.clone())
2957 else {
2958 return result;
2959 };
2960
2961 let mut first_connection_child = info.get_first_child(connection_container_nodeid);
2963
2964 while let Some(connection_nodeid) = first_connection_child {
2965 first_connection_child = info.get_next_sibling(connection_nodeid);
2966
2967 let Some(first_child) = info.get_first_child(connection_nodeid) else {
2968 continue;
2969 };
2970
2971 let Some(mut dataset) = info.get_dataset(first_child) else {
2972 continue;
2973 };
2974
2975 let Some(mut cld) = dataset.downcast_mut::<ConnectionLocalDataset>() else {
2976 continue;
2977 };
2978
2979 if !(cld.out_node_id == node_graph_node_id || cld.in_node_id == node_graph_node_id)
2980 {
2981 continue; }
2983
2984 let Some((new_rect, swap_vert, swap_horz)) = get_rect(&refany.node_graph, *cld)
2985 else {
2986 continue;
2987 };
2988
2989 cld.swap_vert = swap_vert;
2990 cld.swap_horz = swap_horz;
2991
2992 let node_transform = StyleTransformTranslate2D {
2993 x: PixelValue::px(refany.node_graph.offset.x + new_rect.origin.x),
2994 y: PixelValue::px(refany.node_graph.offset.y + new_rect.origin.y),
2995 };
2996
2997 info.set_css_property(
2998 first_child,
2999 CssProperty::transform(
3000 if refany.node_graph.scale_factor == 1.0 {
3001 vec![StyleTransform::Translate(node_transform)]
3002 } else {
3003 vec![
3004 StyleTransform::Translate(node_transform),
3005 StyleTransform::ScaleX(PercentageValue::new(
3006 refany.node_graph.scale_factor * 100.0,
3007 )),
3008 StyleTransform::ScaleY(PercentageValue::new(
3009 refany.node_graph.scale_factor * 100.0,
3010 )),
3011 ]
3012 }
3013 .into(),
3014 ),
3015 );
3016
3017 info.set_css_property(
3018 first_child,
3019 CssProperty::Width(LayoutWidthValue::Exact(LayoutWidth::Px(PixelValue::px(
3020 new_rect.size.width,
3021 )))),
3022 );
3023 info.set_css_property(
3024 first_child,
3025 CssProperty::Height(LayoutHeightValue::Exact(LayoutHeight::Px(
3026 PixelValue::px(new_rect.size.height),
3027 ))),
3028 );
3029 }
3030
3031 result
3032 }
3033 None => {
3035 let result = match refany.callbacks.on_node_graph_dragged.as_ref() {
3036 Some(OnNodeGraphDragged { callback, refany }) => (callback.cb)(
3037 refany.clone(),
3038 info,
3039 GraphDragAmount { x: dx, y: dy },
3040 ),
3041 None => Update::DoNothing,
3042 };
3043
3044 refany.node_graph.offset.x += dx;
3045 refany.node_graph.offset.y += dy;
3046
3047 let Some(node_container) = info.get_first_child(nodegraph_node) else {
3049 return Update::DoNothing;
3050 };
3051
3052 let Some(node_container) = info.get_next_sibling(node_container) else {
3053 return Update::DoNothing;
3054 };
3055
3056 let Some(mut node) = info.get_first_child(node_container) else {
3057 return Update::DoNothing;
3058 };
3059
3060 loop {
3061 let Some(node_first_child) = info.get_first_child(node) else {
3062 return Update::DoNothing;
3063 };
3064
3065 let mut node_local_dataset = match info.get_dataset(node_first_child) {
3066 None => return Update::DoNothing,
3067 Some(s) => s,
3068 };
3069
3070 let Some(node_graph_node_id) =
3071 node_local_dataset.downcast_ref::<NodeLocalDataset>()
3072 else {
3073 continue;
3074 };
3075
3076 let node_graph_node_id = node_graph_node_id.node_id;
3077
3078 let node_position = match refany
3079 .node_graph
3080 .nodes
3081 .iter()
3082 .find(|i| i.node_id == node_graph_node_id)
3083 {
3084 Some(s) => s.node.position,
3085 None => continue,
3086 };
3087
3088 let node_transform = StyleTransformTranslate2D {
3089 x: PixelValue::px(node_position.x + refany.node_graph.offset.x),
3090 y: PixelValue::px(node_position.y + refany.node_graph.offset.y),
3091 };
3092
3093 info.set_css_property(
3094 node_first_child,
3095 CssProperty::transform(
3096 if refany.node_graph.scale_factor == 1.0 {
3097 vec![StyleTransform::Translate(node_transform)]
3098 } else {
3099 vec![
3100 StyleTransform::Translate(node_transform),
3101 StyleTransform::ScaleX(PercentageValue::new(
3102 refany.node_graph.scale_factor * 100.0,
3103 )),
3104 StyleTransform::ScaleY(PercentageValue::new(
3105 refany.node_graph.scale_factor * 100.0,
3106 )),
3107 ]
3108 }
3109 .into(),
3110 ),
3111 );
3112
3113 node = match info.get_next_sibling(node) {
3114 Some(s) => s,
3115 None => break,
3116 };
3117 }
3118
3119 let node_connection_marker = &mut refany.node_connection_marker;
3120
3121 let Some(connection_container_nodeid) =
3123 info.get_node_id_of_root_dataset(node_connection_marker.clone())
3124 else {
3125 return result;
3126 };
3127
3128 let mut first_connection_child = info.get_first_child(connection_container_nodeid);
3129
3130 while let Some(connection_nodeid) = first_connection_child {
3131 first_connection_child = info.get_next_sibling(connection_nodeid);
3132
3133 let Some(first_child) = info.get_first_child(connection_nodeid) else {
3134 continue;
3135 };
3136
3137 let Some(mut dataset) = info.get_dataset(first_child) else {
3138 continue;
3139 };
3140
3141 let Some(cld) = dataset.downcast_ref::<ConnectionLocalDataset>() else {
3142 continue;
3143 };
3144
3145 let Some((new_rect, _, _)) = get_rect(&refany.node_graph, *cld) else {
3146 continue;
3147 };
3148
3149 info.set_css_property(
3150 first_child,
3151 CssProperty::transform(
3152 vec![
3153 StyleTransform::Translate(StyleTransformTranslate2D {
3154 x: PixelValue::px(refany.node_graph.offset.x + new_rect.origin.x),
3155 y: PixelValue::px(refany.node_graph.offset.y + new_rect.origin.y),
3156 }),
3157 StyleTransform::ScaleX(PercentageValue::new(
3158 refany.node_graph.scale_factor * 100.0,
3159 )),
3160 StyleTransform::ScaleY(PercentageValue::new(
3161 refany.node_graph.scale_factor * 100.0,
3162 )),
3163 ]
3164 .into(),
3165 ),
3166 );
3167 }
3168
3169 result
3170 }
3171 };
3172
3173 info.stop_propagation();
3174
3175 should_update
3176}
3177
3178extern "C" fn nodegraph_duplicate_node(mut refany: RefAny, _info: CallbackInfo) -> Update {
3179 let Some(_data) = refany.downcast_mut::<NodeLocalDataset>() else {
3180 return Update::DoNothing;
3181 };
3182
3183 Update::DoNothing }
3185
3186extern "C" fn nodegraph_delete_node(mut refany: RefAny, mut info: CallbackInfo) -> Update {
3187 let Some(mut refany) = refany.downcast_mut::<NodeLocalDataset>() else {
3188 return Update::DoNothing;
3189 };
3190
3191 let node_id = refany.node_id;
3192
3193 let Some(mut backref) = refany.backref.downcast_mut::<NodeGraphLocalDataset>() else {
3194 return Update::DoNothing;
3195 };
3196
3197 let result = match backref.callbacks.on_node_removed.as_ref() {
3198 Some(OnNodeRemoved { callback, refany }) => (callback.cb)(refany.clone(), info, node_id),
3199 None => Update::DoNothing,
3200 };
3201
3202 result
3203}
3204
3205#[allow(clippy::suboptimal_flops)] #[allow(clippy::match_same_arms)] extern "C" fn nodegraph_context_menu_click(mut refany: RefAny, mut info: CallbackInfo) -> Update {
3208 use azul_core::window::CursorPosition;
3209
3210 let Some(mut refany) = refany.downcast_mut::<ContextMenuEntryLocalDataset>() else {
3211 return Update::DoNothing;
3212 };
3213
3214 let new_node_type = refany.node_type;
3215
3216 let Some(node_graph_wrapper_id) = info.get_node_id_of_root_dataset(refany.backref.clone())
3217 else {
3218 return Update::DoNothing;
3219 };
3220
3221 let Some(mut backref) = refany.backref.downcast_mut::<NodeGraphLocalDataset>() else {
3222 return Update::DoNothing;
3223 };
3224
3225 let node_wrapper_offset = info
3226 .get_node_position(node_graph_wrapper_id)
3227 .map_or((0.0, 0.0), |p| (p.x, p.y));
3228
3229 let cursor_in_viewport = match info.get_current_mouse_state().cursor_position {
3230 InWindow(i) => i,
3231 CursorPosition::OutOfWindow(i) => i,
3232 CursorPosition::Uninitialized => LogicalPosition::zero(),
3233 };
3234
3235 let new_node_pos = NodeGraphNodePosition {
3236 x: (cursor_in_viewport.x - node_wrapper_offset.0) * (1.0 / backref.node_graph.scale_factor)
3237 - backref.node_graph.offset.x,
3238 y: (cursor_in_viewport.y - node_wrapper_offset.1) * (1.0 / backref.node_graph.scale_factor)
3239 - backref.node_graph.offset.y,
3240 };
3241
3242 let new_node_id = backref.node_graph.generate_unique_node_id();
3243
3244 let result = match backref.callbacks.on_node_added.as_ref() {
3245 Some(OnNodeAdded { callback, refany }) => (callback.cb)(
3246 refany.clone(),
3247 info,
3248 new_node_type,
3249 new_node_id,
3250 new_node_pos,
3251 ),
3252 None => Update::DoNothing,
3253 };
3254
3255 result
3256}
3257
3258extern "C" fn nodegraph_input_output_connect(mut refany: RefAny, mut info: CallbackInfo) -> Update {
3259 use self::InputOrOutput::{Input, Output};
3260
3261 let Some(mut refany) = refany.downcast_mut::<NodeInputOutputLocalDataset>() else {
3262 return Update::DoNothing;
3263 };
3264
3265 let io_id = refany.io_id;
3266
3267 let Some(mut backref) = refany.backref.downcast_mut::<NodeLocalDataset>() else {
3268 return Update::DoNothing;
3269 };
3270
3271 let node_id = backref.node_id;
3272
3273 let Some(mut backref) = backref.backref.downcast_mut::<NodeGraphLocalDataset>() else {
3274 return Update::DoNothing;
3275 };
3276
3277 let (input_node, input_index, output_node, output_index) =
3278 match backref.last_input_or_output_clicked {
3279 None => {
3280 backref.last_input_or_output_clicked = Some((node_id, io_id));
3281 return Update::DoNothing;
3282 }
3283 Some((prev_node_id, prev_io_id)) => {
3284 match (prev_io_id, io_id) {
3285 (Input(i), Output(o)) => (prev_node_id, i, node_id, o),
3286 (Output(o), Input(i)) => (node_id, i, prev_node_id, o),
3287 _ => {
3288 backref.last_input_or_output_clicked = None;
3290 return Update::DoNothing;
3291 }
3292 }
3293 }
3294 };
3295
3296 match backref.node_graph.connect_input_output(
3298 input_node,
3299 input_index,
3300 output_node,
3301 output_index,
3302 ) {
3303 Ok(()) => {}
3304 Err(e) => {
3305 eprintln!("{e:?}");
3306 backref.last_input_or_output_clicked = None;
3307 return Update::DoNothing;
3308 }
3309 }
3310
3311 let result = match backref.callbacks.on_node_connected.as_ref() {
3312 Some(OnNodeConnected { callback, refany }) => {
3313 let r = (callback.cb)(
3314 refany.clone(),
3315 info,
3316 input_node,
3317 input_index,
3318 output_node,
3319 output_index,
3320 );
3321 backref.last_input_or_output_clicked = None;
3322 r
3323 }
3324 None => Update::DoNothing,
3325 };
3326
3327 result
3328}
3329
3330extern "C" fn nodegraph_input_output_disconnect(mut refany: RefAny, info: CallbackInfo) -> Update {
3331 use self::InputOrOutput::{Input, Output};
3332
3333 let Some(mut refany) = refany.downcast_mut::<NodeInputOutputLocalDataset>() else {
3334 return Update::DoNothing;
3335 };
3336
3337 let io_id = refany.io_id;
3338
3339 let Some(mut backref) = refany.backref.downcast_mut::<NodeLocalDataset>() else {
3340 return Update::DoNothing;
3341 };
3342
3343 let node_id = backref.node_id;
3344
3345 let Some(mut backref) = backref.backref.downcast_mut::<NodeGraphLocalDataset>() else {
3346 return Update::DoNothing;
3347 };
3348
3349 let mut result = Update::DoNothing;
3350 match io_id {
3351 Input(i) => {
3352 result.max_self(
3353 match backref.callbacks.on_node_input_disconnected.as_ref() {
3354 Some(OnNodeInputDisconnected { callback, refany }) => {
3355 (callback.cb)(refany.clone(), info, node_id, i)
3356 }
3357 None => Update::DoNothing,
3358 },
3359 );
3360 }
3361 Output(o) => {
3362 result.max_self(
3363 match backref.callbacks.on_node_output_disconnected.as_ref() {
3364 Some(OnNodeOutputDisconnected { callback, refany }) => {
3365 (callback.cb)(refany.clone(), info, node_id, o)
3366 }
3367 None => Update::DoNothing,
3368 },
3369 );
3370 }
3371 }
3372
3373 result
3374}
3375
3376extern "C" fn nodegraph_on_textinput_focus_lost(
3377 mut refany: RefAny,
3378 info: CallbackInfo,
3379 textinputstate: TextInputState,
3380) -> Update {
3381 let Some(mut refany) = refany.downcast_mut::<NodeFieldLocalDataset>() else {
3382 return Update::DoNothing;
3383 };
3384
3385 let field_idx = refany.field_idx;
3386
3387 let Some(mut node_local_dataset) = refany.backref.downcast_mut::<NodeLocalDataset>() else {
3388 return Update::DoNothing;
3389 };
3390
3391 let node_id = node_local_dataset.node_id;
3392
3393 let Some(mut node_graph) = node_local_dataset
3394 .backref
3395 .downcast_mut::<NodeGraphLocalDataset>()
3396 else {
3397 return Update::DoNothing;
3398 };
3399
3400 let node_type = match node_graph
3401 .node_graph
3402 .nodes
3403 .iter()
3404 .find(|i| i.node_id == node_id)
3405 {
3406 Some(s) => s.node.node_type,
3407 None => return Update::DoNothing,
3408 };
3409
3410 let result = match node_graph.callbacks.on_node_field_edited.as_ref() {
3411 Some(OnNodeFieldEdited { refany, callback }) => (callback.cb)(
3412 refany.clone(),
3413 info,
3414 node_id,
3415 field_idx,
3416 node_type,
3417 NodeTypeFieldValue::TextInput(textinputstate.get_text().into()),
3418 ),
3419 None => Update::DoNothing,
3420 };
3421
3422 result
3423}
3424
3425extern "C" fn nodegraph_on_numberinput_focus_lost(
3426 mut refany: RefAny,
3427 info: CallbackInfo,
3428 numberinputstate: NumberInputState,
3429) -> Update {
3430 let Some(mut refany) = refany.downcast_mut::<NodeFieldLocalDataset>() else {
3431 return Update::DoNothing;
3432 };
3433
3434 let field_idx = refany.field_idx;
3435
3436 let Some(mut node_local_dataset) = refany.backref.downcast_mut::<NodeLocalDataset>() else {
3437 return Update::DoNothing;
3438 };
3439
3440 let node_id = node_local_dataset.node_id;
3441
3442 let Some(mut node_graph) = node_local_dataset
3443 .backref
3444 .downcast_mut::<NodeGraphLocalDataset>()
3445 else {
3446 return Update::DoNothing;
3447 };
3448
3449 let node_type = match node_graph
3450 .node_graph
3451 .nodes
3452 .iter()
3453 .find(|i| i.node_id == node_id)
3454 {
3455 Some(s) => s.node.node_type,
3456 None => return Update::DoNothing,
3457 };
3458
3459 let result = match node_graph.callbacks.on_node_field_edited.as_ref() {
3460 Some(OnNodeFieldEdited { refany, callback }) => (callback.cb)(
3461 refany.clone(),
3462 info,
3463 node_id,
3464 field_idx,
3465 node_type,
3466 NodeTypeFieldValue::NumberInput(numberinputstate.number),
3467 ),
3468 None => Update::DoNothing,
3469 };
3470
3471 result
3472}
3473
3474extern "C" fn nodegraph_on_checkbox_value_changed(
3475 mut refany: RefAny,
3476 info: CallbackInfo,
3477 checkboxinputstate: CheckBoxState,
3478) -> Update {
3479 let Some(mut refany) = refany.downcast_mut::<NodeFieldLocalDataset>() else {
3480 return Update::DoNothing;
3481 };
3482
3483 let field_idx = refany.field_idx;
3484
3485 let Some(mut node_local_dataset) = refany.backref.downcast_mut::<NodeLocalDataset>() else {
3486 return Update::DoNothing;
3487 };
3488
3489 let node_id = node_local_dataset.node_id;
3490
3491 let Some(mut node_graph) = node_local_dataset
3492 .backref
3493 .downcast_mut::<NodeGraphLocalDataset>()
3494 else {
3495 return Update::DoNothing;
3496 };
3497
3498 let node_type = match node_graph
3499 .node_graph
3500 .nodes
3501 .iter()
3502 .find(|i| i.node_id == node_id)
3503 {
3504 Some(s) => s.node.node_type,
3505 None => return Update::DoNothing,
3506 };
3507
3508 let result = match node_graph.callbacks.on_node_field_edited.as_ref() {
3509 Some(OnNodeFieldEdited { refany, callback }) => (callback.cb)(
3510 refany.clone(),
3511 info,
3512 node_id,
3513 field_idx,
3514 node_type,
3515 NodeTypeFieldValue::CheckBox(checkboxinputstate.checked),
3516 ),
3517 None => Update::DoNothing,
3518 };
3519
3520 result
3521}
3522
3523extern "C" fn nodegraph_on_colorinput_value_changed(
3524 mut refany: RefAny,
3525 info: CallbackInfo,
3526 colorinputstate: ColorInputState,
3527) -> Update {
3528 let Some(mut refany) = refany.downcast_mut::<NodeFieldLocalDataset>() else {
3529 return Update::DoNothing;
3530 };
3531
3532 let field_idx = refany.field_idx;
3533
3534 let Some(mut node_local_dataset) = refany.backref.downcast_mut::<NodeLocalDataset>() else {
3535 return Update::DoNothing;
3536 };
3537
3538 let node_id = node_local_dataset.node_id;
3539 let Some(mut node_graph) = node_local_dataset
3540 .backref
3541 .downcast_mut::<NodeGraphLocalDataset>()
3542 else {
3543 return Update::DoNothing;
3544 };
3545
3546 let node_type = match node_graph
3547 .node_graph
3548 .nodes
3549 .iter()
3550 .find(|i| i.node_id == node_id)
3551 {
3552 Some(s) => s.node.node_type,
3553 None => return Update::DoNothing,
3554 };
3555
3556 let result = match node_graph.callbacks.on_node_field_edited.as_ref() {
3557 Some(OnNodeFieldEdited { refany, callback }) => (callback.cb)(
3558 refany.clone(),
3559 info,
3560 node_id,
3561 field_idx,
3562 node_type,
3563 NodeTypeFieldValue::ColorInput(colorinputstate.color),
3564 ),
3565 None => Update::DoNothing,
3566 };
3567
3568 result
3569}
3570
3571extern "C" fn nodegraph_on_fileinput_button_clicked(
3572 mut refany: RefAny,
3573 info: CallbackInfo,
3574 file: FileInputState,
3575) -> Update {
3576 let Some(mut refany) = refany.downcast_mut::<NodeFieldLocalDataset>() else {
3577 return Update::DoNothing;
3578 };
3579
3580 let field_idx = refany.field_idx;
3581
3582 let Some(mut node_local_dataset) = refany.backref.downcast_mut::<NodeLocalDataset>() else {
3583 return Update::DoNothing;
3584 };
3585
3586 let node_id = node_local_dataset.node_id;
3587 let Some(mut node_graph) = node_local_dataset
3588 .backref
3589 .downcast_mut::<NodeGraphLocalDataset>()
3590 else {
3591 return Update::DoNothing;
3592 };
3593
3594 let node_type = match node_graph
3595 .node_graph
3596 .nodes
3597 .iter()
3598 .find(|i| i.node_id == node_id)
3599 {
3600 Some(s) => s.node.node_type,
3601 None => return Update::DoNothing,
3602 };
3603
3604 let result = match node_graph.callbacks.on_node_field_edited.as_ref() {
3606 Some(OnNodeFieldEdited { refany, callback }) => (callback.cb)(
3607 refany.clone(),
3608 info,
3609 node_id,
3610 field_idx,
3611 node_type,
3612 NodeTypeFieldValue::FileInput(file.path),
3613 ),
3614 None => return Update::DoNothing,
3615 };
3616
3617 result
3618}
3619
3620#[cfg(all(test, feature = "std"))]
3621#[allow(clippy::float_cmp, clippy::too_many_lines)]
3622mod autotest_generated {
3623 use std::{
3624 collections::{BTreeMap, HashMap},
3625 sync::{Arc, Mutex},
3626 };
3627
3628 use azul_core::{
3629 dom::{DomId, DomNodeId},
3630 geom::{LogicalRect, OptionLogicalPosition},
3631 gl::OptionGlContextPtr,
3632 hit_test::ScrollPosition,
3633 resources::RendererResources,
3634 styled_dom::{NodeHierarchyItemId, StyledDom},
3635 window::{MonitorVec, RawWindowHandle},
3636 };
3637 use rust_fontconfig::FcFontCache;
3638
3639 use super::*;
3640 #[cfg(feature = "icu")]
3641 use crate::icu::IcuLocalizerHandle;
3642 use crate::{
3643 callbacks::{CallbackChange, CallbackInfoRefData, ExternalSystemCallbacks},
3644 solver3::{display_list::DisplayList, layout_tree::LayoutTree},
3645 window::{DomLayoutResult, LayoutWindow},
3646 window_state::FullWindowState,
3647 };
3648
3649 const TYPE_A: NodeTypeId = NodeTypeId { inner: 1 };
3657 const TYPE_B: NodeTypeId = NodeTypeId { inner: 2 };
3658 const TYPE_UNREGISTERED: NodeTypeId = NodeTypeId { inner: 99 };
3660
3661 const IO_INT: InputOutputTypeId = InputOutputTypeId { inner: 10 };
3662 const IO_FLOAT: InputOutputTypeId = InputOutputTypeId { inner: 20 };
3663 const IO_COLORLESS: InputOutputTypeId = InputOutputTypeId { inner: 77 };
3665
3666 const N1: NodeGraphNodeId = NodeGraphNodeId { inner: 1 };
3667 const N2: NodeGraphNodeId = NodeGraphNodeId { inner: 2 };
3668 const N3: NodeGraphNodeId = NodeGraphNodeId { inner: 3 };
3669 const N4: NodeGraphNodeId = NodeGraphNodeId { inner: 4 };
3670 const MISSING: NodeGraphNodeId = NodeGraphNodeId { inner: 999 };
3672
3673 const EXPECT_NODE_WIDTH: f32 = 250.0;
3677 const EXPECT_V_OFFSET: f32 = 71.0;
3678 const EXPECT_PORT_PITCH: f32 = 25.0; const EXPECT_DOT_HEIGHT: f32 = 15.0;
3680
3681 fn io_types() -> InputOutputTypeIdInfoMapVec {
3682 vec![
3683 InputOutputTypeIdInfoMap {
3684 io_type_id: IO_INT,
3685 io_info: InputOutputInfo {
3686 data_type: AzString::from_const_str("int"),
3687 color: ColorU {
3688 r: 1,
3689 g: 2,
3690 b: 3,
3691 a: 255,
3692 },
3693 },
3694 },
3695 InputOutputTypeIdInfoMap {
3696 io_type_id: IO_FLOAT,
3697 io_info: InputOutputInfo {
3698 data_type: AzString::from_const_str("float"),
3699 color: ColorU {
3700 r: 4,
3701 g: 5,
3702 b: 6,
3703 a: 255,
3704 },
3705 },
3706 },
3707 ]
3708 .into()
3709 }
3710
3711 fn node_types() -> NodeTypeIdInfoMapVec {
3712 vec![
3713 NodeTypeIdInfoMap {
3714 node_type_id: TYPE_A,
3715 node_type_info: NodeTypeInfo {
3716 is_root: true,
3717 node_type_name: AzString::from_const_str("A"),
3718 inputs: vec![IO_INT].into(),
3719 outputs: vec![IO_INT].into(),
3720 },
3721 },
3722 NodeTypeIdInfoMap {
3723 node_type_id: TYPE_B,
3724 node_type_info: NodeTypeInfo {
3725 is_root: false,
3726 node_type_name: AzString::from_const_str("B"),
3727 inputs: vec![IO_FLOAT].into(),
3728 outputs: vec![IO_FLOAT].into(),
3729 },
3730 },
3731 ]
3732 .into()
3733 }
3734
3735 fn mk_node(node_type: NodeTypeId, x: f32, y: f32) -> Node {
3736 Node {
3737 node_type,
3738 position: NodeGraphNodePosition { x, y },
3739 fields: NodeTypeFieldVec::new(),
3740 connect_in: InputConnectionVec::new(),
3741 connect_out: OutputConnectionVec::new(),
3742 }
3743 }
3744
3745 fn graph() -> NodeGraph {
3749 NodeGraph {
3750 node_types: node_types(),
3751 input_output_types: io_types(),
3752 nodes: vec![
3753 NodeIdNodeMap {
3754 node_id: N1,
3755 node: mk_node(TYPE_A, 0.0, 0.0),
3756 },
3757 NodeIdNodeMap {
3758 node_id: N2,
3759 node: mk_node(TYPE_B, 400.0, 100.0),
3760 },
3761 NodeIdNodeMap {
3762 node_id: N3,
3763 node: mk_node(TYPE_A, 800.0, 50.0),
3764 },
3765 NodeIdNodeMap {
3766 node_id: N4,
3767 node: mk_node(TYPE_A, -100.0, 200.0),
3768 },
3769 ]
3770 .into(),
3771 ..NodeGraph::default()
3772 }
3773 }
3774
3775 fn inputs_of(g: &NodeGraph, id: NodeGraphNodeId) -> Vec<(usize, Vec<(u64, usize)>)> {
3777 g.nodes
3778 .iter()
3779 .find(|n| n.node_id == id)
3780 .map_or_else(Vec::new, |n| {
3781 n.node
3782 .connect_in
3783 .iter()
3784 .map(|c| {
3785 (
3786 c.input_index,
3787 c.connects_to
3788 .iter()
3789 .map(|o| (o.node_id.inner, o.output_index))
3790 .collect(),
3791 )
3792 })
3793 .collect()
3794 })
3795 }
3796
3797 fn outputs_of(g: &NodeGraph, id: NodeGraphNodeId) -> Vec<(usize, Vec<(u64, usize)>)> {
3799 g.nodes
3800 .iter()
3801 .find(|n| n.node_id == id)
3802 .map_or_else(Vec::new, |n| {
3803 n.node
3804 .connect_out
3805 .iter()
3806 .map(|c| {
3807 (
3808 c.output_index,
3809 c.connects_to
3810 .iter()
3811 .map(|i| (i.node_id.inner, i.input_index))
3812 .collect(),
3813 )
3814 })
3815 .collect()
3816 })
3817 }
3818
3819 type Wiring = Vec<(u64, Vec<(usize, Vec<(u64, usize)>)>, Vec<(usize, Vec<(u64, usize)>)>)>;
3822 fn wiring(g: &NodeGraph) -> Wiring {
3823 g.nodes
3824 .iter()
3825 .map(|n| {
3826 (
3827 n.node_id.inner,
3828 inputs_of(g, n.node_id),
3829 outputs_of(g, n.node_id),
3830 )
3831 })
3832 .collect()
3833 }
3834
3835 fn force_out_connection(
3839 mut g: NodeGraph,
3840 from: NodeGraphNodeId,
3841 out_idx: usize,
3842 to: NodeGraphNodeId,
3843 in_idx: usize,
3844 ) -> NodeGraph {
3845 if let Some(n) = g.nodes.as_mut().iter_mut().find(|n| n.node_id == from) {
3846 n.node.connect_out.push(OutputConnection {
3847 output_index: out_idx,
3848 connects_to: vec![InputNodeAndIndex {
3849 node_id: to,
3850 input_index: in_idx,
3851 }]
3852 .into(),
3853 });
3854 }
3855 g
3856 }
3857
3858 fn count_nodes(dom: &Dom) -> usize {
3859 1 + dom.children.iter().map(count_nodes).sum::<usize>()
3860 }
3861
3862 fn graph_dataset(g: &NodeGraph) -> RefAny {
3865 RefAny::new(NodeGraphLocalDataset {
3866 node_graph: g.clone(),
3867 last_input_or_output_clicked: None,
3868 active_node_being_dragged: None,
3869 node_connection_marker: RefAny::new(NodeConnectionMarkerDataset {}),
3870 callbacks: g.callbacks.clone(),
3871 })
3872 }
3873
3874 fn dataset_graph(handle: &RefAny) -> NodeGraph {
3876 let mut handle = handle.clone();
3877 let d = handle
3878 .downcast_ref::<NodeGraphLocalDataset>()
3879 .expect("not a NodeGraphLocalDataset");
3880 d.node_graph.clone()
3881 }
3882
3883 fn io_kind(io: InputOrOutput) -> (bool, usize) {
3885 match io {
3886 InputOrOutput::Input(i) => (true, i),
3887 InputOrOutput::Output(o) => (false, o),
3888 }
3889 }
3890
3891 fn pending_click(handle: &RefAny) -> Option<(u64, (bool, usize))> {
3892 let mut handle = handle.clone();
3893 let d = handle
3894 .downcast_ref::<NodeGraphLocalDataset>()
3895 .expect("not a NodeGraphLocalDataset");
3896 d.last_input_or_output_clicked
3897 .map(|(id, io)| (id.inner, io_kind(io)))
3898 }
3899
3900 fn hit_none() -> DomNodeId {
3906 DomNodeId {
3907 dom: DomId::ROOT_ID,
3908 node: NodeHierarchyItemId::NONE,
3909 }
3910 }
3911
3912 fn layout_result(styled_dom: StyledDom) -> DomLayoutResult {
3913 DomLayoutResult {
3914 styled_dom,
3915 layout_tree: LayoutTree {
3916 nodes: Vec::new(),
3917 warm: Vec::new(),
3918 cold: Vec::new(),
3919 root: 0,
3920 dom_to_layout: BTreeMap::new(),
3921 children_arena: Vec::new(),
3922 children_offsets: Vec::new(),
3923 subtree_needs_intrinsic: Vec::new(),
3924 },
3925 calculated_positions: Vec::new(),
3926 viewport: LogicalRect::zero(),
3927 display_list: DisplayList::default(),
3928 scroll_ids: HashMap::new(),
3929 scroll_id_to_node_id: HashMap::new(),
3930 }
3931 }
3932
3933 fn with_info<R>(
3937 styled_dom: StyledDom,
3938 hit: DomNodeId,
3939 f: impl FnOnce(&mut CallbackInfo) -> R,
3940 ) -> (R, Vec<CallbackChange>) {
3941 let mut layout_window =
3942 LayoutWindow::new(FcFontCache::default()).expect("LayoutWindow::new failed");
3943 layout_window
3944 .layout_results
3945 .insert(DomId::ROOT_ID, layout_result(styled_dom));
3946
3947 let renderer_resources = RendererResources::default();
3948 let previous_window_state: Option<FullWindowState> = None;
3949 let current_window_state = FullWindowState::default();
3950 let gl_context = OptionGlContextPtr::None;
3951 let scroll_states: BTreeMap<DomId, BTreeMap<NodeHierarchyItemId, ScrollPosition>> =
3952 BTreeMap::new();
3953 let window_handle = RawWindowHandle::Unsupported;
3954 let system_callbacks = ExternalSystemCallbacks::rust_internal();
3955
3956 let ref_data = CallbackInfoRefData {
3957 layout_window: &layout_window,
3958 renderer_resources: &renderer_resources,
3959 previous_window_state: &previous_window_state,
3960 current_window_state: ¤t_window_state,
3961 gl_context: &gl_context,
3962 current_scroll_manager: &scroll_states,
3963 current_window_handle: &window_handle,
3964 system_callbacks: &system_callbacks,
3965 system_style: Arc::new(azul_css::system::SystemStyle::default()),
3966 monitors: Arc::new(Mutex::new(MonitorVec::from_const_slice(&[]))),
3967 #[cfg(feature = "icu")]
3968 icu_localizer: IcuLocalizerHandle::default(),
3969 ctx: OptionRefAny::None,
3970 };
3971
3972 let changes: Arc<Mutex<Vec<CallbackChange>>> = Arc::new(Mutex::new(Vec::new()));
3973
3974 let mut info = CallbackInfo::new(
3975 &ref_data,
3976 &changes,
3977 hit,
3978 OptionLogicalPosition::None,
3979 OptionLogicalPosition::None,
3980 );
3981
3982 let r = f(&mut info);
3983 let pushed = info.take_changes();
3984 (r, pushed)
3985 }
3986
3987 fn fire(cb: impl FnOnce(CallbackInfo) -> Update) -> Update {
3989 with_info(StyledDom::default(), hit_none(), |info| cb(*info)).0
3990 }
3991
3992 #[derive(Debug, Default)]
3998 struct Log {
3999 removed: Vec<u64>,
4000 added: Vec<(u64, u64, f32, f32)>,
4001 connected: Vec<(u64, usize, u64, usize)>,
4002 input_disconnected: Vec<(u64, usize)>,
4003 output_disconnected: Vec<(u64, usize)>,
4004 edited: Vec<(u64, usize, u64)>,
4006 text_values: Vec<String>,
4007 number_values: Vec<f32>,
4008 bool_values: Vec<bool>,
4009 color_values: Vec<(u8, u8, u8, u8)>,
4010 file_values: Vec<Option<String>>,
4011 }
4012
4013 fn log_of(handle: &RefAny, f: impl FnOnce(&Log)) {
4014 let mut handle = handle.clone();
4015 let l = handle.downcast_ref::<Log>().expect("not a Log");
4016 f(&l);
4017 }
4018
4019 extern "C" fn rec_removed(
4020 mut refany: RefAny,
4021 _info: CallbackInfo,
4022 node_id: NodeGraphNodeId,
4023 ) -> Update {
4024 if let Some(mut l) = refany.downcast_mut::<Log>() {
4025 l.removed.push(node_id.inner);
4026 }
4027 Update::RefreshDom
4028 }
4029
4030 extern "C" fn rec_added(
4031 mut refany: RefAny,
4032 _info: CallbackInfo,
4033 new_node_type: NodeTypeId,
4034 new_node_id: NodeGraphNodeId,
4035 new_node_position: NodeGraphNodePosition,
4036 ) -> Update {
4037 if let Some(mut l) = refany.downcast_mut::<Log>() {
4038 l.added.push((
4039 new_node_type.inner,
4040 new_node_id.inner,
4041 new_node_position.x,
4042 new_node_position.y,
4043 ));
4044 }
4045 Update::RefreshDomAllWindows
4046 }
4047
4048 extern "C" fn rec_connected(
4049 mut refany: RefAny,
4050 _info: CallbackInfo,
4051 input: NodeGraphNodeId,
4052 input_index: usize,
4053 output: NodeGraphNodeId,
4054 output_index: usize,
4055 ) -> Update {
4056 if let Some(mut l) = refany.downcast_mut::<Log>() {
4057 l.connected
4058 .push((input.inner, input_index, output.inner, output_index));
4059 }
4060 Update::RefreshDom
4061 }
4062
4063 extern "C" fn rec_input_disconnected(
4064 mut refany: RefAny,
4065 _info: CallbackInfo,
4066 input: NodeGraphNodeId,
4067 input_index: usize,
4068 ) -> Update {
4069 if let Some(mut l) = refany.downcast_mut::<Log>() {
4070 l.input_disconnected.push((input.inner, input_index));
4071 }
4072 Update::RefreshDom
4073 }
4074
4075 extern "C" fn rec_output_disconnected(
4076 mut refany: RefAny,
4077 _info: CallbackInfo,
4078 output: NodeGraphNodeId,
4079 output_index: usize,
4080 ) -> Update {
4081 if let Some(mut l) = refany.downcast_mut::<Log>() {
4082 l.output_disconnected.push((output.inner, output_index));
4083 }
4084 Update::RefreshDomAllWindows
4085 }
4086
4087 extern "C" fn rec_field_edited(
4088 mut refany: RefAny,
4089 _info: CallbackInfo,
4090 node_id: NodeGraphNodeId,
4091 field_id: usize,
4092 node_type: NodeTypeId,
4093 new_value: NodeTypeFieldValue,
4094 ) -> Update {
4095 if let Some(mut l) = refany.downcast_mut::<Log>() {
4096 l.edited.push((node_id.inner, field_id, node_type.inner));
4097 match new_value {
4098 NodeTypeFieldValue::TextInput(s) => l.text_values.push(s.as_str().to_string()),
4099 NodeTypeFieldValue::NumberInput(n) => l.number_values.push(n),
4100 NodeTypeFieldValue::CheckBox(b) => l.bool_values.push(b),
4101 NodeTypeFieldValue::ColorInput(c) => l.color_values.push((c.r, c.g, c.b, c.a)),
4102 NodeTypeFieldValue::FileInput(p) => l
4103 .file_values
4104 .push(p.as_ref().map(|s| s.as_str().to_string())),
4105 }
4106 }
4107 Update::RefreshDom
4108 }
4109
4110 fn graph_with_log() -> (NodeGraph, RefAny) {
4112 let log = RefAny::new(Log::default());
4113 let mut g = graph();
4114 g.callbacks = NodeGraphCallbacks {
4115 on_node_removed: OptionOnNodeRemoved::Some(OnNodeRemoved {
4116 refany: log.clone(),
4117 callback: OnNodeRemovedCallback {
4118 cb: rec_removed,
4119 ctx: OptionRefAny::None,
4120 },
4121 }),
4122 on_node_added: OptionOnNodeAdded::Some(OnNodeAdded {
4123 refany: log.clone(),
4124 callback: OnNodeAddedCallback {
4125 cb: rec_added,
4126 ctx: OptionRefAny::None,
4127 },
4128 }),
4129 on_node_connected: OptionOnNodeConnected::Some(OnNodeConnected {
4130 refany: log.clone(),
4131 callback: OnNodeConnectedCallback {
4132 cb: rec_connected,
4133 ctx: OptionRefAny::None,
4134 },
4135 }),
4136 on_node_input_disconnected: OptionOnNodeInputDisconnected::Some(
4137 OnNodeInputDisconnected {
4138 refany: log.clone(),
4139 callback: OnNodeInputDisconnectedCallback {
4140 cb: rec_input_disconnected,
4141 ctx: OptionRefAny::None,
4142 },
4143 },
4144 ),
4145 on_node_output_disconnected: OptionOnNodeOutputDisconnected::Some(
4146 OnNodeOutputDisconnected {
4147 refany: log.clone(),
4148 callback: OnNodeOutputDisconnectedCallback {
4149 cb: rec_output_disconnected,
4150 ctx: OptionRefAny::None,
4151 },
4152 },
4153 ),
4154 on_node_field_edited: OptionOnNodeFieldEdited::Some(OnNodeFieldEdited {
4155 refany: log.clone(),
4156 callback: OnNodeFieldEditedCallback {
4157 cb: rec_field_edited,
4158 ctx: OptionRefAny::None,
4159 },
4160 }),
4161 ..NodeGraphCallbacks::default()
4162 };
4163 (g, log)
4164 }
4165
4166 #[test]
4171 fn generate_unique_node_id_on_an_empty_graph_is_one_not_zero() {
4172 assert_eq!(NodeGraph::default().generate_unique_node_id().inner, 1);
4175 }
4176
4177 #[test]
4178 fn generate_unique_node_id_returns_max_plus_one_and_ignores_gaps_and_order() {
4179 let g = NodeGraph {
4182 nodes: vec![
4183 NodeIdNodeMap {
4184 node_id: NodeGraphNodeId { inner: 7 },
4185 node: mk_node(TYPE_A, 0.0, 0.0),
4186 },
4187 NodeIdNodeMap {
4188 node_id: NodeGraphNodeId { inner: 0 },
4189 node: mk_node(TYPE_A, 0.0, 0.0),
4190 },
4191 NodeIdNodeMap {
4192 node_id: NodeGraphNodeId { inner: 3 },
4193 node: mk_node(TYPE_A, 0.0, 0.0),
4194 },
4195 ]
4196 .into(),
4197 ..Default::default()
4198 };
4199 assert_eq!(g.generate_unique_node_id().inner, 8);
4200 }
4201
4202 #[test]
4203 fn generate_unique_node_id_tolerates_duplicate_ids_in_the_graph() {
4204 let g = NodeGraph {
4205 nodes: vec![
4206 NodeIdNodeMap {
4207 node_id: N2,
4208 node: mk_node(TYPE_A, 0.0, 0.0),
4209 },
4210 NodeIdNodeMap {
4211 node_id: N2,
4212 node: mk_node(TYPE_A, 0.0, 0.0),
4213 },
4214 ]
4215 .into(),
4216 ..Default::default()
4217 };
4218 assert_eq!(g.generate_unique_node_id().inner, 3);
4219 }
4220
4221 #[test]
4222 fn generate_unique_node_id_saturates_instead_of_overflowing_at_u64_max() {
4223 let mut g = NodeGraph {
4228 nodes: vec![NodeIdNodeMap {
4229 node_id: NodeGraphNodeId { inner: u64::MAX },
4230 node: mk_node(TYPE_A, 0.0, 0.0),
4231 }]
4232 .into(),
4233 ..Default::default()
4234 };
4235 let id = g.generate_unique_node_id();
4236 assert_eq!(id.inner, u64::MAX);
4237 assert!(
4238 g.nodes.iter().any(|n| n.node_id == id),
4239 "at u64::MAX the generated id collides — documented saturation, not wraparound",
4240 );
4241
4242 g.nodes = vec![NodeIdNodeMap {
4244 node_id: NodeGraphNodeId {
4245 inner: u64::MAX - 1,
4246 },
4247 node: mk_node(TYPE_A, 0.0, 0.0),
4248 }]
4249 .into();
4250 assert_eq!(g.generate_unique_node_id().inner, u64::MAX);
4251 }
4252
4253 #[test]
4254 fn generate_unique_node_id_is_pure_and_repeats_until_the_node_is_inserted() {
4255 let g = graph();
4256 let first = g.generate_unique_node_id();
4257 assert_eq!(first, g.generate_unique_node_id());
4258 assert_eq!(first.inner, 5); }
4260
4261 const ALL_ERRORS: [NodeGraphError; 4] = [
4266 NodeGraphError::NodeMimeTypeMismatch,
4267 NodeGraphError::NodeInvalidIndex,
4268 NodeGraphError::NodeInvalidNode,
4269 NodeGraphError::NoRootNode,
4270 ];
4271
4272 #[test]
4273 fn node_graph_error_display_is_non_empty_ascii_and_single_line() {
4274 for e in ALL_ERRORS {
4275 let s = format!("{e}");
4276 assert!(!s.is_empty(), "{e:?} formatted to the empty string");
4277 assert!(!s.contains('\n'), "{e:?} formatted to a multi-line string");
4278 assert!(s.is_ascii(), "{e:?} formatted to non-ascii: {s}");
4279 }
4280 }
4281
4282 #[test]
4283 fn node_graph_error_display_distinguishes_every_variant() {
4284 let mut seen: Vec<String> = ALL_ERRORS.iter().map(|e| format!("{e}")).collect();
4287 seen.sort();
4288 seen.dedup();
4289 assert_eq!(seen.len(), ALL_ERRORS.len());
4290 }
4291
4292 #[test]
4293 fn node_graph_error_display_survives_width_precision_and_fill_specifiers() {
4294 for e in ALL_ERRORS {
4297 assert!(!format!("{e:>80}").is_empty());
4298 assert!(!format!("{e:*^3}").is_empty());
4299 assert!(!format!("{e:.1}").is_empty());
4300 assert!(!format!("{e:?}").is_empty());
4301 }
4302 }
4303
4304 #[test]
4305 fn node_graph_error_debug_and_display_are_both_usable_and_differ_in_style() {
4306 for e in ALL_ERRORS {
4309 assert_ne!(format!("{e:?}"), format!("{e}"));
4310 }
4311 }
4312
4313 fn summary(g: &NodeGraph) -> (usize, usize, usize, bool, f32, f32, f32, String) {
4319 (
4320 g.node_types.len(),
4321 g.input_output_types.len(),
4322 g.nodes.len(),
4323 g.allow_multiple_root_nodes,
4324 g.offset.x,
4325 g.offset.y,
4326 g.scale_factor,
4327 g.add_node_str.as_str().to_string(),
4328 )
4329 }
4330
4331 fn distinctive() -> NodeGraph {
4332 NodeGraph {
4333 allow_multiple_root_nodes: true,
4334 offset: LogicalPosition { x: -3.5, y: 12.25 },
4335 scale_factor: 2.5,
4336 add_node_str: AzString::from_const_str("Ajouter un nœud"),
4337 ..graph()
4338 }
4339 }
4340
4341 #[test]
4342 fn swap_with_default_hands_back_the_old_value_and_leaves_a_default_behind() {
4343 let mut g = distinctive();
4344 let expected = summary(&g);
4345
4346 let taken = g.swap_with_default();
4347
4348 assert_eq!(summary(&taken), expected);
4349 assert_eq!(summary(&g), summary(&NodeGraph::default()));
4350 }
4351
4352 #[test]
4353 fn swap_with_default_round_trips_a_graph_through_two_owners() {
4354 let mut a = distinctive();
4356 let expected = summary(&a);
4357
4358 let mut b = a.swap_with_default(); let c = b.swap_with_default(); assert_eq!(summary(&c), expected);
4362 assert_eq!(summary(&b), summary(&NodeGraph::default()));
4363 assert_eq!(summary(&a), summary(&NodeGraph::default()));
4364 }
4365
4366 #[test]
4367 fn swap_with_default_on_an_already_default_graph_is_a_no_op() {
4368 let mut g = NodeGraph::default();
4369 let taken = g.swap_with_default();
4370 assert_eq!(summary(&taken), summary(&NodeGraph::default()));
4371 assert_eq!(summary(&g), summary(&NodeGraph::default()));
4372 }
4373
4374 #[test]
4375 fn swap_with_default_preserves_non_finite_offsets_and_scale_verbatim() {
4376 let mut g = NodeGraph {
4379 offset: LogicalPosition {
4380 x: f32::INFINITY,
4381 y: f32::NEG_INFINITY,
4382 },
4383 scale_factor: f32::NAN,
4384 ..NodeGraph::default()
4385 };
4386 let taken = g.swap_with_default();
4387 assert!(taken.offset.x.is_infinite() && taken.offset.x.is_sign_positive());
4388 assert!(taken.offset.y.is_infinite() && taken.offset.y.is_sign_negative());
4389 assert!(taken.scale_factor.is_nan());
4390 assert_eq!(g.scale_factor, 1.0);
4391 }
4392
4393 #[test]
4394 fn swap_with_default_moves_the_connections_not_just_the_node_list() {
4395 let mut g = graph();
4396 g.connect_input_output(N3, 0, N1, 0).expect("legal A->A wire");
4397 let before = wiring(&g);
4398
4399 let taken = g.swap_with_default();
4400
4401 assert_eq!(wiring(&taken), before);
4402 assert!(g.nodes.is_empty());
4403 }
4404
4405 #[test]
4410 fn verify_nodetype_match_accepts_matching_types_at_index_zero() {
4411 let g = graph();
4412 assert_eq!(g.verify_nodetype_match(N1, 0, N3, 0), Ok(()));
4413 }
4414
4415 #[test]
4416 fn verify_nodetype_match_rejects_a_type_mismatch() {
4417 let g = graph();
4418 assert_eq!(
4420 g.verify_nodetype_match(N1, 0, N2, 0),
4421 Err(NodeGraphError::NodeMimeTypeMismatch)
4422 );
4423 }
4424
4425 #[test]
4426 fn verify_nodetype_match_reports_a_missing_node_on_either_side() {
4427 let g = graph();
4428 assert_eq!(
4429 g.verify_nodetype_match(MISSING, 0, N3, 0),
4430 Err(NodeGraphError::NodeInvalidNode)
4431 );
4432 assert_eq!(
4433 g.verify_nodetype_match(N1, 0, MISSING, 0),
4434 Err(NodeGraphError::NodeInvalidNode)
4435 );
4436 }
4437
4438 #[test]
4439 fn verify_nodetype_match_reports_a_node_whose_type_is_not_registered() {
4440 let mut g = graph();
4441 g.nodes.push(NodeIdNodeMap {
4442 node_id: NodeGraphNodeId { inner: 50 },
4443 node: mk_node(TYPE_UNREGISTERED, 0.0, 0.0),
4444 });
4445 assert_eq!(
4446 g.verify_nodetype_match(NodeGraphNodeId { inner: 50 }, 0, N3, 0),
4447 Err(NodeGraphError::NodeInvalidNode)
4448 );
4449 assert_eq!(
4450 g.verify_nodetype_match(N1, 0, NodeGraphNodeId { inner: 50 }, 0),
4451 Err(NodeGraphError::NodeInvalidNode)
4452 );
4453 }
4454
4455 #[test]
4456 fn verify_nodetype_match_rejects_out_of_range_port_indices() {
4457 let g = graph();
4458 assert_eq!(
4461 g.verify_nodetype_match(N1, 1, N3, 0),
4462 Err(NodeGraphError::NodeInvalidIndex)
4463 );
4464 assert_eq!(
4465 g.verify_nodetype_match(N1, 0, N3, 1),
4466 Err(NodeGraphError::NodeInvalidIndex)
4467 );
4468 }
4469
4470 #[test]
4471 fn verify_nodetype_match_does_not_panic_at_usize_max_indices() {
4472 let g = graph();
4474 assert_eq!(
4475 g.verify_nodetype_match(N1, usize::MAX, N3, 0),
4476 Err(NodeGraphError::NodeInvalidIndex)
4477 );
4478 assert_eq!(
4479 g.verify_nodetype_match(N1, 0, N3, usize::MAX),
4480 Err(NodeGraphError::NodeInvalidIndex)
4481 );
4482 assert_eq!(
4483 g.verify_nodetype_match(N1, usize::MAX, N3, usize::MAX),
4484 Err(NodeGraphError::NodeInvalidIndex)
4485 );
4486 }
4487
4488 #[test]
4489 fn verify_nodetype_match_checks_nodes_before_indices() {
4490 let g = graph();
4493 assert_eq!(
4494 g.verify_nodetype_match(MISSING, usize::MAX, N3, usize::MAX),
4495 Err(NodeGraphError::NodeInvalidNode)
4496 );
4497 }
4498
4499 #[test]
4500 fn verify_nodetype_match_allows_a_node_to_be_wired_to_itself() {
4501 let g = graph();
4503 assert_eq!(g.verify_nodetype_match(N1, 0, N1, 0), Ok(()));
4504 }
4505
4506 #[test]
4507 fn verify_nodetype_match_does_not_mutate_the_graph() {
4508 let g = graph();
4509 let before = wiring(&g);
4510 let _ = g.verify_nodetype_match(N1, 0, N3, 0);
4511 let _ = g.verify_nodetype_match(MISSING, usize::MAX, N2, 9);
4512 assert_eq!(wiring(&g), before);
4513 }
4514
4515 #[test]
4520 fn connect_input_output_wires_both_directions_at_index_zero() {
4521 let mut g = graph();
4522 assert_eq!(g.connect_input_output(N3, 0, N1, 0), Ok(()));
4523
4524 assert_eq!(inputs_of(&g, N3), vec![(0, vec![(N1.inner, 0)])]);
4525 assert_eq!(outputs_of(&g, N1), vec![(0, vec![(N3.inner, 0)])]);
4526 assert!(outputs_of(&g, N3).is_empty());
4528 assert!(inputs_of(&g, N1).is_empty());
4529 }
4530
4531 #[test]
4532 fn connect_input_output_rejects_a_mime_type_mismatch_without_mutating() {
4533 let mut g = graph();
4534 let before = wiring(&g);
4535 assert_eq!(
4536 g.connect_input_output(N2, 0, N1, 0),
4537 Err(NodeGraphError::NodeMimeTypeMismatch)
4538 );
4539 assert_eq!(wiring(&g), before, "a rejected connect must be atomic");
4540 }
4541
4542 #[test]
4543 fn connect_input_output_rejects_missing_nodes_without_mutating() {
4544 for (input, output) in [(MISSING, N1), (N3, MISSING), (MISSING, MISSING)] {
4545 let mut g = graph();
4546 let before = wiring(&g);
4547 assert_eq!(
4548 g.connect_input_output(input, 0, output, 0),
4549 Err(NodeGraphError::NodeInvalidNode)
4550 );
4551 assert_eq!(wiring(&g), before);
4552 }
4553 }
4554
4555 #[test]
4556 fn connect_input_output_rejects_out_of_range_and_usize_max_indices() {
4557 for (in_idx, out_idx) in [
4558 (1_usize, 0_usize),
4559 (0, 1),
4560 (usize::MAX, 0),
4561 (0, usize::MAX),
4562 (usize::MAX, usize::MAX),
4563 ] {
4564 let mut g = graph();
4565 let before = wiring(&g);
4566 assert_eq!(
4567 g.connect_input_output(N3, in_idx, N1, out_idx),
4568 Err(NodeGraphError::NodeInvalidIndex),
4569 "in={in_idx} out={out_idx}",
4570 );
4571 assert_eq!(wiring(&g), before);
4572 }
4573 }
4574
4575 #[test]
4576 fn connect_input_output_appends_to_an_existing_port_rather_than_replacing_it() {
4577 let mut g = graph();
4579 g.connect_input_output(N4, 0, N1, 0).expect("N1 -> N4");
4580 g.connect_input_output(N4, 0, N3, 0).expect("N3 -> N4");
4581
4582 assert_eq!(
4583 inputs_of(&g, N4),
4584 vec![(0, vec![(N1.inner, 0), (N3.inner, 0)])],
4585 "the second wire must not overwrite the first",
4586 );
4587 assert_eq!(outputs_of(&g, N1), vec![(0, vec![(N4.inner, 0)])]);
4588 assert_eq!(outputs_of(&g, N3), vec![(0, vec![(N4.inner, 0)])]);
4589 }
4590
4591 #[test]
4592 fn connect_input_output_records_a_duplicate_wire_twice() {
4593 let mut g = graph();
4596 g.connect_input_output(N3, 0, N1, 0).expect("first");
4597 g.connect_input_output(N3, 0, N1, 0).expect("second");
4598
4599 assert_eq!(inputs_of(&g, N3), vec![(0, vec![(N1.inner, 0), (N1.inner, 0)])]);
4600 assert_eq!(outputs_of(&g, N1), vec![(0, vec![(N3.inner, 0), (N3.inner, 0)])]);
4601 }
4602
4603 #[test]
4604 fn connect_input_output_permits_a_self_loop() {
4605 let mut g = graph();
4607 assert_eq!(g.connect_input_output(N1, 0, N1, 0), Ok(()));
4608 assert_eq!(inputs_of(&g, N1), vec![(0, vec![(N1.inner, 0)])]);
4609 assert_eq!(outputs_of(&g, N1), vec![(0, vec![(N1.inner, 0)])]);
4610 }
4611
4612 #[test]
4617 fn disconnect_input_round_trips_a_single_connection() {
4618 let mut g = graph();
4620 let before = wiring(&g);
4621
4622 g.connect_input_output(N3, 0, N1, 0).expect("connect");
4623 assert_ne!(wiring(&g), before);
4624
4625 assert_eq!(g.disconnect_input(N3, 0), Ok(()));
4626 assert_eq!(wiring(&g), before);
4627 }
4628
4629 #[test]
4630 fn disconnect_input_reports_a_missing_node() {
4631 let mut g = graph();
4632 assert_eq!(
4633 g.disconnect_input(MISSING, 0),
4634 Err(NodeGraphError::NodeInvalidNode)
4635 );
4636 }
4637
4638 #[test]
4639 fn disconnect_input_on_an_unconnected_port_is_ok_and_changes_nothing() {
4640 let mut g = graph();
4641 let before = wiring(&g);
4642 assert_eq!(g.disconnect_input(N3, 0), Ok(()));
4643 assert_eq!(wiring(&g), before);
4644 }
4645
4646 #[test]
4647 fn disconnect_input_at_usize_max_is_ok_rather_than_invalid_index() {
4648 let mut g = graph();
4651 let before = wiring(&g);
4652 assert_eq!(g.disconnect_input(N3, usize::MAX), Ok(()));
4653 assert_eq!(wiring(&g), before);
4654 }
4655
4656 #[test]
4657 fn disconnect_input_clears_every_source_feeding_that_port() {
4658 let mut g = graph();
4659 let before = wiring(&g);
4660 g.connect_input_output(N4, 0, N1, 0).expect("N1 -> N4");
4661 g.connect_input_output(N4, 0, N3, 0).expect("N3 -> N4");
4662
4663 assert_eq!(g.disconnect_input(N4, 0), Ok(()));
4664 assert_eq!(
4665 wiring(&g),
4666 before,
4667 "both upstream ports must be released, not just the first",
4668 );
4669 }
4670
4671 #[test]
4672 fn disconnect_input_orphans_a_sibling_sharing_the_same_output_port() {
4673 let mut g = graph();
4680 g.connect_input_output(N3, 0, N1, 0).expect("N1 -> N3");
4681 g.connect_input_output(N4, 0, N1, 0).expect("N1 -> N4");
4682 assert_eq!(
4683 outputs_of(&g, N1),
4684 vec![(0, vec![(N3.inner, 0), (N4.inner, 0)])]
4685 );
4686
4687 assert_eq!(g.disconnect_input(N3, 0), Ok(()));
4688
4689 assert!(inputs_of(&g, N3).is_empty(), "the requested edge is gone");
4690 assert_eq!(
4691 inputs_of(&g, N4),
4692 vec![(0, vec![(N1.inner, 0)])],
4693 "N4 still believes it is connected to N1",
4694 );
4695 assert!(
4696 outputs_of(&g, N1).is_empty(),
4697 "...but N1 no longer lists N4 — the collateral damage this test pins down",
4698 );
4699 }
4700
4701 #[test]
4706 fn disconnect_output_round_trips_a_single_connection() {
4707 let mut g = graph();
4708 let before = wiring(&g);
4709
4710 g.connect_input_output(N3, 0, N1, 0).expect("connect");
4711 assert_eq!(g.disconnect_output(N1, 0), Ok(()));
4712
4713 assert_eq!(wiring(&g), before);
4714 }
4715
4716 #[test]
4717 fn disconnect_output_reports_a_missing_node() {
4718 let mut g = graph();
4719 assert_eq!(
4720 g.disconnect_output(MISSING, 0),
4721 Err(NodeGraphError::NodeInvalidNode)
4722 );
4723 }
4724
4725 #[test]
4726 fn disconnect_output_on_an_unconnected_port_is_ok_and_changes_nothing() {
4727 let mut g = graph();
4728 let before = wiring(&g);
4729 assert_eq!(g.disconnect_output(N1, 0), Ok(()));
4730 assert_eq!(wiring(&g), before);
4731 }
4732
4733 #[test]
4734 fn disconnect_output_at_usize_max_is_ok_rather_than_invalid_index() {
4735 let mut g = graph();
4736 let before = wiring(&g);
4737 assert_eq!(g.disconnect_output(N1, usize::MAX), Ok(()));
4738 assert_eq!(wiring(&g), before);
4739 }
4740
4741 #[test]
4742 fn disconnect_output_releases_every_downstream_input_it_fed() {
4743 let mut g = graph();
4746 let before = wiring(&g);
4747 g.connect_input_output(N3, 0, N1, 0).expect("N1 -> N3");
4748 g.connect_input_output(N4, 0, N1, 0).expect("N1 -> N4");
4749
4750 assert_eq!(g.disconnect_output(N1, 0), Ok(()));
4751 assert_eq!(wiring(&g), before, "no dangling back-reference may survive");
4752 }
4753
4754 #[test]
4755 fn disconnect_output_of_a_self_loop_leaves_no_residue() {
4756 let mut g = graph();
4757 let before = wiring(&g);
4758 g.connect_input_output(N1, 0, N1, 0).expect("self loop");
4759 assert_eq!(g.disconnect_output(N1, 0), Ok(()));
4760 assert_eq!(wiring(&g), before);
4761 }
4762
4763 fn connection(out: NodeGraphNodeId, out_idx: usize, inn: NodeGraphNodeId, in_idx: usize)
4768 -> ConnectionLocalDataset {
4769 ConnectionLocalDataset {
4770 out_node_id: out,
4771 out_idx,
4772 in_node_id: inn,
4773 in_idx,
4774 swap_vert: true,
4776 swap_horz: true,
4777 color: ColorU {
4778 r: 0,
4779 g: 0,
4780 b: 0,
4781 a: 0,
4782 },
4783 }
4784 }
4785
4786 #[test]
4787 fn get_rect_returns_none_for_a_dangling_endpoint() {
4788 let g = graph();
4789 assert!(get_rect(&g, connection(MISSING, 0, N3, 0)).is_none());
4790 assert!(get_rect(&g, connection(N1, 0, MISSING, 0)).is_none());
4791 assert!(get_rect(&g, connection(MISSING, 0, MISSING, 0)).is_none());
4792 }
4793
4794 #[test]
4795 fn get_rect_computes_the_bounding_box_of_the_two_ports() {
4796 let g = graph();
4798 let (rect, swap_vert, swap_horz) =
4799 get_rect(&g, connection(N1, 0, N3, 0)).expect("both nodes exist");
4800
4801 let x_out = 0.0 + EXPECT_NODE_WIDTH;
4802 let y_out = 0.0 + EXPECT_V_OFFSET;
4803 let x_in = 800.0;
4804 let y_in = 50.0 + EXPECT_V_OFFSET;
4805
4806 assert_eq!(rect.origin.x, x_out.min(x_in));
4807 assert_eq!(rect.origin.y, y_out.min(y_in));
4808 assert_eq!(rect.size.width, (x_in - x_out).abs());
4809 assert_eq!(rect.size.height, (y_in - y_out).abs() + EXPECT_DOT_HEIGHT);
4810 assert!(swap_vert, "the input port sits below the output port");
4811 assert!(!swap_horz, "the input node is to the right of the output");
4812 }
4813
4814 #[test]
4815 fn get_rect_recomputes_the_swap_flags_and_ignores_the_ones_it_was_handed() {
4816 let g = graph();
4819 let (_, swap_vert, swap_horz) = get_rect(&g, connection(N3, 0, N1, 0)).expect("exists");
4821 assert!(!swap_vert);
4822 assert!(swap_horz);
4823 }
4824
4825 #[test]
4826 fn get_rect_height_is_never_below_the_connection_dot() {
4827 let mut g = graph();
4830 g.nodes.as_mut()[2].node.position = NodeGraphNodePosition { x: 800.0, y: 0.0 };
4831 let (rect, _, _) = get_rect(&g, connection(N1, 0, N3, 0)).expect("exists");
4832 assert_eq!(rect.size.height, EXPECT_DOT_HEIGHT);
4833 }
4834
4835 #[test]
4836 fn get_rect_port_index_shifts_the_endpoint_by_a_fixed_pitch() {
4837 let g = graph();
4841 let (base, _, _) = get_rect(&g, connection(N1, 0, N3, 0)).expect("exists");
4842 let (shifted, _, _) = get_rect(&g, connection(N1, 0, N3, 3)).expect("exists");
4843
4844 assert_eq!(shifted.origin.y, base.origin.y);
4845 assert_eq!(
4846 shifted.size.height - base.size.height,
4847 3.0 * EXPECT_PORT_PITCH,
4848 );
4849 }
4850
4851 #[test]
4852 fn get_rect_stays_finite_at_usize_max_port_indices() {
4853 let g = graph();
4856 let (rect, _, _) = get_rect(&g, connection(N1, usize::MAX, N3, usize::MAX))
4857 .expect("both nodes exist");
4858 assert!(rect.origin.y.is_finite(), "y = {}", rect.origin.y);
4859 assert!(rect.size.height.is_finite(), "h = {}", rect.size.height);
4860 assert!(rect.size.width.is_finite());
4861 assert!(rect.size.height >= EXPECT_DOT_HEIGHT);
4862 }
4863
4864 #[test]
4865 fn get_rect_with_a_nan_position_yields_nan_extent_but_a_finite_origin() {
4866 let mut g = graph();
4869 g.nodes.as_mut()[2].node.position = NodeGraphNodePosition {
4870 x: f32::NAN,
4871 y: f32::NAN,
4872 };
4873 let (rect, swap_vert, swap_horz) = get_rect(&g, connection(N1, 0, N3, 0)).expect("exists");
4874
4875 assert!(rect.size.width.is_nan());
4876 assert!(rect.size.height.is_nan());
4877 assert_eq!(rect.origin.x, EXPECT_NODE_WIDTH);
4878 assert_eq!(rect.origin.y, EXPECT_V_OFFSET);
4879 assert!(!swap_vert);
4881 assert!(!swap_horz);
4882 }
4883
4884 #[test]
4885 fn get_rect_with_infinite_positions_does_not_panic() {
4886 let mut g = graph();
4887 g.nodes.as_mut()[2].node.position = NodeGraphNodePosition {
4888 x: f32::INFINITY,
4889 y: f32::NEG_INFINITY,
4890 };
4891 let (rect, swap_vert, swap_horz) = get_rect(&g, connection(N1, 0, N3, 0)).expect("exists");
4892 assert!(rect.size.width.is_infinite());
4893 assert!(rect.size.height.is_infinite());
4894 assert!(!swap_vert, "-inf is not above the output port");
4895 assert!(!swap_horz, "+inf is not left of the output port");
4896
4897 g.nodes.as_mut()[0].node.position = NodeGraphNodePosition {
4899 x: f32::INFINITY,
4900 y: f32::NEG_INFINITY,
4901 };
4902 let (rect, _, _) = get_rect(&g, connection(N1, 0, N3, 0)).expect("exists");
4903 assert!(rect.size.width.is_nan());
4904 }
4905
4906 #[test]
4907 fn get_rect_is_a_pure_query() {
4908 let g = graph();
4909 let before = wiring(&g);
4910 let _ = get_rect(&g, connection(N1, usize::MAX, N3, 0));
4911 assert_eq!(wiring(&g), before);
4912 }
4913
4914 fn node_dataset(g: &NodeGraph, id: NodeGraphNodeId) -> NodeLocalDataset {
4919 NodeLocalDataset {
4920 node_id: id,
4921 backref: graph_dataset(g),
4922 }
4923 }
4924
4925 fn render_one(g: &NodeGraph, id: NodeGraphNodeId, offset: (f32, f32), scale: f32) -> Dom {
4926 let n = g
4927 .nodes
4928 .iter()
4929 .find(|n| n.node_id == id)
4930 .expect("node in fixture");
4931 let ty = g
4932 .node_types
4933 .iter()
4934 .find(|t| t.node_type_id == n.node.node_type)
4935 .expect("type in fixture");
4936 render_node(
4937 &n.node,
4938 offset,
4939 &ty.node_type_info,
4940 node_dataset(g, id),
4941 scale,
4942 )
4943 }
4944
4945 #[test]
4946 fn render_node_produces_a_single_wrapper_child_carrying_the_dataset() {
4947 let g = graph();
4948 let dom = render_one(&g, N1, (0.0, 0.0), 1.0);
4949 assert_eq!(dom.children.len(), 1);
4950 let inner = &dom.children.as_slice()[0];
4951 let mut ds = inner
4952 .root
4953 .get_dataset()
4954 .cloned()
4955 .expect("the node body must carry its NodeLocalDataset");
4956 assert!(ds.downcast_ref::<NodeLocalDataset>().is_some());
4957 }
4958
4959 #[test]
4960 fn render_node_survives_every_pathological_scale_factor() {
4961 let g = graph();
4964 let baseline = count_nodes(&render_one(&g, N1, (0.0, 0.0), 1.0));
4965 for scale in [
4966 0.0,
4967 -0.0,
4968 -1.0,
4969 1e-30,
4970 f32::MAX,
4971 f32::MIN,
4972 f32::EPSILON,
4973 f32::INFINITY,
4974 f32::NEG_INFINITY,
4975 f32::NAN,
4976 ] {
4977 let dom = render_one(&g, N1, (0.0, 0.0), scale);
4978 assert_eq!(
4979 count_nodes(&dom),
4980 baseline,
4981 "scale {scale} changed the node structure",
4982 );
4983 }
4984 }
4985
4986 #[test]
4987 fn render_node_survives_every_pathological_graph_offset() {
4988 let g = graph();
4989 let baseline = count_nodes(&render_one(&g, N1, (0.0, 0.0), 1.0));
4990 for offset in [
4991 (f32::NAN, f32::NAN),
4992 (f32::INFINITY, f32::NEG_INFINITY),
4993 (f32::MAX, f32::MIN),
4994 (-1e30, 1e30),
4995 ] {
4996 assert_eq!(count_nodes(&render_one(&g, N1, offset, 1.0)), baseline);
4997 }
4998 }
4999
5000 #[test]
5001 fn render_node_survives_a_node_positioned_at_nan_and_infinity() {
5002 let mut g = graph();
5003 g.nodes.as_mut()[0].node.position = NodeGraphNodePosition {
5004 x: f32::NAN,
5005 y: f32::INFINITY,
5006 };
5007 let dom = render_one(&g, N1, (0.0, 0.0), 1.0);
5008 assert!(count_nodes(&dom) > 1);
5009 }
5010
5011 #[test]
5012 fn render_node_drops_all_ports_when_the_backref_is_not_a_node_graph_dataset() {
5013 let g = graph();
5016 let n = g.nodes.iter().find(|n| n.node_id == N1).expect("fixture");
5017 let ty = g
5018 .node_types
5019 .iter()
5020 .find(|t| t.node_type_id == TYPE_A)
5021 .expect("fixture");
5022
5023 let broken = render_node(
5024 &n.node,
5025 (0.0, 0.0),
5026 &ty.node_type_info,
5027 NodeLocalDataset {
5028 node_id: N1,
5029 backref: RefAny::new(0xDEAD_BEEF_u32),
5030 },
5031 1.0,
5032 );
5033 let intact = render_one(&g, N1, (0.0, 0.0), 1.0);
5034
5035 assert!(count_nodes(&broken) > 1, "the node body still renders");
5036 assert!(
5037 count_nodes(&broken) < count_nodes(&intact),
5038 "a broken backref must cost the ports: {} vs {}",
5039 count_nodes(&broken),
5040 count_nodes(&intact),
5041 );
5042 }
5043
5044 #[test]
5045 fn render_node_drops_ports_whose_io_type_has_no_registered_info() {
5046 let mut g = graph();
5047 g.node_types.as_mut()[0].node_type_info.inputs = vec![IO_COLORLESS].into();
5049 let stripped = count_nodes(&render_one(&g, N1, (0.0, 0.0), 1.0));
5050 let intact = count_nodes(&render_one(&graph(), N1, (0.0, 0.0), 1.0));
5051 assert!(stripped < intact, "{stripped} vs {intact}");
5052 }
5053
5054 #[test]
5055 fn render_node_renders_all_five_field_widget_kinds() {
5056 let mut g = graph();
5057 g.nodes.as_mut()[0].node.fields = vec![
5058 NodeTypeField {
5059 key: AzString::from_const_str("text"),
5060 value: NodeTypeFieldValue::TextInput(AzString::from_const_str("hello")),
5061 },
5062 NodeTypeField {
5063 key: AzString::from_const_str("number"),
5064 value: NodeTypeFieldValue::NumberInput(1.5),
5065 },
5066 NodeTypeField {
5067 key: AzString::from_const_str("check"),
5068 value: NodeTypeFieldValue::CheckBox(true),
5069 },
5070 NodeTypeField {
5071 key: AzString::from_const_str("color"),
5072 value: NodeTypeFieldValue::ColorInput(ColorU {
5073 r: 9,
5074 g: 8,
5075 b: 7,
5076 a: 6,
5077 }),
5078 },
5079 NodeTypeField {
5080 key: AzString::from_const_str("file"),
5081 value: NodeTypeFieldValue::FileInput(OptionString::None),
5082 },
5083 ]
5084 .into();
5085
5086 let with_fields = count_nodes(&render_one(&g, N1, (0.0, 0.0), 1.0));
5087 let without = count_nodes(&render_one(&graph(), N1, (0.0, 0.0), 1.0));
5088 assert!(with_fields > without, "{with_fields} vs {without}");
5089 }
5090
5091 #[test]
5092 fn render_node_field_count_is_monotonic() {
5093 let base = count_nodes(&render_one(&graph(), N1, (0.0, 0.0), 1.0));
5094 let mut previous = base;
5095 for count in 1..=4_usize {
5096 let mut g = graph();
5097 g.nodes.as_mut()[0].node.fields = (0..count)
5098 .map(|_| NodeTypeField {
5099 key: AzString::from_const_str("f"),
5100 value: NodeTypeFieldValue::CheckBox(false),
5101 })
5102 .collect::<Vec<_>>()
5103 .into();
5104 let now = count_nodes(&render_one(&g, N1, (0.0, 0.0), 1.0));
5105 assert!(now > previous, "{count} fields: {now} !> {previous}");
5106 previous = now;
5107 }
5108 }
5109
5110 #[test]
5111 fn render_node_accepts_pathological_field_values() {
5112 let mut g = graph();
5115 g.nodes.as_mut()[0].node.fields = vec![
5116 NodeTypeField {
5117 key: AzString::from_const_str(""),
5118 value: NodeTypeFieldValue::TextInput(AzString::from_const_str("")),
5119 },
5120 NodeTypeField {
5121 key: AzString::from_const_str("🎉\u{200b}اختبار"),
5122 value: NodeTypeFieldValue::TextInput(AzString::from_const_str("𝕬\u{0301}\u{feff}")),
5123 },
5124 NodeTypeField {
5125 key: AzString::from_const_str("nan"),
5126 value: NodeTypeFieldValue::NumberInput(f32::NAN),
5127 },
5128 NodeTypeField {
5129 key: AzString::from_const_str("inf"),
5130 value: NodeTypeFieldValue::NumberInput(f32::NEG_INFINITY),
5131 },
5132 NodeTypeField {
5133 key: AzString::from_const_str("max"),
5134 value: NodeTypeFieldValue::NumberInput(f32::MAX),
5135 },
5136 NodeTypeField {
5137 key: AzString::from_const_str("clear"),
5138 value: NodeTypeFieldValue::ColorInput(ColorU {
5139 r: 0,
5140 g: 0,
5141 b: 0,
5142 a: 0,
5143 }),
5144 },
5145 NodeTypeField {
5146 key: AzString::from_const_str("path"),
5147 value: NodeTypeFieldValue::FileInput(OptionString::Some(
5148 AzString::from_const_str("/tmp/日本語/🎉.txt"),
5149 )),
5150 },
5151 ]
5152 .into();
5153
5154 assert!(count_nodes(&render_one(&g, N1, (f32::NAN, f32::NAN), f32::NAN)) > 1);
5155 }
5156
5157 fn marker() -> RefAny {
5162 RefAny::new(NodeConnectionMarkerDataset {})
5163 }
5164
5165 #[test]
5166 fn render_connections_of_an_unwired_graph_has_no_children() {
5167 let dom = render_connections(&graph(), marker());
5168 assert_eq!(dom.children.len(), 0);
5169 assert!(
5170 dom.root.get_dataset().is_some(),
5171 "the container must keep the marker dataset that drag-handling looks up",
5172 );
5173 }
5174
5175 #[test]
5176 fn render_connections_emits_exactly_one_child_per_wire() {
5177 let mut g = graph();
5178 g.connect_input_output(N3, 0, N1, 0).expect("N1 -> N3");
5179 assert_eq!(render_connections(&g, marker()).children.len(), 1);
5180
5181 g.connect_input_output(N4, 0, N1, 0).expect("N1 -> N4");
5182 assert_eq!(render_connections(&g, marker()).children.len(), 2);
5183
5184 g.connect_input_output(N4, 0, N3, 0).expect("N3 -> N4");
5185 assert_eq!(render_connections(&g, marker()).children.len(), 3);
5186 }
5187
5188 #[test]
5189 fn render_connections_skips_a_wire_to_a_node_that_no_longer_exists() {
5190 let g = force_out_connection(graph(), N1, 0, MISSING, 0);
5192 assert_eq!(render_connections(&g, marker()).children.len(), 0);
5193 }
5194
5195 #[test]
5196 fn render_connections_skips_an_out_of_range_output_port() {
5197 for out_idx in [1_usize, 99, usize::MAX] {
5198 let g = force_out_connection(graph(), N1, out_idx, N3, 0);
5199 assert_eq!(
5200 render_connections(&g, marker()).children.len(),
5201 0,
5202 "output index {out_idx} must be skipped",
5203 );
5204 }
5205 }
5206
5207 #[test]
5208 fn render_connections_skips_a_node_whose_type_is_not_registered() {
5209 let mut g = graph();
5210 g.nodes.push(NodeIdNodeMap {
5211 node_id: NodeGraphNodeId { inner: 50 },
5212 node: mk_node(TYPE_UNREGISTERED, 0.0, 0.0),
5213 });
5214 let g = force_out_connection(g, NodeGraphNodeId { inner: 50 }, 0, N3, 0);
5215 assert_eq!(render_connections(&g, marker()).children.len(), 0);
5216 }
5217
5218 #[test]
5219 fn render_connections_skips_a_port_whose_io_type_has_no_color() {
5220 let mut g = graph();
5221 g.node_types.as_mut()[0].node_type_info.outputs = vec![IO_COLORLESS].into();
5222 let g = force_out_connection(g, N1, 0, N3, 0);
5223 assert_eq!(render_connections(&g, marker()).children.len(), 0);
5224 }
5225
5226 #[test]
5227 fn render_connections_survives_nan_positions_and_scale() {
5228 let mut g = graph();
5229 g.scale_factor = f32::NAN;
5230 g.offset = LogicalPosition {
5231 x: f32::INFINITY,
5232 y: f32::NAN,
5233 };
5234 g.nodes.as_mut()[0].node.position = NodeGraphNodePosition {
5235 x: f32::NAN,
5236 y: f32::NAN,
5237 };
5238 g.connect_input_output(N3, 0, N1, 0).expect("N1 -> N3");
5239 assert_eq!(render_connections(&g, marker()).children.len(), 1);
5240 }
5241
5242 #[test]
5243 fn render_connections_renders_a_self_loop() {
5244 let mut g = graph();
5245 g.connect_input_output(N1, 0, N1, 0).expect("self loop");
5246 assert_eq!(render_connections(&g, marker()).children.len(), 1);
5247 }
5248
5249 #[test]
5254 fn draw_connection_returns_a_fixed_100x100_null_image() {
5255 let img = draw_connection(RefAny::new(connection(N1, 0, N3, 0)), ());
5258 assert_eq!(img.get_size().width, 100.0);
5259 assert_eq!(img.get_size().height, 100.0);
5260 }
5261
5262 #[test]
5263 fn draw_connection_ignores_a_payload_of_the_wrong_type() {
5264 for payload in [
5265 RefAny::new(NodeConnectionMarkerDataset {}),
5266 RefAny::new(0_u8),
5267 RefAny::new(String::new()),
5268 ] {
5269 let img = draw_connection(payload, ());
5270 assert_eq!(img.get_size().width, 100.0);
5271 }
5272 }
5273
5274 #[test]
5275 fn draw_connection_does_not_consume_or_corrupt_its_payload() {
5276 let cld = RefAny::new(connection(N1, 2, N3, 5));
5277 let _ = draw_connection(cld.clone(), ());
5278 let _ = draw_connection(cld.clone(), ());
5279
5280 let mut probe = cld.clone();
5281 let read_back = probe
5282 .downcast_ref::<ConnectionLocalDataset>()
5283 .expect("payload must still be downcastable after the callback ran");
5284 assert_eq!(read_back.out_idx, 2);
5285 assert_eq!(read_back.in_idx, 5);
5286 }
5287
5288 #[test]
5289 fn draw_connection_returns_distinct_image_handles_per_call() {
5290 let cld = RefAny::new(connection(N1, 0, N3, 0));
5291 let a = draw_connection(cld.clone(), ());
5292 let b = draw_connection(cld.clone(), ());
5293 assert_ne!(a, b, "each call must mint a fresh ImageRef id");
5294 }
5295
5296 #[test]
5301 fn dom_of_a_default_graph_has_the_expected_skeleton() {
5302 let dom = NodeGraph::default().dom();
5303
5304 assert!(
5305 dom.root.get_context_menu().is_some(),
5306 "the 'add node' context menu is the only way to create nodes",
5307 );
5308 assert!(dom.root.get_dataset().is_some());
5309 assert_eq!(dom.children.len(), 1, "wrapper holds exactly the .nodegraph");
5310
5311 let nodegraph = &dom.children.as_slice()[0];
5312 assert_eq!(
5313 nodegraph.children.len(),
5314 2,
5315 "connections container + nodes container",
5316 );
5317 }
5318
5319 #[test]
5320 fn dom_root_dataset_round_trips_back_to_a_node_graph_local_dataset() {
5321 let dom = graph().dom();
5322 let mut ds = dom.root.get_dataset().cloned().expect("root dataset");
5323 let inner = ds
5324 .downcast_ref::<NodeGraphLocalDataset>()
5325 .expect("root dataset must be the NodeGraphLocalDataset");
5326 assert_eq!(inner.node_graph.nodes.len(), 4);
5327 assert!(inner.last_input_or_output_clicked.is_none());
5328 assert!(inner.active_node_being_dragged.is_none());
5329 }
5330
5331 #[test]
5332 fn dom_renders_one_child_per_node_and_silently_drops_unregistered_types() {
5333 let mut g = graph();
5334 g.nodes.push(NodeIdNodeMap {
5335 node_id: NodeGraphNodeId { inner: 50 },
5336 node: mk_node(TYPE_UNREGISTERED, 0.0, 0.0),
5337 });
5338
5339 let dom = g.dom();
5340 let nodes_container = &dom.children.as_slice()[0].children.as_slice()[1];
5341 assert_eq!(
5342 nodes_container.children.len(),
5343 4,
5344 "the 5th node has no registered type and must be filtered out",
5345 );
5346 }
5347
5348 #[test]
5349 fn dom_context_menu_lists_one_submenu_entry_per_node_type() {
5350 let g = graph();
5351 let dom = g.dom();
5352 let menu = dom.root.get_context_menu().expect("context menu").clone();
5353 assert_eq!(menu.items.len(), 1, "one top-level 'add node' entry");
5354 match &menu.items.as_slice()[0] {
5355 MenuItem::String(s) => assert_eq!(s.children.len(), 2, "TYPE_A and TYPE_B"),
5356 other => panic!("expected a string menu item, got {other:?}"),
5357 }
5358 }
5359
5360 #[test]
5361 fn dom_context_menu_is_present_even_with_no_node_types_at_all() {
5362 let mut g = graph();
5363 g.node_types = NodeTypeIdInfoMapVec::new();
5364 let dom = g.dom();
5365 let menu = dom.root.get_context_menu().expect("context menu").clone();
5366 assert_eq!(menu.items.len(), 1);
5367 match &menu.items.as_slice()[0] {
5368 MenuItem::String(s) => assert_eq!(s.children.len(), 0),
5369 other => panic!("expected a string menu item, got {other:?}"),
5370 }
5371 }
5372
5373 #[test]
5374 fn dom_survives_pathological_scale_offset_and_positions() {
5375 for (scale, ox, oy) in [
5376 (f32::NAN, f32::NAN, f32::NAN),
5377 (0.0, 0.0, 0.0),
5378 (-1.0, -1e30, 1e30),
5379 (f32::INFINITY, f32::NEG_INFINITY, f32::INFINITY),
5380 (f32::MAX, f32::MAX, f32::MIN),
5381 ] {
5382 let mut g = graph();
5383 g.scale_factor = scale;
5384 g.offset = LogicalPosition { x: ox, y: oy };
5385 g.nodes.as_mut()[0].node.position = NodeGraphNodePosition {
5386 x: f32::NAN,
5387 y: f32::INFINITY,
5388 };
5389 g.connect_input_output(N3, 0, N1, 0).expect("N1 -> N3");
5390 let dom = g.dom();
5391 assert_eq!(dom.children.len(), 1, "scale {scale}");
5392 }
5393 }
5394
5395 #[test]
5396 fn dom_of_a_wired_graph_renders_the_connection_container_children() {
5397 let mut g = graph();
5398 g.connect_input_output(N3, 0, N1, 0).expect("N1 -> N3");
5399 let dom = g.dom();
5400 let connections = &dom.children.as_slice()[0].children.as_slice()[0];
5401 assert_eq!(connections.children.len(), 1);
5402 }
5403
5404 #[test]
5405 fn dom_can_be_converted_into_a_styled_dom() {
5406 let mut g = graph();
5409 g.connect_input_output(N3, 0, N1, 0).expect("N1 -> N3");
5410 g.nodes.as_mut()[0].node.fields = vec![NodeTypeField {
5411 key: AzString::from_const_str("k"),
5412 value: NodeTypeFieldValue::NumberInput(f32::NAN),
5413 }]
5414 .into();
5415 let styled = StyledDom::create_from_dom(g.dom());
5416 assert!(styled.node_data.len() > 1);
5417 }
5418
5419 #[test]
5424 fn set_active_node_records_the_node_and_unset_clears_it() {
5425 let g = graph();
5426 let gd = graph_dataset(&g);
5427 let nd = RefAny::new(NodeLocalDataset {
5428 node_id: N2,
5429 backref: gd.clone(),
5430 });
5431
5432 assert_eq!(
5433 fire(|info| nodegraph_set_active_node(nd.clone(), info)),
5434 Update::DoNothing,
5435 );
5436 {
5437 let mut probe = gd.clone();
5438 let d = probe.downcast_ref::<NodeGraphLocalDataset>().expect("gd");
5439 assert_eq!(
5440 d.active_node_being_dragged.as_ref().map(|(id, _)| *id),
5441 Some(N2),
5442 );
5443 }
5444
5445 assert_eq!(
5446 fire(|info| nodegraph_unset_active_node(gd.clone(), info)),
5447 Update::DoNothing,
5448 );
5449 {
5450 let mut probe = gd.clone();
5451 let d = probe.downcast_ref::<NodeGraphLocalDataset>().expect("gd");
5452 assert!(d.active_node_being_dragged.is_none());
5453 }
5454 }
5455
5456 #[test]
5457 fn set_active_node_ignores_a_payload_of_the_wrong_type() {
5458 assert_eq!(
5459 fire(|info| nodegraph_set_active_node(RefAny::new(1_u64), info)),
5460 Update::DoNothing,
5461 );
5462 }
5463
5464 #[test]
5465 fn set_active_node_ignores_a_node_dataset_with_a_broken_backref() {
5466 let nd = RefAny::new(NodeLocalDataset {
5469 node_id: N2,
5470 backref: RefAny::new(0_u8),
5471 });
5472 assert_eq!(
5473 fire(|info| nodegraph_set_active_node(nd.clone(), info)),
5474 Update::DoNothing,
5475 );
5476 }
5477
5478 #[test]
5479 fn unset_active_node_is_idempotent_and_ignores_wrong_payloads() {
5480 let g = graph();
5481 let gd = graph_dataset(&g);
5482 for _ in 0..3 {
5483 assert_eq!(
5484 fire(|info| nodegraph_unset_active_node(gd.clone(), info)),
5485 Update::DoNothing,
5486 );
5487 }
5488 assert_eq!(
5489 fire(|info| nodegraph_unset_active_node(RefAny::new(0_i8), info)),
5490 Update::DoNothing,
5491 );
5492 }
5493
5494 #[test]
5499 fn duplicate_node_is_a_documented_no_op_for_valid_and_invalid_payloads() {
5500 let g = graph();
5501 let nd = RefAny::new(NodeLocalDataset {
5502 node_id: N1,
5503 backref: graph_dataset(&g),
5504 });
5505 assert_eq!(
5506 fire(|info| nodegraph_duplicate_node(nd.clone(), info)),
5507 Update::DoNothing,
5508 );
5509 assert_eq!(
5510 fire(|info| nodegraph_duplicate_node(RefAny::new(0_u16), info)),
5511 Update::DoNothing,
5512 );
5513 }
5514
5515 #[test]
5516 fn delete_node_forwards_the_node_id_to_on_node_removed() {
5517 let (g, log) = graph_with_log();
5518 let nd = RefAny::new(NodeLocalDataset {
5519 node_id: N3,
5520 backref: graph_dataset(&g),
5521 });
5522
5523 assert_eq!(
5524 fire(|info| nodegraph_delete_node(nd.clone(), info)),
5525 Update::RefreshDom,
5526 "the user callback's Update must be propagated verbatim",
5527 );
5528 log_of(&log, |l| assert_eq!(l.removed, vec![N3.inner]));
5529 }
5530
5531 #[test]
5532 fn delete_node_without_a_user_callback_does_nothing() {
5533 let g = graph();
5534 let nd = RefAny::new(NodeLocalDataset {
5535 node_id: N3,
5536 backref: graph_dataset(&g),
5537 });
5538 assert_eq!(
5539 fire(|info| nodegraph_delete_node(nd.clone(), info)),
5540 Update::DoNothing,
5541 );
5542 }
5543
5544 #[test]
5545 fn delete_node_reports_a_node_id_that_is_not_even_in_the_graph() {
5546 let (g, log) = graph_with_log();
5549 let nd = RefAny::new(NodeLocalDataset {
5550 node_id: MISSING,
5551 backref: graph_dataset(&g),
5552 });
5553 let _ = fire(|info| nodegraph_delete_node(nd.clone(), info));
5554 log_of(&log, |l| assert_eq!(l.removed, vec![MISSING.inner]));
5555 }
5556
5557 #[test]
5558 fn delete_node_ignores_broken_payloads() {
5559 assert_eq!(
5560 fire(|info| nodegraph_delete_node(RefAny::new(0_u32), info)),
5561 Update::DoNothing,
5562 );
5563 let nd = RefAny::new(NodeLocalDataset {
5564 node_id: N1,
5565 backref: RefAny::new(0_u8),
5566 });
5567 assert_eq!(
5568 fire(|info| nodegraph_delete_node(nd.clone(), info)),
5569 Update::DoNothing,
5570 );
5571 }
5572
5573 #[test]
5578 fn drag_without_a_previous_window_state_does_nothing() {
5579 let (g, _log) = graph_with_log();
5582 let gd = graph_dataset(&g);
5583 let before = wiring(&dataset_graph(&gd));
5584
5585 assert_eq!(
5586 fire(|info| nodegraph_drag_graph_or_nodes(gd.clone(), info)),
5587 Update::DoNothing,
5588 );
5589 assert_eq!(wiring(&dataset_graph(&gd)), before);
5590 let after = dataset_graph(&gd);
5591 assert_eq!(after.offset.x, 0.0);
5592 assert_eq!(after.offset.y, 0.0);
5593 }
5594
5595 #[test]
5596 fn drag_ignores_a_payload_of_the_wrong_type() {
5597 assert_eq!(
5598 fire(|info| nodegraph_drag_graph_or_nodes(RefAny::new(0_u64), info)),
5599 Update::DoNothing,
5600 );
5601 }
5602
5603 #[test]
5604 fn drag_does_not_dereference_the_active_node_before_checking_the_mouse() {
5605 let g = graph();
5608 let gd = graph_dataset(&g);
5609 {
5610 let mut probe = gd.clone();
5611 let mut d = probe.downcast_mut::<NodeGraphLocalDataset>().expect("gd");
5612 d.active_node_being_dragged = Some((MISSING, RefAny::new(0_u8)));
5613 }
5614 assert_eq!(
5615 fire(|info| nodegraph_drag_graph_or_nodes(gd.clone(), info)),
5616 Update::DoNothing,
5617 );
5618 }
5619
5620 fn io_dataset(gd: &RefAny, node_id: NodeGraphNodeId, io: InputOrOutput) -> RefAny {
5625 RefAny::new(NodeInputOutputLocalDataset {
5626 io_id: io,
5627 backref: RefAny::new(NodeLocalDataset {
5628 node_id,
5629 backref: gd.clone(),
5630 }),
5631 })
5632 }
5633
5634 #[test]
5635 fn connect_click_one_only_arms_the_pending_port() {
5636 let g = graph();
5637 let gd = graph_dataset(&g);
5638 let first = io_dataset(&gd, N1, InputOrOutput::Output(0));
5639
5640 assert_eq!(
5641 fire(|info| nodegraph_input_output_connect(first.clone(), info)),
5642 Update::DoNothing,
5643 );
5644 assert_eq!(pending_click(&gd), Some((N1.inner, (false, 0))));
5645 assert_eq!(
5646 wiring(&dataset_graph(&gd)),
5647 wiring(&graph()),
5648 "arming a port must not wire anything yet",
5649 );
5650 }
5651
5652 #[test]
5653 fn connect_output_then_input_wires_the_graph_inside_the_dataset() {
5654 let (g, log) = graph_with_log();
5655 let gd = graph_dataset(&g);
5656 let out = io_dataset(&gd, N1, InputOrOutput::Output(0));
5657 let inn = io_dataset(&gd, N3, InputOrOutput::Input(0));
5658
5659 let _ = fire(|info| nodegraph_input_output_connect(out.clone(), info));
5660 assert_eq!(
5661 fire(|info| nodegraph_input_output_connect(inn.clone(), info)),
5662 Update::RefreshDom,
5663 );
5664
5665 let wired = dataset_graph(&gd);
5666 assert_eq!(inputs_of(&wired, N3), vec![(0, vec![(N1.inner, 0)])]);
5667 assert_eq!(outputs_of(&wired, N1), vec![(0, vec![(N3.inner, 0)])]);
5668 log_of(&log, |l| {
5669 assert_eq!(l.connected, vec![(N3.inner, 0, N1.inner, 0)]);
5670 });
5671 assert_eq!(
5672 pending_click(&gd),
5673 None,
5674 "a completed connection must disarm the pending port",
5675 );
5676 }
5677
5678 #[test]
5679 fn connect_input_then_output_wires_the_same_edge_in_the_same_direction() {
5680 let (g, _log) = graph_with_log();
5683
5684 let gd_a = graph_dataset(&g);
5685 let _ = fire(|info| {
5686 nodegraph_input_output_connect(io_dataset(&gd_a, N1, InputOrOutput::Output(0)), info)
5687 });
5688 let _ = fire(|info| {
5689 nodegraph_input_output_connect(io_dataset(&gd_a, N3, InputOrOutput::Input(0)), info)
5690 });
5691
5692 let gd_b = graph_dataset(&g);
5693 let _ = fire(|info| {
5694 nodegraph_input_output_connect(io_dataset(&gd_b, N3, InputOrOutput::Input(0)), info)
5695 });
5696 let _ = fire(|info| {
5697 nodegraph_input_output_connect(io_dataset(&gd_b, N1, InputOrOutput::Output(0)), info)
5698 });
5699
5700 assert_eq!(wiring(&dataset_graph(&gd_a)), wiring(&dataset_graph(&gd_b)));
5701 }
5702
5703 #[test]
5704 fn connect_output_to_output_disarms_instead_of_wiring() {
5705 let (g, log) = graph_with_log();
5706 let gd = graph_dataset(&g);
5707
5708 let _ = fire(|info| {
5709 nodegraph_input_output_connect(io_dataset(&gd, N1, InputOrOutput::Output(0)), info)
5710 });
5711 assert_eq!(
5712 fire(|info| {
5713 nodegraph_input_output_connect(io_dataset(&gd, N3, InputOrOutput::Output(0)), info)
5714 }),
5715 Update::DoNothing,
5716 );
5717
5718 assert_eq!(pending_click(&gd), None);
5719 assert_eq!(wiring(&dataset_graph(&gd)), wiring(&graph()));
5720 log_of(&log, |l| assert!(l.connected.is_empty()));
5721 }
5722
5723 #[test]
5724 fn connect_input_to_input_disarms_instead_of_wiring() {
5725 let (g, _log) = graph_with_log();
5726 let gd = graph_dataset(&g);
5727
5728 let _ = fire(|info| {
5729 nodegraph_input_output_connect(io_dataset(&gd, N1, InputOrOutput::Input(0)), info)
5730 });
5731 assert_eq!(
5732 fire(|info| {
5733 nodegraph_input_output_connect(io_dataset(&gd, N3, InputOrOutput::Input(0)), info)
5734 }),
5735 Update::DoNothing,
5736 );
5737 assert_eq!(pending_click(&gd), None);
5738 assert_eq!(wiring(&dataset_graph(&gd)), wiring(&graph()));
5739 }
5740
5741 #[test]
5742 fn connect_across_incompatible_types_disarms_and_leaves_the_graph_alone() {
5743 let (g, log) = graph_with_log();
5744 let gd = graph_dataset(&g);
5745
5746 let _ = fire(|info| {
5748 nodegraph_input_output_connect(io_dataset(&gd, N1, InputOrOutput::Output(0)), info)
5749 });
5750 assert_eq!(
5751 fire(|info| {
5752 nodegraph_input_output_connect(io_dataset(&gd, N2, InputOrOutput::Input(0)), info)
5753 }),
5754 Update::DoNothing,
5755 );
5756
5757 assert_eq!(pending_click(&gd), None);
5758 assert_eq!(wiring(&dataset_graph(&gd)), wiring(&graph()));
5759 log_of(&log, |l| assert!(l.connected.is_empty()));
5760 }
5761
5762 #[test]
5763 fn connect_with_an_out_of_range_port_index_is_rejected() {
5764 let (g, _log) = graph_with_log();
5765 let gd = graph_dataset(&g);
5766
5767 let _ = fire(|info| {
5768 nodegraph_input_output_connect(
5769 io_dataset(&gd, N1, InputOrOutput::Output(usize::MAX)),
5770 info,
5771 )
5772 });
5773 assert_eq!(
5774 fire(|info| {
5775 nodegraph_input_output_connect(io_dataset(&gd, N3, InputOrOutput::Input(0)), info)
5776 }),
5777 Update::DoNothing,
5778 );
5779 assert_eq!(wiring(&dataset_graph(&gd)), wiring(&graph()));
5780 }
5781
5782 #[test]
5783 fn connect_leaves_the_pending_port_armed_when_no_user_callback_is_installed() {
5784 let g = graph(); let gd = graph_dataset(&g);
5790
5791 let _ = fire(|info| {
5792 nodegraph_input_output_connect(io_dataset(&gd, N1, InputOrOutput::Output(0)), info)
5793 });
5794 let _ = fire(|info| {
5795 nodegraph_input_output_connect(io_dataset(&gd, N3, InputOrOutput::Input(0)), info)
5796 });
5797
5798 let wired = dataset_graph(&gd);
5799 assert_eq!(inputs_of(&wired, N3), vec![(0, vec![(N1.inner, 0)])]);
5800 assert_eq!(
5801 pending_click(&gd),
5802 Some((N1.inner, (false, 0))),
5803 "N1's output stays armed after the wire was already made",
5804 );
5805
5806 let _ = fire(|info| {
5808 nodegraph_input_output_connect(io_dataset(&gd, N4, InputOrOutput::Input(0)), info)
5809 });
5810 assert_eq!(
5811 inputs_of(&dataset_graph(&gd), N4),
5812 vec![(0, vec![(N1.inner, 0)])],
5813 "the stale port produced an edge the user never armed",
5814 );
5815 }
5816
5817 #[test]
5818 fn connect_ignores_broken_payloads_at_every_level_of_the_backref_chain() {
5819 assert_eq!(
5820 fire(|info| nodegraph_input_output_connect(RefAny::new(0_u32), info)),
5821 Update::DoNothing,
5822 );
5823 let bad_mid = RefAny::new(NodeInputOutputLocalDataset {
5824 io_id: InputOrOutput::Input(0),
5825 backref: RefAny::new(0_u8),
5826 });
5827 assert_eq!(
5828 fire(|info| nodegraph_input_output_connect(bad_mid.clone(), info)),
5829 Update::DoNothing,
5830 );
5831 let bad_tail = RefAny::new(NodeInputOutputLocalDataset {
5832 io_id: InputOrOutput::Input(0),
5833 backref: RefAny::new(NodeLocalDataset {
5834 node_id: N1,
5835 backref: RefAny::new(0_u16),
5836 }),
5837 });
5838 assert_eq!(
5839 fire(|info| nodegraph_input_output_connect(bad_tail.clone(), info)),
5840 Update::DoNothing,
5841 );
5842 }
5843
5844 #[test]
5845 fn disconnect_notifies_the_input_or_the_output_callback_but_never_both() {
5846 let (g, log) = graph_with_log();
5847 let gd = graph_dataset(&g);
5848
5849 assert_eq!(
5850 fire(|info| {
5851 nodegraph_input_output_disconnect(io_dataset(&gd, N3, InputOrOutput::Input(4)), info)
5852 }),
5853 Update::RefreshDom,
5854 );
5855 log_of(&log, |l| {
5856 assert_eq!(l.input_disconnected, vec![(N3.inner, 4)]);
5857 assert!(l.output_disconnected.is_empty());
5858 });
5859
5860 assert_eq!(
5861 fire(|info| {
5862 nodegraph_input_output_disconnect(
5863 io_dataset(&gd, N1, InputOrOutput::Output(7)),
5864 info,
5865 )
5866 }),
5867 Update::RefreshDomAllWindows,
5868 );
5869 log_of(&log, |l| {
5870 assert_eq!(l.input_disconnected, vec![(N3.inner, 4)]);
5871 assert_eq!(l.output_disconnected, vec![(N1.inner, 7)]);
5872 });
5873 }
5874
5875 #[test]
5876 fn disconnect_notifies_but_does_not_actually_unwire_the_graph() {
5877 let (mut g, _log) = graph_with_log();
5882 g.connect_input_output(N3, 0, N1, 0).expect("N1 -> N3");
5883 let gd = graph_dataset(&g);
5884 let before = wiring(&dataset_graph(&gd));
5885
5886 let _ = fire(|info| {
5887 nodegraph_input_output_disconnect(io_dataset(&gd, N3, InputOrOutput::Input(0)), info)
5888 });
5889
5890 assert_eq!(
5891 wiring(&dataset_graph(&gd)),
5892 before,
5893 "the model is untouched by the disconnect gesture",
5894 );
5895 }
5896
5897 #[test]
5898 fn disconnect_without_user_callbacks_does_nothing() {
5899 let g = graph();
5900 let gd = graph_dataset(&g);
5901 assert_eq!(
5902 fire(|info| {
5903 nodegraph_input_output_disconnect(io_dataset(&gd, N1, InputOrOutput::Input(0)), info)
5904 }),
5905 Update::DoNothing,
5906 );
5907 assert_eq!(
5908 fire(|info| nodegraph_input_output_disconnect(RefAny::new(0_u32), info)),
5909 Update::DoNothing,
5910 );
5911 }
5912
5913 #[test]
5914 fn disconnect_forwards_usize_max_port_indices_unclamped() {
5915 let (g, log) = graph_with_log();
5916 let gd = graph_dataset(&g);
5917 let _ = fire(|info| {
5918 nodegraph_input_output_disconnect(
5919 io_dataset(&gd, N1, InputOrOutput::Output(usize::MAX)),
5920 info,
5921 )
5922 });
5923 log_of(&log, |l| {
5924 assert_eq!(l.output_disconnected, vec![(N1.inner, usize::MAX)]);
5925 });
5926 }
5927
5928 #[test]
5933 fn context_menu_click_does_nothing_when_the_graph_is_not_in_the_dom() {
5934 let (g, log) = graph_with_log();
5937 let cm = RefAny::new(ContextMenuEntryLocalDataset {
5938 node_type: TYPE_A,
5939 backref: graph_dataset(&g),
5940 });
5941 assert_eq!(
5942 fire(|info| nodegraph_context_menu_click(cm.clone(), info)),
5943 Update::DoNothing,
5944 );
5945 log_of(&log, |l| assert!(l.added.is_empty()));
5946 }
5947
5948 #[test]
5949 fn context_menu_click_reports_a_fresh_node_id_at_the_cursor() {
5950 let (g, log) = graph_with_log();
5951 let dom = g.dom();
5952 let gd = dom.root.get_dataset().cloned().expect("root dataset");
5953 let styled = StyledDom::create_from_dom(dom);
5954
5955 let cm = RefAny::new(ContextMenuEntryLocalDataset {
5956 node_type: TYPE_B,
5957 backref: gd,
5958 });
5959
5960 let (update, _) = with_info(styled, hit_none(), |info| {
5961 nodegraph_context_menu_click(cm.clone(), *info)
5962 });
5963
5964 assert_eq!(update, Update::RefreshDomAllWindows);
5965 log_of(&log, |l| {
5966 assert_eq!(l.added, vec![(TYPE_B.inner, 5, 0.0, 0.0)]);
5969 });
5970 }
5971
5972 #[test]
5973 fn context_menu_click_position_degrades_to_nan_at_a_zero_scale_factor() {
5974 let (mut g, log) = graph_with_log();
5977 g.scale_factor = 0.0;
5978 let dom = g.dom();
5979 let gd = dom.root.get_dataset().cloned().expect("root dataset");
5980 let styled = StyledDom::create_from_dom(dom);
5981
5982 let cm = RefAny::new(ContextMenuEntryLocalDataset {
5983 node_type: TYPE_A,
5984 backref: gd,
5985 });
5986 let (_, _) = with_info(styled, hit_none(), |info| {
5987 nodegraph_context_menu_click(cm.clone(), *info)
5988 });
5989
5990 log_of(&log, |l| {
5991 assert_eq!(l.added.len(), 1);
5992 let (_, id, x, y) = l.added[0];
5993 assert_eq!(id, 5, "the id must still be generated normally");
5994 assert!(x.is_nan() && y.is_nan(), "0 * inf == NaN, got ({x}, {y})");
5995 });
5996 }
5997
5998 #[test]
5999 fn context_menu_click_ignores_a_payload_of_the_wrong_type() {
6000 assert_eq!(
6001 fire(|info| nodegraph_context_menu_click(RefAny::new(0_u32), info)),
6002 Update::DoNothing,
6003 );
6004 }
6005
6006 #[test]
6011 fn textinput_focus_lost_forwards_the_decoded_text() {
6012 let (g, log) = graph_with_log();
6013 let fd = RefAny::new(NodeFieldLocalDataset {
6014 field_idx: 2,
6015 backref: RefAny::new(NodeLocalDataset {
6016 node_id: N2,
6017 backref: graph_dataset(&g),
6018 }),
6019 });
6020
6021 let state = TextInputState {
6024 text: vec![0x48_u32, 0xD800, 0x1F389].into(),
6025 ..TextInputState::default()
6026 };
6027
6028 assert_eq!(
6029 fire(|info| nodegraph_on_textinput_focus_lost(fd.clone(), info, state.clone())),
6030 Update::RefreshDom,
6031 );
6032 log_of(&log, |l| {
6033 assert_eq!(l.edited, vec![(N2.inner, 2, TYPE_B.inner)]);
6034 assert_eq!(l.text_values, vec!["H\u{1F389}".to_string()]);
6035 });
6036 }
6037
6038 #[test]
6039 fn textinput_focus_lost_handles_an_empty_and_an_out_of_range_scalar() {
6040 let (g, log) = graph_with_log();
6041 let fd = RefAny::new(NodeFieldLocalDataset {
6042 field_idx: 0,
6043 backref: RefAny::new(NodeLocalDataset {
6044 node_id: N1,
6045 backref: graph_dataset(&g),
6046 }),
6047 });
6048
6049 for (raw, expected) in [
6050 (vec![], ""),
6051 (vec![0x11_0000_u32, 0xFFFF_FFFF], ""), (vec![0x0041, 0x0301], "A\u{0301}"), ] {
6054 let state = TextInputState {
6055 text: raw.into(),
6056 ..TextInputState::default()
6057 };
6058 let _ = fire(|info| nodegraph_on_textinput_focus_lost(fd.clone(), info, state.clone()));
6059 log_of(&log, |l| {
6060 assert_eq!(l.text_values.last().map(String::as_str), Some(expected));
6061 });
6062 }
6063 }
6064
6065 #[test]
6066 fn numberinput_focus_lost_forwards_nan_and_infinities_verbatim() {
6067 let (g, log) = graph_with_log();
6068 let fd = RefAny::new(NodeFieldLocalDataset {
6069 field_idx: 1,
6070 backref: RefAny::new(NodeLocalDataset {
6071 node_id: N1,
6072 backref: graph_dataset(&g),
6073 }),
6074 });
6075
6076 for n in [
6077 0.0_f32,
6078 -0.0,
6079 f32::MAX,
6080 f32::MIN,
6081 f32::MIN_POSITIVE,
6082 f32::EPSILON,
6083 f32::INFINITY,
6084 f32::NEG_INFINITY,
6085 f32::NAN,
6086 ] {
6087 let state = NumberInputState {
6088 number: n,
6089 ..NumberInputState::default()
6090 };
6091 assert_eq!(
6092 fire(|info| nodegraph_on_numberinput_focus_lost(fd.clone(), info, state)),
6093 Update::RefreshDom,
6094 );
6095 }
6096
6097 log_of(&log, |l| {
6098 assert_eq!(l.number_values.len(), 9);
6099 assert_eq!(l.number_values[0], 0.0);
6100 assert_eq!(l.number_values[2], f32::MAX);
6101 assert!(l.number_values[6].is_infinite());
6102 assert!(l.number_values[8].is_nan(), "NaN must not be normalised");
6103 assert!(l.edited.iter().all(|e| *e == (N1.inner, 1, TYPE_A.inner)));
6104 });
6105 }
6106
6107 #[test]
6108 fn checkbox_change_forwards_both_states() {
6109 let (g, log) = graph_with_log();
6110 let fd = RefAny::new(NodeFieldLocalDataset {
6111 field_idx: 0,
6112 backref: RefAny::new(NodeLocalDataset {
6113 node_id: N1,
6114 backref: graph_dataset(&g),
6115 }),
6116 });
6117 for checked in [true, false, true] {
6118 let _ = fire(|info| {
6119 nodegraph_on_checkbox_value_changed(fd.clone(), info, CheckBoxState { checked })
6120 });
6121 }
6122 log_of(&log, |l| assert_eq!(l.bool_values, vec![true, false, true]));
6123 }
6124
6125 #[test]
6126 fn colorinput_change_forwards_every_channel_including_alpha_extremes() {
6127 let (g, log) = graph_with_log();
6128 let fd = RefAny::new(NodeFieldLocalDataset {
6129 field_idx: 3,
6130 backref: RefAny::new(NodeLocalDataset {
6131 node_id: N1,
6132 backref: graph_dataset(&g),
6133 }),
6134 });
6135 for c in [
6138 ColorU { r: 1, g: 2, b: 3, a: 4 },
6139 ColorU { r: 0, g: 0, b: 0, a: 0 },
6140 ColorU { r: 255, g: 255, b: 255, a: 255 },
6141 ] {
6142 let _ = fire(|info| {
6143 nodegraph_on_colorinput_value_changed(fd.clone(), info, ColorInputState { color: c })
6144 });
6145 }
6146 log_of(&log, |l| {
6147 assert_eq!(
6148 l.color_values,
6149 vec![(1, 2, 3, 4), (0, 0, 0, 0), (255, 255, 255, 255)],
6150 );
6151 });
6152 }
6153
6154 #[test]
6155 fn fileinput_click_forwards_both_a_missing_and_a_unicode_path() {
6156 let (g, log) = graph_with_log();
6157 let fd = RefAny::new(NodeFieldLocalDataset {
6158 field_idx: 4,
6159 backref: RefAny::new(NodeLocalDataset {
6160 node_id: N1,
6161 backref: graph_dataset(&g),
6162 }),
6163 });
6164
6165 let _ = fire(|info| {
6166 nodegraph_on_fileinput_button_clicked(
6167 fd.clone(),
6168 info,
6169 FileInputState {
6170 path: OptionString::None,
6171 },
6172 )
6173 });
6174 let _ = fire(|info| {
6175 nodegraph_on_fileinput_button_clicked(
6176 fd.clone(),
6177 info,
6178 FileInputState {
6179 path: OptionString::Some(AzString::from_const_str("/tmp/日本語/🎉.txt")),
6180 },
6181 )
6182 });
6183
6184 log_of(&log, |l| {
6185 assert_eq!(
6186 l.file_values,
6187 vec![None, Some("/tmp/日本語/🎉.txt".to_string())],
6188 );
6189 });
6190 }
6191
6192 #[test]
6193 fn field_callbacks_bail_out_when_the_node_is_not_in_the_graph() {
6194 let (g, log) = graph_with_log();
6197 let fd = RefAny::new(NodeFieldLocalDataset {
6198 field_idx: 0,
6199 backref: RefAny::new(NodeLocalDataset {
6200 node_id: MISSING,
6201 backref: graph_dataset(&g),
6202 }),
6203 });
6204
6205 assert_eq!(
6206 fire(|info| nodegraph_on_textinput_focus_lost(
6207 fd.clone(),
6208 info,
6209 TextInputState::default()
6210 )),
6211 Update::DoNothing,
6212 );
6213 assert_eq!(
6214 fire(|info| nodegraph_on_numberinput_focus_lost(
6215 fd.clone(),
6216 info,
6217 NumberInputState::default()
6218 )),
6219 Update::DoNothing,
6220 );
6221 assert_eq!(
6222 fire(|info| nodegraph_on_checkbox_value_changed(
6223 fd.clone(),
6224 info,
6225 CheckBoxState::default()
6226 )),
6227 Update::DoNothing,
6228 );
6229 assert_eq!(
6230 fire(|info| nodegraph_on_colorinput_value_changed(
6231 fd.clone(),
6232 info,
6233 ColorInputState::default()
6234 )),
6235 Update::DoNothing,
6236 );
6237 assert_eq!(
6238 fire(|info| nodegraph_on_fileinput_button_clicked(
6239 fd.clone(),
6240 info,
6241 FileInputState::default()
6242 )),
6243 Update::DoNothing,
6244 );
6245
6246 log_of(&log, |l| assert!(l.edited.is_empty()));
6247 }
6248
6249 #[test]
6250 fn field_callbacks_bail_out_on_a_payload_of_the_wrong_type() {
6251 assert_eq!(
6252 fire(|info| nodegraph_on_textinput_focus_lost(
6253 RefAny::new(0_u32),
6254 info,
6255 TextInputState::default()
6256 )),
6257 Update::DoNothing,
6258 );
6259 assert_eq!(
6260 fire(|info| nodegraph_on_numberinput_focus_lost(
6261 RefAny::new(0_u32),
6262 info,
6263 NumberInputState::default()
6264 )),
6265 Update::DoNothing,
6266 );
6267 assert_eq!(
6268 fire(|info| nodegraph_on_checkbox_value_changed(
6269 RefAny::new(0_u32),
6270 info,
6271 CheckBoxState::default()
6272 )),
6273 Update::DoNothing,
6274 );
6275 assert_eq!(
6276 fire(|info| nodegraph_on_colorinput_value_changed(
6277 RefAny::new(0_u32),
6278 info,
6279 ColorInputState::default()
6280 )),
6281 Update::DoNothing,
6282 );
6283 assert_eq!(
6284 fire(|info| nodegraph_on_fileinput_button_clicked(
6285 RefAny::new(0_u32),
6286 info,
6287 FileInputState::default()
6288 )),
6289 Update::DoNothing,
6290 );
6291 }
6292
6293 #[test]
6294 fn field_callbacks_forward_a_usize_max_field_index_unclamped() {
6295 let (g, log) = graph_with_log();
6298 let fd = RefAny::new(NodeFieldLocalDataset {
6299 field_idx: usize::MAX,
6300 backref: RefAny::new(NodeLocalDataset {
6301 node_id: N1,
6302 backref: graph_dataset(&g),
6303 }),
6304 });
6305 let _ = fire(|info| {
6306 nodegraph_on_checkbox_value_changed(fd.clone(), info, CheckBoxState { checked: true })
6307 });
6308 log_of(&log, |l| {
6309 assert_eq!(l.edited, vec![(N1.inner, usize::MAX, TYPE_A.inner)]);
6310 });
6311 }
6312
6313 #[test]
6314 fn field_callbacks_without_a_user_callback_do_nothing() {
6315 let g = graph(); let fd = RefAny::new(NodeFieldLocalDataset {
6317 field_idx: 0,
6318 backref: RefAny::new(NodeLocalDataset {
6319 node_id: N1,
6320 backref: graph_dataset(&g),
6321 }),
6322 });
6323 assert_eq!(
6324 fire(|info| nodegraph_on_checkbox_value_changed(
6325 fd.clone(),
6326 info,
6327 CheckBoxState::default()
6328 )),
6329 Update::DoNothing,
6330 );
6331 assert_eq!(
6332 fire(|info| nodegraph_on_fileinput_button_clicked(
6333 fd.clone(),
6334 info,
6335 FileInputState::default()
6336 )),
6337 Update::DoNothing,
6338 );
6339 }
6340}