Skip to content

Commit ad7a721

Browse files
authored
Merge branch 'master' into fix-min-max-macro
2 parents fc7c6e1 + c4e16e1 commit ad7a721

File tree

11 files changed

+270
-72
lines changed

11 files changed

+270
-72
lines changed

editor/src/messages/portfolio/document/graph_operation/utility_types.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ impl<'a> ModifyInputsContext<'a> {
292292
// If inserting a path node, insert a flatten vector elements if the type is a graphic group.
293293
// TODO: Allow the path node to operate on Graphic Group data by utilizing the reference for each vector data in a group.
294294
if node_definition.identifier == "Path" {
295-
let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]).0.nested_type();
295+
let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]).0.nested_type().clone();
296296
if layer_input_type == concrete!(GraphicGroupTable) {
297297
let Some(flatten_vector_elements_definition) = resolve_document_node_type("Flatten Vector Elements") else {
298298
log::error!("Flatten Vector Elements does not exist in ModifyInputsContext::existing_node_id");

editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use super::node_properties;
22
use super::utility_types::FrontendNodeType;
33
use crate::messages::layout::utility_types::widget_prelude::*;
44
use crate::messages::portfolio::document::utility_types::network_interface::{
5-
DocumentNodeMetadata, DocumentNodePersistentMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata, NumberInputSettings,
6-
PropertiesRow, Vec2InputSettings, WidgetOverride,
5+
DocumentNodeMetadata, DocumentNodePersistentMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodePersistentMetadata, NodePosition, NodeTemplate,
6+
NodeTypePersistentMetadata, NumberInputSettings, PropertiesRow, Vec2InputSettings, WidgetOverride,
77
};
88
use crate::messages::portfolio::utility_types::PersistentData;
99
use crate::messages::prelude::Message;
@@ -2663,6 +2663,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
26632663
let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap();
26642664
'outer: for (id, metadata) in graphene_core::registry::NODE_METADATA.lock().unwrap().iter() {
26652665
use graphene_core::registry::*;
2666+
let id = id.clone();
26662667

26672668
for node in custom.iter() {
26682669
let DocumentNodeDefinition {
@@ -2673,7 +2674,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
26732674
..
26742675
} = node;
26752676
match implementation {
2676-
DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == id => continue 'outer,
2677+
DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == &id => continue 'outer,
26772678
_ => (),
26782679
}
26792680
}
@@ -2685,12 +2686,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
26852686
description,
26862687
properties,
26872688
} = metadata;
2688-
let Some(implementations) = &node_registry.get(id) else { continue };
2689+
let Some(implementations) = &node_registry.get(&id) else { continue };
26892690
let valid_inputs: HashSet<_> = implementations.iter().map(|(_, node_io)| node_io.call_argument.clone()).collect();
26902691
let first_node_io = implementations.first().map(|(_, node_io)| node_io).unwrap_or(const { &NodeIOTypes::empty() });
26912692
let mut input_type = &first_node_io.call_argument;
26922693
if valid_inputs.len() > 1 {
2693-
input_type = &const { generic!(T) };
2694+
input_type = &const { generic!(D) };
26942695
}
26952696
let output_type = &first_node_io.return_value;
26962697

@@ -2740,6 +2741,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
27402741
output_names: vec![output_type.to_string()],
27412742
has_primary_output: true,
27422743
locked: false,
2744+
27432745
..Default::default()
27442746
},
27452747
},

editor/src/messages/portfolio/document/utility_types/network_interface.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,7 @@ impl NodeNetworkInterface {
654654
let input_type = self.input_type(&InputConnector::node(*node_id, iterator_index), network_path).0;
655655
// Value inputs are stored as concrete, so they are compared to the nested type. Node inputs are stored as fn, so they are compared to the entire type.
656656
// For example a node input of (Footprint) -> VectorData would not be compatible with () -> VectorData
657-
node_io.inputs[iterator_index].clone().nested_type() == input_type || node_io.inputs[iterator_index] == input_type
657+
node_io.inputs[iterator_index].clone().nested_type() == &input_type || node_io.inputs[iterator_index] == input_type
658658
});
659659
if valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None }
660660
})

