mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-25 23:58:11 +08:00
Fix transforms, Brush tool, and G/R/S (#1473)
* Transform fixes * Fix the desert artwork * Change artboard icon * Better handling when transforming brush strokes * Code review pass --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
5ee79031ab
commit
b2ca643e6e
@@ -158,7 +158,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
||||
}
|
||||
#[remain::unsorted]
|
||||
Navigation(message) => {
|
||||
let document_bounds = self.metadata().document_bounds();
|
||||
let document_bounds = self.metadata().document_bounds_viewport_space();
|
||||
self.navigation_handler.process_message(
|
||||
message,
|
||||
responses,
|
||||
@@ -306,7 +306,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
||||
responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
}
|
||||
DeselectAllLayers => {
|
||||
responses.add_front(SetSelectedLayers { replacement_selected_layers: vec![] });
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||
self.layer_range_selection_reference = None;
|
||||
}
|
||||
DirtyRenderDocument => {
|
||||
@@ -623,7 +623,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
||||
|
||||
let viewport_size = ipp.viewport_bounds.size();
|
||||
let viewport_mid = ipp.viewport_bounds.center();
|
||||
let [bounds1, bounds2] = self.metadata().document_bounds().unwrap_or([viewport_mid; 2]);
|
||||
let [bounds1, bounds2] = self.metadata().document_bounds_viewport_space().unwrap_or([viewport_mid; 2]);
|
||||
let bounds1 = bounds1.min(viewport_mid) - viewport_size * scale;
|
||||
let bounds2 = bounds2.max(viewport_mid) + viewport_size * scale;
|
||||
let bounds_length = (bounds2 - bounds1) * (1. + SCROLLBAR_SPACING);
|
||||
@@ -657,20 +657,20 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
||||
})
|
||||
}
|
||||
SelectAllLayers => {
|
||||
let all = self.all_layers().map(|path| path.to_vec()).collect();
|
||||
responses.add_front(SetSelectedLayers { replacement_selected_layers: all });
|
||||
let all = self.metadata().all_layers().map(|layer| layer.to_node()).collect();
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: all });
|
||||
}
|
||||
SelectedLayersLower => {
|
||||
responses.add_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: -1 });
|
||||
responses.add(DocumentMessage::SelectedLayersReorder { relative_index_offset: 1 });
|
||||
}
|
||||
SelectedLayersLowerToBack => {
|
||||
responses.add_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MIN });
|
||||
responses.add(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MAX });
|
||||
}
|
||||
SelectedLayersRaise => {
|
||||
responses.add_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: 1 });
|
||||
responses.add(DocumentMessage::SelectedLayersReorder { relative_index_offset: -1 });
|
||||
}
|
||||
SelectedLayersRaiseToFront => {
|
||||
responses.add_front(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MAX });
|
||||
responses.add(DocumentMessage::SelectedLayersReorder { relative_index_offset: isize::MIN });
|
||||
}
|
||||
SelectedLayersReorder { relative_index_offset } => {
|
||||
self.selected_layers_reorder(relative_index_offset, responses);
|
||||
@@ -813,7 +813,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
||||
}
|
||||
SetViewMode { view_mode } => {
|
||||
self.view_mode = view_mode;
|
||||
responses.add_front(DocumentMessage::DirtyRenderDocument);
|
||||
responses.add_front(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
StartTransaction => self.backup(responses),
|
||||
ToggleLayerExpansion { layer } => {
|
||||
@@ -876,7 +876,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
||||
responses.add_front(NavigationMessage::SetCanvasZoom { zoom_factor: 2. });
|
||||
}
|
||||
ZoomCanvasToFitAll => {
|
||||
if let Some(bounds) = self.metadata().document_bounds() {
|
||||
if let Some(bounds) = self.metadata().document_bounds_document_space() {
|
||||
responses.add(NavigationMessage::FitViewportToBounds {
|
||||
bounds,
|
||||
padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR),
|
||||
|
||||
@@ -68,12 +68,10 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
|
||||
responses.add(SetCanvasZoom { zoom_factor: new_scale });
|
||||
}
|
||||
FitViewportToBounds {
|
||||
bounds: [bounds_corner_a, bounds_corner_b],
|
||||
bounds: [pos1, pos2],
|
||||
padding_scale_factor,
|
||||
prevent_zoom_past_100,
|
||||
} => {
|
||||
let pos1 = document.metadata.document_to_viewport.inverse().transform_point2(bounds_corner_a);
|
||||
let pos2 = document.metadata.document_to_viewport.inverse().transform_point2(bounds_corner_b);
|
||||
let v1 = document.metadata.document_to_viewport.inverse().transform_point2(DVec2::ZERO);
|
||||
let v2 = document.metadata.document_to_viewport.inverse().transform_point2(ipp.viewport_bounds.size());
|
||||
|
||||
@@ -98,8 +96,9 @@ impl MessageHandler<NavigationMessage, (&Document, Option<[DVec2; 2]>, &InputPre
|
||||
}
|
||||
FitViewportToSelection => {
|
||||
if let Some(bounds) = selection_bounds {
|
||||
let transform = document.metadata.document_to_viewport.inverse();
|
||||
responses.add(FitViewportToBounds {
|
||||
bounds,
|
||||
bounds: [transform.transform_point2(bounds[0]), transform.transform_point2(bounds[1])],
|
||||
padding_scale_factor: Some(VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR),
|
||||
prevent_zoom_past_100: false,
|
||||
})
|
||||
@@ -382,16 +381,18 @@ impl NavigationMessageHandler {
|
||||
}
|
||||
|
||||
pub fn calculate_offset_transform(&self, viewport_center: DVec2) -> DAffine2 {
|
||||
let scaled_centre = viewport_center / self.snapped_scale();
|
||||
|
||||
// Try to avoid fractional coordinates to reduce anti aliasing.
|
||||
let scale = self.snapped_scale();
|
||||
let rounded_pan = ((self.pan + viewport_center) * scale).round() / scale - viewport_center;
|
||||
let rounded_pan = ((self.pan + scaled_centre) * scale).round() / scale - scaled_centre;
|
||||
|
||||
// TODO: replace with DAffine2::from_scale_angle_translation and fix the errors
|
||||
let offset_transform = DAffine2::from_translation(viewport_center);
|
||||
let offset_transform = DAffine2::from_translation(scaled_centre);
|
||||
let scale_transform = DAffine2::from_scale(DVec2::splat(scale));
|
||||
let angle_transform = DAffine2::from_angle(self.snapped_angle());
|
||||
let translation_transform = DAffine2::from_translation(rounded_pan);
|
||||
scale_transform * offset_transform * angle_transform * offset_transform.inverse() * translation_transform
|
||||
scale_transform * offset_transform * angle_transform * translation_transform
|
||||
}
|
||||
|
||||
fn create_document_transform(&self, viewport_center: DVec2, responses: &mut VecDeque<Message>) {
|
||||
@@ -403,7 +404,7 @@ impl NavigationMessageHandler {
|
||||
let new_viewport_bounds = viewport_bounds / zoom_factor;
|
||||
let delta_size = viewport_bounds - new_viewport_bounds;
|
||||
let mouse_fraction = mouse / viewport_bounds;
|
||||
let delta = delta_size * (-mouse_fraction);
|
||||
let delta = delta_size * (DVec2::splat(0.5) - mouse_fraction);
|
||||
|
||||
NavigationMessage::TranslateCanvas { delta }.into()
|
||||
}
|
||||
|
||||
+12
-5
@@ -282,7 +282,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
};
|
||||
|
||||
let metadata = output_node.metadata.clone();
|
||||
let new_input = output_node.inputs[0].clone();
|
||||
let new_input = output_node.inputs.first().cloned().filter(|input| input.as_node().is_some());
|
||||
let node_id = generate_uuid();
|
||||
|
||||
output_node.metadata.position.x += 8;
|
||||
@@ -292,7 +292,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
warn!("Node type \"{name}\" doesn't exist");
|
||||
return;
|
||||
};
|
||||
let mut new_document_node = node_type.to_document_node_default_inputs([Some(new_input)], metadata);
|
||||
let mut new_document_node = node_type.to_document_node_default_inputs([new_input], metadata);
|
||||
update_input(&mut new_document_node.inputs, node_id, self.document_metadata);
|
||||
self.network.nodes.insert(node_id, new_document_node);
|
||||
}
|
||||
@@ -575,7 +575,10 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
|
||||
transform_in,
|
||||
skip_rerender,
|
||||
} => {
|
||||
let parent_transform = document.metadata.document_to_viewport * document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default();
|
||||
let layer_identifier = LayerNodeIdentifier::new(*layer.last().unwrap(), &document.document_network);
|
||||
let parent_transform = document
|
||||
.metadata
|
||||
.transform_to_viewport(layer_identifier.parent(&document.metadata).unwrap_or(LayerNodeIdentifier::ROOT));
|
||||
let bounds = LayerBounds::new(document, &layer);
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) {
|
||||
modify_inputs.transform_change(transform, transform_in, parent_transform, bounds, skip_rerender);
|
||||
@@ -597,8 +600,12 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
|
||||
transform_in,
|
||||
skip_rerender,
|
||||
} => {
|
||||
let parent_transform = document.metadata.document_to_viewport * document.multiply_transforms(&layer[..layer.len() - 1]).unwrap_or_default();
|
||||
let current_transform = Some(document.metadata.transform_to_viewport(LayerNodeIdentifier::new(*layer.last().unwrap(), &document.document_network)));
|
||||
let layer_identifier = LayerNodeIdentifier::new(*layer.last().unwrap(), &document.document_network);
|
||||
let parent_transform = document
|
||||
.metadata
|
||||
.transform_to_viewport(layer_identifier.parent(&document.metadata).unwrap_or(LayerNodeIdentifier::ROOT));
|
||||
|
||||
let current_transform = Some(document.metadata.transform_to_viewport(layer_identifier));
|
||||
let bounds = LayerBounds::new(document, &layer);
|
||||
if let Some(mut modify_inputs) = ModifyInputsContext::new_layer(&layer, document, node_graph, responses) {
|
||||
modify_inputs.transform_set(transform, transform_in, parent_transform, current_transform, bounds, skip_rerender);
|
||||
|
||||
@@ -34,7 +34,7 @@ impl OriginalTransforms {
|
||||
match self {
|
||||
OriginalTransforms::Layer(layer_map) => {
|
||||
for &layer in selected {
|
||||
layer_map.entry(layer).or_insert_with(|| document.metadata.transform_to_document(layer));
|
||||
layer_map.entry(layer).or_insert_with(|| document.metadata.local_transform(layer));
|
||||
}
|
||||
}
|
||||
OriginalTransforms::Path(path_map) => {
|
||||
@@ -349,7 +349,7 @@ impl<'a> Selected<'a> {
|
||||
let xy_summation = self
|
||||
.selected
|
||||
.iter()
|
||||
.filter_map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.document))
|
||||
.map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.document))
|
||||
.reduce(|a, b| a + b)
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -366,6 +366,40 @@ impl<'a> Selected<'a> {
|
||||
(min + max) / 2.
|
||||
}
|
||||
|
||||
fn transform_layer(document: &Document, layer: LayerNodeIdentifier, original_transform: Option<&DAffine2>, transformation: DAffine2, responses: &mut VecDeque<Message>) {
|
||||
let Some(&original_transform) = original_transform else { return };
|
||||
let parent = layer.parent(&document.metadata);
|
||||
let to = parent.map(|parent| document.metadata.transform_to_viewport(parent)).unwrap_or(document.metadata.document_to_viewport);
|
||||
let new = to.inverse() * transformation * to * original_transform;
|
||||
responses.add(GraphOperationMessage::TransformSet {
|
||||
layer: layer.to_path(),
|
||||
transform: new,
|
||||
transform_in: TransformIn::Local,
|
||||
skip_rerender: false,
|
||||
});
|
||||
}
|
||||
|
||||
fn transform_path(document: &Document, layer: LayerNodeIdentifier, initial_points: Option<&Vec<(ManipulatorPointId, DVec2)>>, transformation: DAffine2, responses: &mut VecDeque<Message>) {
|
||||
let viewspace = document.metadata.transform_to_viewport(layer);
|
||||
let layerspace_rotation = viewspace.inverse() * transformation;
|
||||
|
||||
let Some(initial_points) = initial_points else {
|
||||
return;
|
||||
};
|
||||
|
||||
for (point_id, position) in initial_points {
|
||||
let viewport_point = viewspace.transform_point2(*position);
|
||||
let new_pos_viewport = layerspace_rotation.transform_point2(viewport_point);
|
||||
let point = *point_id;
|
||||
let position = new_pos_viewport;
|
||||
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer.to_path(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_transforms(&mut self, delta: DAffine2) {
|
||||
if !self.selected.is_empty() {
|
||||
let pivot = DAffine2::from_translation(*self.pivot);
|
||||
@@ -374,56 +408,13 @@ impl<'a> Selected<'a> {
|
||||
// TODO: Cache the result of `shallowest_unique_layers` to avoid this heavy computation every frame of movement, see https://github.com/GraphiteEditor/Graphite/pull/481
|
||||
for layer_ancestors in self.document.metadata.shallowest_unique_layers(self.selected.iter().copied()) {
|
||||
let layer = *layer_ancestors.last().unwrap();
|
||||
let parent = layer.parent(&self.document.metadata);
|
||||
if *self.tool_type == ToolType::Select {
|
||||
let original_layer_transforms = match self.original_transforms {
|
||||
OriginalTransforms::Layer(layer_map) => *layer_map.get(&layer).unwrap(),
|
||||
OriginalTransforms::Path(_path_map) => {
|
||||
warn!("Found Path variant in original_transforms, returning identity transform for layer {layer:?}");
|
||||
DAffine2::IDENTITY
|
||||
}
|
||||
};
|
||||
let to = parent
|
||||
.map(|parent| self.document.metadata.transform_to_viewport(parent))
|
||||
.unwrap_or(self.document.metadata.document_to_viewport);
|
||||
let new = to.inverse() * transformation * to * original_layer_transforms;
|
||||
self.responses.add(GraphOperationMessage::TransformSet {
|
||||
layer: layer.to_path(),
|
||||
transform: new,
|
||||
transform_in: TransformIn::Local,
|
||||
skip_rerender: false,
|
||||
});
|
||||
|
||||
match &self.original_transforms {
|
||||
OriginalTransforms::Layer(layer_transforms) => Self::transform_layer(self.document, layer, layer_transforms.get(&layer), transformation, self.responses),
|
||||
OriginalTransforms::Path(path_transforms) => Self::transform_path(self.document, layer, path_transforms.get(&layer), transformation, self.responses),
|
||||
}
|
||||
if *self.tool_type == ToolType::Path {
|
||||
let viewspace = self.document.metadata.transform_to_viewport(layer);
|
||||
let layerspace_rotation = viewspace.inverse() * transformation;
|
||||
|
||||
let initial_points = match self.original_transforms {
|
||||
OriginalTransforms::Layer(_layer_map) => {
|
||||
warn!("Found Layer variant in original_transforms when Path wanted, returning identity transform for layer");
|
||||
None
|
||||
}
|
||||
OriginalTransforms::Path(path_map) => path_map.get(&layer),
|
||||
};
|
||||
|
||||
let Some(original) = initial_points else {
|
||||
warn!("Initial Points empty, it should not be possible to reach here without points");
|
||||
continue;
|
||||
};
|
||||
for (point_id, position) in original {
|
||||
let viewport_point = viewspace.transform_point2(*position);
|
||||
let new_pos_viewport = layerspace_rotation.transform_point2(viewport_point);
|
||||
let point = *point_id;
|
||||
let position = new_pos_viewport;
|
||||
|
||||
self.responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer.to_path(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
});
|
||||
}
|
||||
}
|
||||
self.responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
}
|
||||
self.responses.add(BroadcastEvent::DocumentIsDirty);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user