mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 16:18:11 +08:00
Clean up DocumentMessageHandler by breaking out several lengthy handlers into helper functions (#3568)
refactor(document): extract large match arms into helper methods Part of #2740 - Code quality refactors. This commit addresses the 'Match statements' section of the tracking issue by extracting three large match arms from process_message() into dedicated helper methods: - handle_group_selected_layers: Handles layer grouping in both artboard and non-artboard workflows (~50 lines extracted) - handle_move_selected_layers_to: Handles layer movement with proper transform preservation (~85 lines extracted) - handle_nudge_selected_layers: Handles keyboard nudging with optional resize mode (~75 lines extracted) The main match statement in DocumentMessageHandler is now ~210 lines shorter and more readable. Logic and behavior remain unchanged.
This commit is contained in:
@@ -623,145 +623,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
}
|
}
|
||||||
DocumentMessage::GroupSelectedLayers { group_folder_type } => {
|
DocumentMessage::GroupSelectedLayers { group_folder_type } => {
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
self.handle_group_selected_layers(group_folder_type, responses);
|
||||||
|
|
||||||
let mut parent_per_selected_nodes: HashMap<LayerNodeIdentifier, Vec<NodeId>> = HashMap::new();
|
|
||||||
let artboards = LayerNodeIdentifier::ROOT_PARENT
|
|
||||||
.children(self.metadata())
|
|
||||||
.filter(|x| self.network_interface.is_artboard(&x.to_node(), &self.selection_network_path))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let selected_nodes = self.network_interface.selected_nodes();
|
|
||||||
|
|
||||||
// Non-artboard (infinite canvas) workflow
|
|
||||||
if artboards.is_empty() {
|
|
||||||
let Some(parent) = self.network_interface.deepest_common_ancestor(&selected_nodes, &self.selection_network_path, false) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let Some(selected_nodes) = &self.network_interface.selected_nodes_in_nested_network(&self.selection_network_path) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), selected_nodes, parent);
|
|
||||||
|
|
||||||
DocumentMessageHandler::group_layers(responses, insert_index, parent, group_folder_type, &mut self.network_interface);
|
|
||||||
}
|
|
||||||
// Artboard workflow
|
|
||||||
else {
|
|
||||||
for artboard in artboards {
|
|
||||||
let selected_descendants = artboard.descendants(self.metadata()).filter(|x| selected_nodes.selected_layers_contains(*x, self.metadata()));
|
|
||||||
for selected_descendant in selected_descendants {
|
|
||||||
parent_per_selected_nodes.entry(artboard).or_default().push(selected_descendant.to_node());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut new_folders: Vec<NodeId> = Vec::new();
|
|
||||||
|
|
||||||
for children in parent_per_selected_nodes.into_values() {
|
|
||||||
let child_selected_nodes = SelectedNodes(children);
|
|
||||||
let Some(parent) = self.network_interface.deepest_common_ancestor(&child_selected_nodes, &self.selection_network_path, false) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
let insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), &child_selected_nodes, parent);
|
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: child_selected_nodes.0 });
|
|
||||||
|
|
||||||
new_folders.push(DocumentMessageHandler::group_layers(responses, insert_index, parent, group_folder_type, &mut self.network_interface));
|
|
||||||
}
|
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: new_folders });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
DocumentMessage::MoveSelectedLayersTo { parent, insert_index } => {
|
DocumentMessage::MoveSelectedLayersTo { parent, insert_index } => {
|
||||||
if !self.selection_network_path.is_empty() {
|
self.handle_move_selected_layers_to(parent, insert_index, responses);
|
||||||
log::error!("Moving selected layers is only supported for the Document Network");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disallow trying to insert into self.
|
|
||||||
if self
|
|
||||||
.network_interface
|
|
||||||
.selected_nodes()
|
|
||||||
.selected_layers(self.metadata())
|
|
||||||
.any(|layer| parent.ancestors(self.metadata()).any(|ancestor| ancestor == layer))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Artboards can only have `ROOT_PARENT` as the parent.
|
|
||||||
let any_artboards = self
|
|
||||||
.network_interface
|
|
||||||
.selected_nodes()
|
|
||||||
.selected_layers(self.metadata())
|
|
||||||
.any(|layer| self.network_interface.is_artboard(&layer.to_node(), &self.selection_network_path));
|
|
||||||
if any_artboards && parent != LayerNodeIdentifier::ROOT_PARENT {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Non-artboards cannot be put at the top level if artboards also exist there
|
|
||||||
let selected_any_non_artboards = self
|
|
||||||
.network_interface
|
|
||||||
.selected_nodes()
|
|
||||||
.selected_layers(self.metadata())
|
|
||||||
.any(|layer| !self.network_interface.is_artboard(&layer.to_node(), &self.selection_network_path));
|
|
||||||
|
|
||||||
let top_level_artboards = LayerNodeIdentifier::ROOT_PARENT
|
|
||||||
.children(self.metadata())
|
|
||||||
.any(|layer| self.network_interface.is_artboard(&layer.to_node(), &self.selection_network_path));
|
|
||||||
|
|
||||||
if selected_any_non_artboards && parent == LayerNodeIdentifier::ROOT_PARENT && top_level_artboards {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let layers_to_move = self.network_interface.shallowest_unique_layers_sorted(&self.selection_network_path);
|
|
||||||
// Offset the index for layers to move that are below another layer to move. For example when moving 1 and 2 between 3 and 4, 2 should be inserted at the same index as 1 since 1 is moved first.
|
|
||||||
let layers_to_move_with_insert_offset = layers_to_move
|
|
||||||
.iter()
|
|
||||||
.map(|layer| {
|
|
||||||
if layer.parent(self.metadata()) != Some(parent) {
|
|
||||||
return (*layer, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
let upstream_selected_siblings = layer
|
|
||||||
.downstream_siblings(self.network_interface.document_metadata())
|
|
||||||
.filter(|sibling| {
|
|
||||||
sibling != layer
|
|
||||||
&& layers_to_move.iter().any(|layer| {
|
|
||||||
layer == sibling
|
|
||||||
&& layer
|
|
||||||
.parent(self.metadata())
|
|
||||||
.is_some_and(|parent| parent.children(self.metadata()).position(|child| child == *layer) < Some(insert_index))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.count();
|
|
||||||
(*layer, upstream_selected_siblings)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
|
||||||
|
|
||||||
for (layer_index, (layer_to_move, insert_offset)) in layers_to_move_with_insert_offset.into_iter().enumerate() {
|
|
||||||
responses.add(NodeGraphMessage::MoveLayerToStack {
|
|
||||||
layer: layer_to_move,
|
|
||||||
parent,
|
|
||||||
insert_index: insert_index + layer_index - insert_offset,
|
|
||||||
});
|
|
||||||
|
|
||||||
if layer_to_move.parent(self.metadata()) != Some(parent) {
|
|
||||||
// TODO: Fix this so it works when dragging a layer into a group parent which has a Transform node, which used to work before #2689 caused this regression by removing the empty vector table row.
|
|
||||||
// TODO: See #2688 for this issue.
|
|
||||||
let layer_local_transform = self.network_interface.document_metadata().transform_to_viewport(layer_to_move);
|
|
||||||
let undo_transform = self.network_interface.document_metadata().transform_to_viewport(parent).inverse();
|
|
||||||
let transform = undo_transform * layer_local_transform;
|
|
||||||
|
|
||||||
responses.add(GraphOperationMessage::TransformSet {
|
|
||||||
layer: layer_to_move,
|
|
||||||
transform,
|
|
||||||
transform_in: TransformIn::Local,
|
|
||||||
skip_rerender: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
|
||||||
}
|
}
|
||||||
DocumentMessage::MoveSelectedLayersToGroup { parent } => {
|
DocumentMessage::MoveSelectedLayersToGroup { parent } => {
|
||||||
// Group all shallowest unique selected layers in order
|
// Group all shallowest unique selected layers in order
|
||||||
@@ -786,83 +651,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
resize,
|
resize,
|
||||||
resize_opposite_corner,
|
resize_opposite_corner,
|
||||||
} => {
|
} => {
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
self.handle_nudge_selected_layers(delta_x, delta_y, resize, resize_opposite_corner, ipp, viewport, responses);
|
||||||
|
|
||||||
let resize = ipp.keyboard.key(resize);
|
|
||||||
let resize_opposite_corner = ipp.keyboard.key(resize_opposite_corner);
|
|
||||||
|
|
||||||
let can_move = |layer| {
|
|
||||||
let selected = self.network_interface.selected_nodes();
|
|
||||||
selected.layer_visible(layer, &self.network_interface) && !selected.layer_locked(layer, &self.network_interface)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Nudge translation without resizing
|
|
||||||
if !resize {
|
|
||||||
let transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y)));
|
|
||||||
responses.add(SelectToolMessage::ShiftSelectedNodes { offset: transform.translation });
|
|
||||||
|
|
||||||
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
|
|
||||||
responses.add(GraphOperationMessage::TransformChange {
|
|
||||||
layer,
|
|
||||||
transform,
|
|
||||||
transform_in: TransformIn::Local,
|
|
||||||
skip_rerender: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let selected_bounding_box = self.network_interface.selected_bounds_document_space(false, &[]);
|
|
||||||
let Some([existing_top_left, existing_bottom_right]) = selected_bounding_box else { return };
|
|
||||||
|
|
||||||
// Swap and negate coordinates as needed to match the resize direction that's closest to the current tilt angle
|
|
||||||
let tilt = (self.document_ptz.tilt() + std::f64::consts::TAU) % std::f64::consts::TAU;
|
|
||||||
let (delta_x, delta_y, opposite_x, opposite_y) = match ((tilt + std::f64::consts::FRAC_PI_4) / std::f64::consts::FRAC_PI_2).floor() as i32 % 4 {
|
|
||||||
0 => (delta_x, delta_y, false, false),
|
|
||||||
1 => (delta_y, -delta_x, false, true),
|
|
||||||
2 => (-delta_x, -delta_y, true, true),
|
|
||||||
3 => (-delta_y, delta_x, true, false),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let size = existing_bottom_right - existing_top_left;
|
|
||||||
// TODO: This is a hacky band-aid. It still results in the shape becoming zero-sized. Properly fix this using the correct math.
|
|
||||||
// If size is zero we clamp it to minimun value to avoid dividing by zero vector to calculate enlargement.
|
|
||||||
let size = size.max(DVec2::ONE);
|
|
||||||
let enlargement = DVec2::new(
|
|
||||||
if resize_opposite_corner != opposite_x { -delta_x } else { delta_x },
|
|
||||||
if resize_opposite_corner != opposite_y { -delta_y } else { delta_y },
|
|
||||||
);
|
|
||||||
let enlargement_factor = (enlargement + size) / size;
|
|
||||||
|
|
||||||
let position = DVec2::new(
|
|
||||||
existing_top_left.x + if resize_opposite_corner != opposite_x { delta_x } else { 0. },
|
|
||||||
existing_top_left.y + if resize_opposite_corner != opposite_y { delta_y } else { 0. },
|
|
||||||
);
|
|
||||||
let mut pivot = (existing_top_left * enlargement_factor - position) / (enlargement_factor - DVec2::ONE);
|
|
||||||
if !pivot.x.is_finite() {
|
|
||||||
pivot.x = 0.;
|
|
||||||
}
|
|
||||||
if !pivot.y.is_finite() {
|
|
||||||
pivot.y = 0.;
|
|
||||||
}
|
|
||||||
let scale = DAffine2::from_scale(enlargement_factor);
|
|
||||||
let pivot = DAffine2::from_translation(pivot);
|
|
||||||
let transformation = pivot * scale * pivot.inverse();
|
|
||||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);
|
|
||||||
|
|
||||||
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
|
|
||||||
let to = document_to_viewport.inverse() * self.metadata().downstream_transform_to_viewport(layer);
|
|
||||||
let original_transform = self.metadata().upstream_transform(layer.to_node());
|
|
||||||
let new = to.inverse() * transformation * to * original_transform;
|
|
||||||
responses.add(GraphOperationMessage::TransformSet {
|
|
||||||
layer,
|
|
||||||
transform: new,
|
|
||||||
transform_in: TransformIn::Local,
|
|
||||||
skip_rerender: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
DocumentMessage::PasteImage {
|
DocumentMessage::PasteImage {
|
||||||
name,
|
name,
|
||||||
@@ -2141,6 +1930,244 @@ impl DocumentMessageHandler {
|
|||||||
folder_id
|
folder_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper method for GroupSelectedLayers message.
|
||||||
|
/// Handles grouping layers in both artboard and non-artboard workflows.
|
||||||
|
fn handle_group_selected_layers(&mut self, group_folder_type: GroupFolderType, responses: &mut VecDeque<Message>) {
|
||||||
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
|
|
||||||
|
let mut parent_per_selected_nodes: HashMap<LayerNodeIdentifier, Vec<NodeId>> = HashMap::new();
|
||||||
|
let artboards = LayerNodeIdentifier::ROOT_PARENT
|
||||||
|
.children(self.metadata())
|
||||||
|
.filter(|x| self.network_interface.is_artboard(&x.to_node(), &self.selection_network_path))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let selected_nodes = self.network_interface.selected_nodes();
|
||||||
|
|
||||||
|
// Non-artboard (infinite canvas) workflow
|
||||||
|
if artboards.is_empty() {
|
||||||
|
let Some(parent) = self.network_interface.deepest_common_ancestor(&selected_nodes, &self.selection_network_path, false) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(selected_nodes) = &self.network_interface.selected_nodes_in_nested_network(&self.selection_network_path) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), selected_nodes, parent);
|
||||||
|
|
||||||
|
DocumentMessageHandler::group_layers(responses, insert_index, parent, group_folder_type, &mut self.network_interface);
|
||||||
|
}
|
||||||
|
// Artboard workflow
|
||||||
|
else {
|
||||||
|
for artboard in artboards {
|
||||||
|
let selected_descendants = artboard.descendants(self.metadata()).filter(|x| selected_nodes.selected_layers_contains(*x, self.metadata()));
|
||||||
|
for selected_descendant in selected_descendants {
|
||||||
|
parent_per_selected_nodes.entry(artboard).or_default().push(selected_descendant.to_node());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut new_folders: Vec<NodeId> = Vec::new();
|
||||||
|
|
||||||
|
for children in parent_per_selected_nodes.into_values() {
|
||||||
|
let child_selected_nodes = SelectedNodes(children);
|
||||||
|
let Some(parent) = self.network_interface.deepest_common_ancestor(&child_selected_nodes, &self.selection_network_path, false) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let insert_index = DocumentMessageHandler::get_calculated_insert_index(self.metadata(), &child_selected_nodes, parent);
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: child_selected_nodes.0 });
|
||||||
|
|
||||||
|
new_folders.push(DocumentMessageHandler::group_layers(responses, insert_index, parent, group_folder_type, &mut self.network_interface));
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: new_folders });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper method for MoveSelectedLayersTo message.
|
||||||
|
/// Handles moving selected layers to a new parent with proper transform preservation.
|
||||||
|
fn handle_move_selected_layers_to(&mut self, parent: LayerNodeIdentifier, insert_index: usize, responses: &mut VecDeque<Message>) {
|
||||||
|
if !self.selection_network_path.is_empty() {
|
||||||
|
log::error!("Moving selected layers is only supported for the Document Network");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disallow trying to insert into self.
|
||||||
|
if self
|
||||||
|
.network_interface
|
||||||
|
.selected_nodes()
|
||||||
|
.selected_layers(self.metadata())
|
||||||
|
.any(|layer| parent.ancestors(self.metadata()).any(|ancestor| ancestor == layer))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Artboards can only have `ROOT_PARENT` as the parent.
|
||||||
|
let any_artboards = self
|
||||||
|
.network_interface
|
||||||
|
.selected_nodes()
|
||||||
|
.selected_layers(self.metadata())
|
||||||
|
.any(|layer| self.network_interface.is_artboard(&layer.to_node(), &self.selection_network_path));
|
||||||
|
if any_artboards && parent != LayerNodeIdentifier::ROOT_PARENT {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-artboards cannot be put at the top level if artboards also exist there
|
||||||
|
let selected_any_non_artboards = self
|
||||||
|
.network_interface
|
||||||
|
.selected_nodes()
|
||||||
|
.selected_layers(self.metadata())
|
||||||
|
.any(|layer| !self.network_interface.is_artboard(&layer.to_node(), &self.selection_network_path));
|
||||||
|
|
||||||
|
let top_level_artboards = LayerNodeIdentifier::ROOT_PARENT
|
||||||
|
.children(self.metadata())
|
||||||
|
.any(|layer| self.network_interface.is_artboard(&layer.to_node(), &self.selection_network_path));
|
||||||
|
|
||||||
|
if selected_any_non_artboards && parent == LayerNodeIdentifier::ROOT_PARENT && top_level_artboards {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let layers_to_move = self.network_interface.shallowest_unique_layers_sorted(&self.selection_network_path);
|
||||||
|
// Offset the index for layers to move that are below another layer to move. For example when moving 1 and 2 between 3 and 4, 2 should be inserted at the same index as 1 since 1 is moved first.
|
||||||
|
let layers_to_move_with_insert_offset = layers_to_move
|
||||||
|
.iter()
|
||||||
|
.map(|layer| {
|
||||||
|
if layer.parent(self.metadata()) != Some(parent) {
|
||||||
|
return (*layer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
let upstream_selected_siblings = layer
|
||||||
|
.downstream_siblings(self.network_interface.document_metadata())
|
||||||
|
.filter(|sibling| {
|
||||||
|
sibling != layer
|
||||||
|
&& layers_to_move.iter().any(|layer| {
|
||||||
|
layer == sibling
|
||||||
|
&& layer
|
||||||
|
.parent(self.metadata())
|
||||||
|
.is_some_and(|parent| parent.children(self.metadata()).position(|child| child == *layer) < Some(insert_index))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
(*layer, upstream_selected_siblings)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
|
|
||||||
|
for (layer_index, (layer_to_move, insert_offset)) in layers_to_move_with_insert_offset.into_iter().enumerate() {
|
||||||
|
responses.add(NodeGraphMessage::MoveLayerToStack {
|
||||||
|
layer: layer_to_move,
|
||||||
|
parent,
|
||||||
|
insert_index: insert_index + layer_index - insert_offset,
|
||||||
|
});
|
||||||
|
|
||||||
|
if layer_to_move.parent(self.metadata()) != Some(parent) {
|
||||||
|
// TODO: Fix this so it works when dragging a layer into a group parent which has a Transform node, which used to work before #2689 caused this regression by removing the empty vector table row.
|
||||||
|
// TODO: See #2688 for this issue.
|
||||||
|
let layer_local_transform = self.network_interface.document_metadata().transform_to_viewport(layer_to_move);
|
||||||
|
let undo_transform = self.network_interface.document_metadata().transform_to_viewport(parent).inverse();
|
||||||
|
let transform = undo_transform * layer_local_transform;
|
||||||
|
|
||||||
|
responses.add(GraphOperationMessage::TransformSet {
|
||||||
|
layer: layer_to_move,
|
||||||
|
transform,
|
||||||
|
transform_in: TransformIn::Local,
|
||||||
|
skip_rerender: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper method for NudgeSelectedLayers message.
|
||||||
|
/// Handles keyboard nudging of selected layers with optional resize mode.
|
||||||
|
fn handle_nudge_selected_layers(
|
||||||
|
&mut self,
|
||||||
|
delta_x: f64,
|
||||||
|
delta_y: f64,
|
||||||
|
resize: Key,
|
||||||
|
resize_opposite_corner: Key,
|
||||||
|
ipp: &InputPreprocessorMessageHandler,
|
||||||
|
viewport: &ViewportMessageHandler,
|
||||||
|
responses: &mut VecDeque<Message>,
|
||||||
|
) {
|
||||||
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
|
|
||||||
|
let resize = ipp.keyboard.key(resize);
|
||||||
|
let resize_opposite_corner = ipp.keyboard.key(resize_opposite_corner);
|
||||||
|
|
||||||
|
let can_move = |layer| {
|
||||||
|
let selected = self.network_interface.selected_nodes();
|
||||||
|
selected.layer_visible(layer, &self.network_interface) && !selected.layer_locked(layer, &self.network_interface)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Nudge translation without resizing
|
||||||
|
if !resize {
|
||||||
|
let transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y)));
|
||||||
|
responses.add(SelectToolMessage::ShiftSelectedNodes { offset: transform.translation });
|
||||||
|
|
||||||
|
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
|
||||||
|
responses.add(GraphOperationMessage::TransformChange {
|
||||||
|
layer,
|
||||||
|
transform,
|
||||||
|
transform_in: TransformIn::Local,
|
||||||
|
skip_rerender: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let selected_bounding_box = self.network_interface.selected_bounds_document_space(false, &[]);
|
||||||
|
let Some([existing_top_left, existing_bottom_right]) = selected_bounding_box else { return };
|
||||||
|
|
||||||
|
// Swap and negate coordinates as needed to match the resize direction that's closest to the current tilt angle
|
||||||
|
let tilt = (self.document_ptz.tilt() + std::f64::consts::TAU) % std::f64::consts::TAU;
|
||||||
|
let (delta_x, delta_y, opposite_x, opposite_y) = match ((tilt + std::f64::consts::FRAC_PI_4) / std::f64::consts::FRAC_PI_2).floor() as i32 % 4 {
|
||||||
|
0 => (delta_x, delta_y, false, false),
|
||||||
|
1 => (delta_y, -delta_x, false, true),
|
||||||
|
2 => (-delta_x, -delta_y, true, true),
|
||||||
|
3 => (-delta_y, delta_x, true, false),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let size = existing_bottom_right - existing_top_left;
|
||||||
|
// TODO: This is a hacky band-aid. It still results in the shape becoming zero-sized. Properly fix this using the correct math.
|
||||||
|
// If size is zero we clamp it to minimun value to avoid dividing by zero vector to calculate enlargement.
|
||||||
|
let size = size.max(DVec2::ONE);
|
||||||
|
let enlargement = DVec2::new(
|
||||||
|
if resize_opposite_corner != opposite_x { -delta_x } else { delta_x },
|
||||||
|
if resize_opposite_corner != opposite_y { -delta_y } else { delta_y },
|
||||||
|
);
|
||||||
|
let enlargement_factor = (enlargement + size) / size;
|
||||||
|
|
||||||
|
let position = DVec2::new(
|
||||||
|
existing_top_left.x + if resize_opposite_corner != opposite_x { delta_x } else { 0. },
|
||||||
|
existing_top_left.y + if resize_opposite_corner != opposite_y { delta_y } else { 0. },
|
||||||
|
);
|
||||||
|
let mut pivot = (existing_top_left * enlargement_factor - position) / (enlargement_factor - DVec2::ONE);
|
||||||
|
if !pivot.x.is_finite() {
|
||||||
|
pivot.x = 0.;
|
||||||
|
}
|
||||||
|
if !pivot.y.is_finite() {
|
||||||
|
pivot.y = 0.;
|
||||||
|
}
|
||||||
|
let scale = DAffine2::from_scale(enlargement_factor);
|
||||||
|
let pivot = DAffine2::from_translation(pivot);
|
||||||
|
let transformation = pivot * scale * pivot.inverse();
|
||||||
|
let document_to_viewport = self.navigation_handler.calculate_offset_transform(viewport.center_in_viewport_space().into(), &self.document_ptz);
|
||||||
|
|
||||||
|
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
|
||||||
|
let to = document_to_viewport.inverse() * self.metadata().downstream_transform_to_viewport(layer);
|
||||||
|
let original_transform = self.metadata().upstream_transform(layer.to_node());
|
||||||
|
let new = to.inverse() * transformation * to * original_transform;
|
||||||
|
responses.add(GraphOperationMessage::TransformSet {
|
||||||
|
layer,
|
||||||
|
transform: new,
|
||||||
|
transform_in: TransformIn::Local,
|
||||||
|
skip_rerender: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Loads all of the fonts in the document.
|
/// Loads all of the fonts in the document.
|
||||||
pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>) {
|
pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>) {
|
||||||
let mut fonts_to_load = HashSet::new();
|
let mut fonts_to_load = HashSet::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user