editor/src/messages/tool/common_functionality/graph_modification_utils.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ impl<'a> NodeGraphLayer<'a> {
420420

421421
/// Check if a layer is a raster layer
422422
pub fn is_raster_layer(layer: LayerNodeIdentifier, network_interface: &mut NodeNetworkInterface) -> bool {
423-
let layer_input_type = network_interface.input_type(&InputConnector::node(layer.to_node(), 1), &[]).0.nested_type();
423+
let layer_input_type = network_interface.input_type(&InputConnector::node(layer.to_node(), 1), &[]).0.nested_type().clone();
424424
if layer_input_type == concrete!(graphene_core::raster::image::ImageFrameTable<graphene_core::Color>)
425425
|| layer_input_type == concrete!(graphene_core::application_io::TextureFrameTable)
426426
|| layer_input_type == concrete!(graphene_std::RasterFrame)

editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs

Lines changed: 157 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,7 @@ mod test_transform_layer {
705705
use crate::messages::portfolio::document::graph_operation::transform_utils;
706706
use crate::test_utils::test_prelude::*;
707707
// Use ModifyInputsContext to locate the transform node
708-
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
708+
use crate::messages::portfolio::document::graph_operation::utility_types::{ModifyInputsContext, TransformIn};
709709
use crate::messages::prelude::Message;
710710
use glam::DAffine2;
711711
use std::collections::VecDeque;
@@ -908,4 +908,160 @@ mod test_transform_layer {
908908
let translation_diff = (after_cancel.translation - original_transform.translation).length();
909909
assert!(translation_diff < 1.0, "Translation component changed too much: {}", translation_diff);
910910
}
911+
912+
#[tokio::test]
913+
async fn test_grab_rotate_scale_chained() {
914+
let mut editor = EditorTestUtils::create();
915+
editor.new_document().await;
916+
editor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;
917+
let document = editor.active_document();
918+
let layer = document.metadata().all_layers().next().unwrap();
919+
editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }).await;
920+
let original_transform = get_layer_transform(&mut editor, layer).await.unwrap();
921+
922+
editor.handle_message(TransformLayerMessage::BeginGrab).await;
923+
editor.move_mouse(150.0, 130.0, ModifierKeys::empty(), MouseKeys::NONE).await;
924+
editor
925+
.handle_message(TransformLayerMessage::PointerMove {
926+
slow_key: Key::Shift,
927+
increments_key: Key::Control,
928+
})
929+
.await;
930+
931+
let after_grab_transform = get_layer_transform(&mut editor, layer).await.unwrap();
932+
let expected_translation = DVec2::new(50.0, 30.0);
933+
let actual_translation = after_grab_transform.translation - original_transform.translation;
934+
assert!(
935+
(actual_translation - expected_translation).length() < 1e-5,
936+
"Expected translation of {:?}, got {:?}",
937+
expected_translation,
938+
actual_translation
939+
);
940+
941+
// 2. Chain to rotation - from current position to create ~45 degree rotation
942+
editor.handle_message(TransformLayerMessage::BeginRotate).await;
943+
editor.move_mouse(190.0, 90.0, ModifierKeys::empty(), MouseKeys::NONE).await;
944+
editor
945+
.handle_message(TransformLayerMessage::PointerMove {
946+
slow_key: Key::Shift,
947+
increments_key: Key::Control,
948+
})
949+
.await;
950+
let after_rotate_transform = get_layer_transform(&mut editor, layer).await.unwrap();
951+
// Checking for off-diagonal elements close to 0.707, which corresponds to cos(45°) and sin(45°)
952+
assert!(
953+
!after_rotate_transform.matrix2.abs_diff_eq(after_grab_transform.matrix2, 1e-5) &&
954+
(after_rotate_transform.matrix2.x_axis.y.abs() - 0.707).abs() < 0.1 && // Check for off-diagonal elements close to 0.707
955+
(after_rotate_transform.matrix2.y_axis.x.abs() - 0.707).abs() < 0.1, // that would indicate ~45° rotation
956+
"Rotation should change matrix components with approximately 45° rotation"
957+
);
958+
959+
// 3. Chain to scaling - scale(area) up by 2x
960+
editor.handle_message(TransformLayerMessage::BeginScale).await;
961+
editor.move_mouse(250.0, 200.0, ModifierKeys::empty(), MouseKeys::NONE).await;
962+
editor
963+
.handle_message(TransformLayerMessage::PointerMove {
964+
slow_key: Key::Shift,
965+
increments_key: Key::Control,
966+
})
967+
.await;
968+
969+
let after_scale_transform = get_layer_transform(&mut editor, layer).await.unwrap();
970+
let before_scale_det = after_rotate_transform.matrix2.determinant();
971+
let after_scale_det = after_scale_transform.matrix2.determinant();
972+
assert!(
973+
after_scale_det >= 2.0 * before_scale_det,
974+
"Scale should increase the determinant of the matrix (before: {}, after: {})",
975+
before_scale_det,
976+
after_scale_det
977+
);
978+
979+
editor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;
980+
let final_transform = get_layer_transform(&mut editor, layer).await.unwrap();
981+
982+
assert!(final_transform.abs_diff_eq(after_scale_transform, 1e-5), "Final transform should match the transform before committing");
983+
assert!(!final_transform.abs_diff_eq(original_transform, 1e-5), "Final transform should be different from original transform");
984+
}
985+
986+
#[tokio::test]
987+
async fn test_scale_with_panned_view() {
988+
let mut editor = EditorTestUtils::create();
989+
editor.new_document().await;
990+
editor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;
991+
let document = editor.active_document();
992+
let layer = document.metadata().all_layers().next().unwrap();
993+
994+
let original_transform = get_layer_transform(&mut editor, layer).await.unwrap();
995+
996+
let pan_amount = DVec2::new(200.0, 150.0);
997+
editor.handle_message(NavigationMessage::CanvasPan { delta: pan_amount }).await;
998+
999+
editor.handle_message(TransformLayerMessage::BeginScale).await;
1000+
editor.handle_message(TransformLayerMessage::TypeDigit { digit: 2 }).await;
1001+
editor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;
1002+
1003+
let final_transform = get_layer_transform(&mut editor, layer).await.unwrap();
1004+
1005+
let scale_x = final_transform.matrix2.x_axis.length() / original_transform.matrix2.x_axis.length();
1006+
let scale_y = final_transform.matrix2.y_axis.length() / original_transform.matrix2.y_axis.length();
1007+
1008+
assert!((scale_x - 2.0).abs() < 0.1, "Expected scale factor X of 2.0, got: {}", scale_x);
1009+
assert!((scale_y - 2.0).abs() < 0.1, "Expected scale factor Y of 2.0, got: {}", scale_y);
1010+
}
1011+
1012+
#[tokio::test]
1013+
async fn test_scale_with_zoomed_view() {
1014+
let mut editor = EditorTestUtils::create();
1015+
editor.new_document().await;
1016+
editor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;
1017+
let document = editor.active_document();
1018+
let layer = document.metadata().all_layers().next().unwrap();
1019+
1020+
let original_transform = get_layer_transform(&mut editor, layer).await.unwrap();
1021+
1022+
editor.handle_message(NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }).await;
1023+
editor.handle_message(NavigationMessage::CanvasZoomIncrease { center_on_mouse: false }).await;
1024+
1025+
editor.handle_message(TransformLayerMessage::BeginScale).await;
1026+
editor.handle_message(TransformLayerMessage::TypeDigit { digit: 2 }).await;
1027+
editor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;
1028+
1029+
let final_transform = get_layer_transform(&mut editor, layer).await.unwrap();
1030+
1031+
let scale_x = final_transform.matrix2.x_axis.length() / original_transform.matrix2.x_axis.length();
1032+
let scale_y = final_transform.matrix2.y_axis.length() / original_transform.matrix2.y_axis.length();
1033+
1034+
assert!((scale_x - 2.0).abs() < 0.1, "Expected scale factor X of 2.0, got: {}", scale_x);
1035+
assert!((scale_y - 2.0).abs() < 0.1, "Expected scale factor Y of 2.0, got: {}", scale_y);
1036+
}
1037+
1038+
#[tokio::test]
1039+
async fn test_rotate_with_rotated_view() {
1040+
let mut editor = EditorTestUtils::create();
1041+
editor.new_document().await;
1042+
editor.drag_tool(ToolType::Rectangle, 0., 0., 100., 100., ModifierKeys::empty()).await;
1043+
let document = editor.active_document();
1044+
let layer = document.metadata().all_layers().next().unwrap();
1045+
1046+
let original_transform = get_layer_transform(&mut editor, layer).await.unwrap();
1047+
1048+
// Rotate the document view (45 degrees)
1049+
editor.handle_message(NavigationMessage::BeginCanvasTilt { was_dispatched_from_menu: false }).await;
1050+
editor.handle_message(NavigationMessage::CanvasTiltSet { angle_radians: 45.0_f64.to_radians() }).await;
1051+
editor.handle_message(TransformLayerMessage::BeginRotate).await;
1052+
1053+
editor.handle_message(TransformLayerMessage::TypeDigit { digit: 9 }).await;
1054+
editor.handle_message(TransformLayerMessage::TypeDigit { digit: 0 }).await;
1055+
editor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;
1056+
1057+
let final_transform = get_layer_transform(&mut editor, layer).await.unwrap();
1058+
1059+
let original_angle = original_transform.to_scale_angle_translation().1;
1060+
let final_angle = final_transform.to_scale_angle_translation().1;
1061+
let angle_change = (final_angle - original_angle).to_degrees();
1062+
1063+
// Normalize angle between 0 and 360
1064+
let angle_change = ((angle_change % 360.0) + 360.0) % 360.0;
1065+
assert!((angle_change - 90.0).abs() < 0.1, "Expected rotation of 90 degrees, got: {}", angle_change);
1066+
}
9111067
}

node-graph/gcore/src/registry.rs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use crate::transform::Footprint;
21
use crate::{Node, NodeIO, NodeIOTypes, Type, WasmNotSend};
32
use dyn_any::{DynAny, StaticType};
43
use std::collections::HashMap;
@@ -251,21 +250,6 @@ where
251250
};
252251
match dyn_any::downcast(input) {
253252
Ok(input) => Box::pin(output(*input)),
254-
// If the input type of the node is `()` and we supply an invalid type, we can still call the
255-
// node and just ignore the input and call it with the unit type instead.
256-
Err(_) if core::any::TypeId::of::<_I::Static>() == core::any::TypeId::of::<()>() => {
257-
assert_eq!(std::mem::size_of::<_I>(), 0);
258-
// Rust can't know, that `_I` and `()` are the same size, so we have to use a `transmute_copy()` here
259-
Box::pin(output(unsafe { std::mem::transmute_copy(&()) }))
260-
}
261-
// If the Node expects a footprint but we provide (). In this case construct the default Footprint and pass that
262-
// This is pretty hacky pls fix
263-
Err(_) if core::any::TypeId::of::<_I::Static>() == core::any::TypeId::of::<Footprint>() => {
264-
assert_eq!(std::mem::size_of::<_I>(), std::mem::size_of::<Footprint>());
265-
assert_eq!(std::mem::align_of::<_I>(), std::mem::align_of::<Footprint>());
266-
// Rust can't know, that `_I` and `Footprint` are the same size, so we have to use a `transmute_copy()` here
267-
Box::pin(output(unsafe { std::mem::transmute_copy(&Footprint::default()) }))
268-
}
269253
Err(e) => panic!("DynAnyNode Input, {0} in:\n{1}", e, node_name),
270254
}
271255
}

node-graph/gcore/src/types.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,14 +293,26 @@ impl Type {
293293
}
294294
}
295295

296-
pub fn nested_type(self) -> Type {
296+
pub fn nested_type(&self) -> &Type {
297297
match self {
298298
Self::Generic(_) => self,
299299
Self::Concrete(_) => self,
300300
Self::Fn(_, output) => output.nested_type(),
301301
Self::Future(output) => output.nested_type(),
302302
}
303303
}
304+
305+
pub fn replace_nested(&mut self, f: impl Fn(&Type) -> Option<Type>) -> Option<Type> {
306+
if let Some(replacement) = f(self) {
307+
return Some(std::mem::replace(self, replacement));
308+
}
309+
match self {
310+
Self::Generic(_) => None,
311+
Self::Concrete(_) => None,
312+
Self::Fn(_, output) => output.replace_nested(f),
313+
Self::Future(output) => output.replace_nested(f),
314+
}
315+
}
304316
}
305317

306318
fn format_type(ty: &str) -> String {

node-graph/gcore/src/vector/vector_data.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,10 @@ impl HandleId {
519519

520520
/// Calculate the magnitude of the handle from the anchor.
521521
pub fn length(self, vector_data: &VectorData) -> f64 {
522-
let anchor_position = self.to_manipulator_point().get_anchor_position(vector_data).unwrap();
522+
let Some(anchor_position) = self.to_manipulator_point().get_anchor_position(vector_data) else {
523+
// TODO: This was previously an unwrap which was encountered, so this is a temporary way to avoid a crash
524+
return 0.;
525+
};
523526
let handle_position = self.to_manipulator_point().get_position(vector_data);
524527
handle_position.map(|pos| (pos - anchor_position).length()).unwrap_or(f64::MAX)
525528
}

node-graph/graph-craft/src/document.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -281,21 +281,16 @@ impl DocumentNode {
281281
self.inputs[index] = NodeInput::Node { node_id, output_index, lambda };
282282
let input_source = &mut self.original_location.inputs_source;
283283
for source in source {
284-
input_source.insert(source, index + self.original_location.skip_inputs - skip);
284+
input_source.insert(source, (index + self.original_location.skip_inputs).saturating_sub(skip));
285285
}
286286
}
287287

288288
fn resolve_proto_node(mut self) -> ProtoNode {
289289
assert!(!self.inputs.is_empty() || self.manual_composition.is_some(), "Resolving document node {self:#?} with no inputs");
290-
let DocumentNodeImplementation::ProtoNode(fqn) = self.implementation else {
290+
let DocumentNodeImplementation::ProtoNode(identifier) = self.implementation else {
291291
unreachable!("tried to resolve not flattened node on resolved node {self:?}");
292292
};
293293

294-
// TODO replace with proper generics removal
295-
let identifier = match fqn.name.clone().split_once('<') {
296-
Some((path, _generics)) => ProtoNodeIdentifier { name: Cow::Owned(path.to_string()) },
297-
_ => ProtoNodeIdentifier { name: fqn.name },
298-
};
299294
let (input, mut args) = if let Some(ty) = self.manual_composition {
300295
(ProtoNodeInput::ManualComposition(ty), ConstructionArgs::Nodes(vec![]))
301296
} else {

0 commit comments

Comments
 (0)