mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-24 08:05:04 +00:00
Fix transformation cage centered-scaling to use the center as its pivot (#769)
* Add centre of transformation * Add alt support * New breaking file format version * Spelling Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
5afaee1e4b
commit
41c137ed1b
9 changed files with 109 additions and 104 deletions
|
@ -1 +1 @@
|
|||
{"graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":10689566813179949075,"layer_ids":[10689566813179949074],"layers":[{"visible":true,"name":"Folder 1","data":{"Folder":{"next_assignment_id":17222868332373661780,"layer_ids":[17222868332373661779],"layers":[{"visible":true,"name":"Shape 1","data":{"Shape":{"shape":{"elements":[{"points":[{"position":[0.5,1.0],"manipulator_type":"Anchor"},{"position":[0.7761415,1.0],"manipulator_type":"InHandle"},{"position":[0.22385850000000002,1.0],"manipulator_type":"OutHandle"}]},{"points":[{"position":[0.0,0.5],"manipulator_type":"Anchor"},{"position":[0.0,0.7761415],"manipulator_type":"InHandle"},{"position":[0.0,0.22385850000000002],"manipulator_type":"OutHandle"}]},{"points":[{"position":[0.5,0.0],"manipulator_type":"Anchor"},{"position":[0.22385850000000002,0.0],"manipulator_type":"InHandle"},{"position":[0.7761415,0.0],"manipulator_type":"OutHandle"}]},{"points":[{"position":[1.0,0.5],"manipulator_type":"Anchor"},{"position":[1.0,0.22385850000000002],"manipulator_type":"InHandle"},{"position":[1.0,0.7761415],"manipulator_type":"OutHandle"}]},{"points":[null,null,null]}],"element_ids":[1,2,3,4,5]},"style":{"stroke":null,"fill":{"Solid":{"red":0.0,"green":0.0,"blue":0.0,"alpha":1.0}}},"render_index":1}},"transform":{"matrix2":[379.0,0.0,-0.0,239.0],"translation":[-479.3046875,-99.5]},"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[0.0,0.0]},"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[1060.3046875,373.5]},"blend_mode":"Normal","opacity":1.0}},"saved_document_identifier":6520881531418194372,"name":"Untitled Document","version":"0.0.12","document_mode":"DesignMode","view_mode":"Normal","snapping_enabled":true,"overlays_visible":true,"layer_metadata":[[[],{"selected":false,"expanded":true}],[[10689566813179949074],{"selected":false,"expanded":true}],[[10689566813179949074,17222868332373661779],{"selected":true,"expanded":false}]],"layer_range_selection_reference":[10689566813179949074,17222868332373661779],"navigation_handler":{"pan":[0.0,0.0],"panning":false,"snap_tilt":false,"snap_tilt_released":false,"tilt":0.0,"tilting":false,"zoom":1.0,"zooming":false,"snap_zoom":false,"mouse_position":[0.0,0.0]},"artboard_message_handler":{"artboards_graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":0,"layer_ids":[],"layers":[]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[1060.3046875,373.5]},"blend_mode":"Normal","opacity":1.0}},"artboard_ids":[]},"properties_panel_message_handler":{"active_selection":[[10689566813179949074,17222868332373661779],"Artwork"]}}
|
||||
{"graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":10689566813179949075,"layer_ids":[10689566813179949074],"layers":[{"visible":true,"name":"Folder 1","data":{"Folder":{"next_assignment_id":17222868332373661780,"layer_ids":[17222868332373661779],"layers":[{"visible":true,"name":"Shape 1","data":{"Shape":{"shape":{"elements":[{"points":[{"position":[0.5,1.0],"manipulator_type":"Anchor"},{"position":[0.7761415,1.0],"manipulator_type":"InHandle"},{"position":[0.22385850000000002,1.0],"manipulator_type":"OutHandle"}]},{"points":[{"position":[0.0,0.5],"manipulator_type":"Anchor"},{"position":[0.0,0.7761415],"manipulator_type":"InHandle"},{"position":[0.0,0.22385850000000002],"manipulator_type":"OutHandle"}]},{"points":[{"position":[0.5,0.0],"manipulator_type":"Anchor"},{"position":[0.22385850000000002,0.0],"manipulator_type":"InHandle"},{"position":[0.7761415,0.0],"manipulator_type":"OutHandle"}]},{"points":[{"position":[1.0,0.5],"manipulator_type":"Anchor"},{"position":[1.0,0.22385850000000002],"manipulator_type":"InHandle"},{"position":[1.0,0.7761415],"manipulator_type":"OutHandle"}]},{"points":[null,null,null]}],"element_ids":[1,2,3,4,5]},"style":{"stroke":null,"fill":{"Solid":{"red":0.0,"green":0.0,"blue":0.0,"alpha":1.0}}},"render_index":1}},"transform":{"matrix2":[379.0,0.0,-0.0,239.0],"translation":[-479.3046875,-99.5]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[0.0,0.0]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[1060.3046875,373.5]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}},"saved_document_identifier":6520881531418194372,"name":"Untitled Document","version":"0.0.13","document_mode":"DesignMode","view_mode":"Normal","snapping_enabled":true,"overlays_visible":true,"layer_metadata":[[[],{"selected":false,"expanded":true}],[[10689566813179949074],{"selected":false,"expanded":true}],[[10689566813179949074,17222868332373661779],{"selected":true,"expanded":false}]],"layer_range_selection_reference":[10689566813179949074,17222868332373661779],"navigation_handler":{"pan":[0.0,0.0],"panning":false,"snap_tilt":false,"snap_tilt_released":false,"tilt":0.0,"tilting":false,"zoom":1.0,"zooming":false,"snap_zoom":false,"mouse_position":[0.0,0.0]},"artboard_message_handler":{"artboards_graphene_document":{"root":{"visible":true,"name":null,"data":{"Folder":{"next_assignment_id":0,"layer_ids":[],"layers":[]}},"transform":{"matrix2":[1.0,0.0,0.0,1.0],"translation":[1060.3046875,373.5]},"pivot":[0.5,0.5],"blend_mode":"Normal","opacity":1.0}},"artboard_ids":[]},"properties_panel_message_handler":{"active_selection":[[10689566813179949074,17222868332373661779],"Artwork"]}}
|
||||
|
|
|
@ -71,7 +71,7 @@ pub const DEFAULT_FONT_FAMILY: &str = "Merriweather";
|
|||
pub const DEFAULT_FONT_STYLE: &str = "Normal (400)";
|
||||
|
||||
// Document
|
||||
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.12"; // Remember to save a simple document and replace the test file `graphite-test-document.graphite`
|
||||
pub const GRAPHITE_DOCUMENT_VERSION: &str = "0.0.13"; // Remember to save a simple document and replace the test file `graphite-test-document.graphite`
|
||||
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
|
||||
pub const FILE_SAVE_SUFFIX: &str = ".graphite";
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
|||
selected.revert_operation();
|
||||
typing.clear();
|
||||
} else {
|
||||
*selected.pivot = selected.calculate_pivot(font_cache);
|
||||
*selected.pivot = selected.mean_average_of_pivots(font_cache);
|
||||
}
|
||||
|
||||
*mouse_position = ipp.mouse.position;
|
||||
|
@ -137,7 +137,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
|
|||
self.transform_operation.apply_transform_operation(&mut selected, self.snap);
|
||||
}
|
||||
TransformOperation::Rotating(rotation) => {
|
||||
let selected_pivot = selected.calculate_pivot(font_cache);
|
||||
let selected_pivot = selected.mean_average_of_pivots(font_cache);
|
||||
let angle = {
|
||||
let start_offset = self.mouse_position - selected_pivot;
|
||||
let end_offset = ipp.mouse.position - selected_pivot;
|
||||
|
|
|
@ -216,27 +216,26 @@ impl<'a> Selected<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn calculate_pivot(&mut self, font_cache: &FontCache) -> DVec2 {
|
||||
let xy_summation = self
|
||||
.selected
|
||||
.iter()
|
||||
.map(|path| {
|
||||
let multiplied_transform = self.document.multiply_transforms(path).unwrap();
|
||||
|
||||
let bounds = self
|
||||
.document
|
||||
.layer(path)
|
||||
.unwrap()
|
||||
.aabb_for_transform(multiplied_transform, font_cache)
|
||||
.unwrap_or([multiplied_transform.translation; 2]);
|
||||
|
||||
(bounds[0] + bounds[1]) / 2.
|
||||
})
|
||||
.fold(DVec2::ZERO, |summation, next| summation + next);
|
||||
pub fn mean_average_of_pivots(&mut self, font_cache: &FontCache) -> DVec2 {
|
||||
let xy_summation = self.selected.iter().filter_map(|path| self.document.pivot(path, font_cache)).reduce(|a, b| a + b).unwrap_or_default();
|
||||
|
||||
xy_summation / self.selected.len() as f64
|
||||
}
|
||||
|
||||
pub fn center_of_aabb(&mut self, font_cache: &FontCache) -> DVec2 {
|
||||
let [min, max] = self
|
||||
.selected
|
||||
.iter()
|
||||
.filter_map(|path| {
|
||||
let multiplied_transform = self.document.multiply_transforms(path).unwrap();
|
||||
|
||||
self.document.layer(path).unwrap().aabb_for_transform(multiplied_transform, font_cache)
|
||||
})
|
||||
.reduce(|a, b| [a[0].min(b[0]), a[1].max(b[1])])
|
||||
.unwrap_or_default();
|
||||
(min + max) / 2.
|
||||
}
|
||||
|
||||
pub fn update_transforms(&mut self, delta: DAffine2) {
|
||||
if !self.selected.is_empty() {
|
||||
let pivot = DAffine2::from_translation(*self.pivot);
|
||||
|
|
|
@ -39,9 +39,10 @@ impl SelectedEdges {
|
|||
|
||||
/// Calculate the pivot for the operation (the opposite point to the edge dragged)
|
||||
pub fn calculate_pivot(&self) -> DVec2 {
|
||||
let min = self.bounds[0];
|
||||
let max = self.bounds[1];
|
||||
self.pivot_from_bounds(self.bounds[0], self.bounds[1])
|
||||
}
|
||||
|
||||
fn pivot_from_bounds(&self, min: DVec2, max: DVec2) -> DVec2 {
|
||||
let x = if self.left {
|
||||
max.x
|
||||
} else if self.right {
|
||||
|
@ -62,7 +63,7 @@ impl SelectedEdges {
|
|||
}
|
||||
|
||||
/// Computes the new bounds with the given mouse move and modifier keys
|
||||
pub fn new_size(&self, mouse: DVec2, transform: DAffine2, center: bool, constrain: bool) -> (DVec2, DVec2) {
|
||||
pub fn new_size(&self, mouse: DVec2, transform: DAffine2, center: bool, center_around: DVec2, constrain: bool) -> (DVec2, DVec2) {
|
||||
let mouse = transform.inverse().transform_point2(mouse);
|
||||
|
||||
let mut min = self.bounds[0];
|
||||
|
@ -73,73 +74,49 @@ impl SelectedEdges {
|
|||
max.y = mouse.y;
|
||||
}
|
||||
if self.left {
|
||||
let delta = min.x - mouse.x;
|
||||
min.x = mouse.x;
|
||||
max.x += delta;
|
||||
} else if self.right {
|
||||
max.x = mouse.x;
|
||||
}
|
||||
|
||||
let mut size = max - min;
|
||||
let mut pivot = self.pivot_from_bounds(min, max);
|
||||
if center {
|
||||
if self.top {
|
||||
max.y = center_around.y * 2. - min.y;
|
||||
pivot.y = center_around.y;
|
||||
} else if self.bottom {
|
||||
min.y = center_around.y * 2. - max.y;
|
||||
pivot.y = center_around.y;
|
||||
}
|
||||
if self.left {
|
||||
max.x = center_around.x * 2. - min.x;
|
||||
pivot.x = center_around.x;
|
||||
} else if self.right {
|
||||
min.x = center_around.x * 2. - max.x;
|
||||
pivot.x = center_around.x;
|
||||
}
|
||||
}
|
||||
|
||||
if constrain {
|
||||
size = match ((self.top || self.bottom), (self.left || self.right)) {
|
||||
let size = max - min;
|
||||
let min_pivot = (pivot - min) / size;
|
||||
let new_size = match ((self.top || self.bottom), (self.left || self.right)) {
|
||||
(true, true) => DVec2::new(size.x, size.x / self.aspect_ratio).abs().max(DVec2::new(size.y * self.aspect_ratio, size.y).abs()) * size.signum(),
|
||||
(true, false) => DVec2::new(size.y * self.aspect_ratio, size.y),
|
||||
(false, true) => DVec2::new(size.x, size.x / self.aspect_ratio),
|
||||
_ => size,
|
||||
};
|
||||
}
|
||||
if center {
|
||||
if self.left || self.right {
|
||||
size.x *= 2.;
|
||||
}
|
||||
|
||||
if self.bottom || self.top {
|
||||
size.y *= 2.;
|
||||
}
|
||||
let delta_size = new_size - size;
|
||||
min = min - delta_size * min_pivot;
|
||||
max = min + new_size;
|
||||
}
|
||||
|
||||
(min, size)
|
||||
}
|
||||
|
||||
/// Offsets the transformation pivot in order to scale from the center
|
||||
fn offset_pivot(&self, center: bool, size: DVec2) -> DVec2 {
|
||||
let mut offset = DVec2::ZERO;
|
||||
|
||||
if !center {
|
||||
return offset;
|
||||
}
|
||||
|
||||
if self.right {
|
||||
offset.x -= size.x / 2.;
|
||||
}
|
||||
if self.left {
|
||||
offset.x += size.x / 2.;
|
||||
}
|
||||
if self.bottom {
|
||||
offset.y -= size.y / 2.;
|
||||
}
|
||||
if self.top {
|
||||
offset.y += size.y / 2.;
|
||||
}
|
||||
offset
|
||||
}
|
||||
|
||||
/// Moves the position to account for centering (only necessary with absolute transforms - e.g. with artboards)
|
||||
pub fn center_position(&self, mut position: DVec2, size: DVec2) -> DVec2 {
|
||||
if self.right {
|
||||
position.x -= size.x / 2.;
|
||||
}
|
||||
if self.bottom {
|
||||
position.y -= size.y / 2.;
|
||||
}
|
||||
|
||||
position
|
||||
(min, max - min)
|
||||
}
|
||||
|
||||
/// Calculates the required scaling to resize the bounding box
|
||||
pub fn bounds_to_scale_transform(&self, center: bool, size: DVec2) -> DAffine2 {
|
||||
DAffine2::from_translation(self.offset_pivot(center, size)) * DAffine2::from_scale(size / (self.bounds[1] - self.bounds[0]))
|
||||
pub fn bounds_to_scale_transform(&self, size: DVec2) -> DAffine2 {
|
||||
DAffine2::from_scale(size / (self.bounds[1] - self.bounds[0]))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -213,7 +190,8 @@ pub struct BoundingBoxOverlays {
|
|||
pub transform: DAffine2,
|
||||
pub selected_edges: Option<SelectedEdges>,
|
||||
pub original_transforms: OriginalTransforms,
|
||||
pub pivot: DVec2,
|
||||
pub opposite_pivot: DVec2,
|
||||
pub center_of_transformation: DVec2,
|
||||
}
|
||||
|
||||
impl BoundingBoxOverlays {
|
||||
|
|
|
@ -176,7 +176,8 @@ impl Fsm for ArtboardToolFsmState {
|
|||
|
||||
bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| {
|
||||
let edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds);
|
||||
bounding_box.pivot = edges.calculate_pivot();
|
||||
bounding_box.opposite_pivot = edges.calculate_pivot();
|
||||
|
||||
edges
|
||||
});
|
||||
|
||||
|
@ -189,11 +190,17 @@ impl Fsm for ArtboardToolFsmState {
|
|||
let snap_x = selected_edges.2 || selected_edges.3;
|
||||
let snap_y = selected_edges.0 || selected_edges.1;
|
||||
|
||||
tool_data
|
||||
.snap_manager
|
||||
.start_snap(document, document.bounding_boxes(None, Some(tool_data.selected_board.unwrap()), font_cache), snap_x, snap_y);
|
||||
let board = tool_data.selected_board.unwrap();
|
||||
tool_data.snap_manager.start_snap(document, document.bounding_boxes(None, Some(board), font_cache), snap_x, snap_y);
|
||||
tool_data.snap_manager.add_all_document_handles(document, &[], &[], &[]);
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
let pivot = document.artboard_message_handler.artboards_graphene_document.pivot(&[board], font_cache).unwrap_or_default();
|
||||
let root = document.graphene_document.root.transform;
|
||||
let pivot = root.inverse().transform_point2(pivot);
|
||||
bounds.center_of_transformation = pivot;
|
||||
}
|
||||
|
||||
ArtboardToolFsmState::ResizingBounds
|
||||
} else {
|
||||
let tolerance = DVec2::splat(SELECTION_TOLERANCE);
|
||||
|
@ -249,11 +256,7 @@ impl Fsm for ArtboardToolFsmState {
|
|||
let mouse_position = input.mouse.position;
|
||||
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
||||
|
||||
let (mut position, size) = movement.new_size(snapped_mouse_position, bounds.transform, from_center, constrain_square);
|
||||
if from_center {
|
||||
position = movement.center_position(position, size);
|
||||
}
|
||||
|
||||
let (position, size) = movement.new_size(snapped_mouse_position, bounds.transform, from_center, bounds.center_of_transformation, constrain_square);
|
||||
responses.push_back(
|
||||
ArtboardMessage::ResizeArtboard {
|
||||
artboard: tool_data.selected_board.unwrap(),
|
||||
|
|
|
@ -418,7 +418,7 @@ impl Fsm for SelectToolFsmState {
|
|||
|
||||
bounding_box.selected_edges = edges.map(|(top, bottom, left, right)| {
|
||||
let edges = SelectedEdges::new(top, bottom, left, right, bounding_box.bounds);
|
||||
bounding_box.pivot = edges.calculate_pivot();
|
||||
bounding_box.opposite_pivot = edges.calculate_pivot();
|
||||
edges
|
||||
});
|
||||
|
||||
|
@ -452,13 +452,21 @@ impl Fsm for SelectToolFsmState {
|
|||
|
||||
tool_data.layers_dragging = selected;
|
||||
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
let document = &document.graphene_document;
|
||||
|
||||
let selected = &tool_data.layers_dragging.iter().collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.center_of_transformation, selected, responses, document);
|
||||
bounds.center_of_transformation = selected.mean_average_of_pivots(font_cache);
|
||||
}
|
||||
|
||||
ResizingBounds
|
||||
} else if rotating_bounds {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
let selected = selected.iter().collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.pivot, &selected, responses, &document.graphene_document);
|
||||
let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.center_of_transformation, &selected, responses, &document.graphene_document);
|
||||
|
||||
*selected.pivot = selected.calculate_pivot(font_cache);
|
||||
bounds.center_of_transformation = selected.mean_average_of_pivots(font_cache);
|
||||
}
|
||||
|
||||
tool_data.layers_dragging = selected;
|
||||
|
@ -543,11 +551,12 @@ impl Fsm for SelectToolFsmState {
|
|||
|
||||
let snapped_mouse_position = tool_data.snap_manager.snap_position(responses, document, mouse_position);
|
||||
|
||||
let (_, size) = movement.new_size(snapped_mouse_position, bounds.transform, center, axis_align);
|
||||
let delta = movement.bounds_to_scale_transform(center, size);
|
||||
let (_, size) = movement.new_size(snapped_mouse_position, bounds.transform, center, bounds.center_of_transformation, axis_align);
|
||||
let delta = movement.bounds_to_scale_transform(size);
|
||||
|
||||
let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.pivot, &selected, responses, &document.graphene_document);
|
||||
let selected = &tool_data.layers_dragging.iter().collect::<Vec<_>>();
|
||||
let pivot = if center { &mut bounds.center_of_transformation } else { &mut bounds.opposite_pivot };
|
||||
let mut selected = Selected::new(&mut bounds.original_transforms, pivot, selected, responses, &document.graphene_document);
|
||||
|
||||
selected.update_transforms(delta);
|
||||
}
|
||||
|
@ -557,8 +566,8 @@ impl Fsm for SelectToolFsmState {
|
|||
(RotatingBounds, PointerMove { snap_angle, .. }) => {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_overlays {
|
||||
let angle = {
|
||||
let start_offset = tool_data.drag_start - bounds.pivot;
|
||||
let end_offset = input.mouse.position - bounds.pivot;
|
||||
let start_offset = tool_data.drag_start - bounds.center_of_transformation;
|
||||
let end_offset = input.mouse.position - bounds.center_of_transformation;
|
||||
|
||||
start_offset.angle_between(end_offset)
|
||||
};
|
||||
|
@ -573,7 +582,7 @@ impl Fsm for SelectToolFsmState {
|
|||
let delta = DAffine2::from_angle(snapped_angle);
|
||||
|
||||
let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.pivot, &selected, responses, &document.graphene_document);
|
||||
let mut selected = Selected::new(&mut bounds.original_transforms, &mut bounds.center_of_transformation, &selected, responses, &document.graphene_document);
|
||||
|
||||
selected.update_transforms(delta);
|
||||
}
|
||||
|
@ -672,7 +681,7 @@ impl Fsm for SelectToolFsmState {
|
|||
let selected = tool_data.layers_dragging.iter().collect::<Vec<_>>();
|
||||
let mut selected = Selected::new(
|
||||
&mut bounding_box_overlays.original_transforms,
|
||||
&mut bounding_box_overlays.pivot,
|
||||
&mut bounding_box_overlays.opposite_pivot,
|
||||
&selected,
|
||||
responses,
|
||||
&document.graphene_document,
|
||||
|
@ -865,7 +874,7 @@ impl SelectToolData {
|
|||
|
||||
// Duplicate each previously selected layer and select the new ones.
|
||||
for layer_path in Document::shallowest_unique_layers(self.layers_dragging.iter_mut()) {
|
||||
// Moves the origional back to its starting position.
|
||||
// Moves the original back to its starting position.
|
||||
responses.push_front(
|
||||
Operation::TransformLayerInViewport {
|
||||
path: layer_path.clone(),
|
||||
|
@ -875,7 +884,7 @@ impl SelectToolData {
|
|||
);
|
||||
|
||||
// Copy the layers.
|
||||
// Not using the Copy message allows us to retrieve the ids of the new layers to initalise the drag.
|
||||
// Not using the Copy message allows us to retrieve the ids of the new layers to initialize the drag.
|
||||
let layer = match document.graphene_document.layer(layer_path) {
|
||||
Ok(layer) => layer.clone(),
|
||||
Err(e) => {
|
||||
|
@ -907,7 +916,7 @@ impl SelectToolData {
|
|||
|
||||
/// Removes the duplicated layers. Called when alt is released and the layers have been duplicated.
|
||||
fn stop_duplicates(&mut self, responses: &mut VecDeque<Message>) {
|
||||
let origionals = match self.not_duplicated_layers.take() {
|
||||
let originals = match self.not_duplicated_layers.take() {
|
||||
Some(x) => x,
|
||||
None => return,
|
||||
};
|
||||
|
@ -919,8 +928,8 @@ impl SelectToolData {
|
|||
responses.push_back(Operation::DeleteLayer { path: layer_path.clone() }.into());
|
||||
}
|
||||
|
||||
// Move the origional to under the mouse
|
||||
for layer_path in Document::shallowest_unique_layers(origionals.iter()) {
|
||||
// Move the original to under the mouse
|
||||
for layer_path in Document::shallowest_unique_layers(originals.iter()) {
|
||||
responses.push_front(
|
||||
Operation::TransformLayerInViewport {
|
||||
path: layer_path.clone(),
|
||||
|
@ -930,14 +939,14 @@ impl SelectToolData {
|
|||
);
|
||||
}
|
||||
|
||||
// Select the origionals
|
||||
// Select the originals
|
||||
responses.push_back(
|
||||
DocumentMessage::SetSelectedLayers {
|
||||
replacement_selected_layers: origionals.clone(),
|
||||
replacement_selected_layers: originals.clone(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
self.layers_dragging = origionals;
|
||||
self.layers_dragging = originals;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -367,6 +367,12 @@ impl Document {
|
|||
Ok(layer.data.bounding_box(layer.transform, font_cache).map(|bounds| (bounds, transform)))
|
||||
}
|
||||
|
||||
/// Compute the center of transformation multiplied with `Document::multiply_transforms`.
|
||||
pub fn pivot(&self, path: &[LayerId], font_cache: &FontCache) -> Option<DVec2> {
|
||||
let layer = self.layer(path).ok()?;
|
||||
Some(self.multiply_transforms(path).unwrap_or_default().transform_point2(layer.layerspace_pivot(font_cache)))
|
||||
}
|
||||
|
||||
pub fn visible_layers_bounding_box(&self, font_cache: &FontCache) -> Option<[DVec2; 2]> {
|
||||
let mut paths = vec![];
|
||||
self.visible_layers(&mut vec![], &mut paths).ok()?;
|
||||
|
|
|
@ -192,6 +192,9 @@ pub struct Layer {
|
|||
/// A transformation applied to the layer (translation, rotation, scaling, and shear).
|
||||
#[serde(with = "DAffine2Ref")]
|
||||
pub transform: glam::DAffine2,
|
||||
/// The center of transformations like rotation or scaling with the shift key.
|
||||
/// This is in local space (so the layer's transform should be applied).
|
||||
pub pivot: DVec2,
|
||||
/// The cached SVG thumbnail view of the layer.
|
||||
#[serde(skip)]
|
||||
pub thumbnail_cache: String,
|
||||
|
@ -217,6 +220,7 @@ impl Layer {
|
|||
name: None,
|
||||
data,
|
||||
transform: glam::DAffine2::from_cols_array(&transform),
|
||||
pivot: DVec2::splat(0.5),
|
||||
cache: String::new(),
|
||||
thumbnail_cache: String::new(),
|
||||
svg_defs_cache: String::new(),
|
||||
|
@ -363,6 +367,11 @@ impl Layer {
|
|||
self.transform * scale
|
||||
}
|
||||
|
||||
pub fn layerspace_pivot(&self, font_cache: &FontCache) -> DVec2 {
|
||||
let [min, max] = self.aabb_for_transform(DAffine2::IDENTITY, font_cache).unwrap_or([DVec2::ZERO, DVec2::ONE]);
|
||||
self.pivot * (max - min) + min
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the Folder wrapped by the layer.
|
||||
/// This operation will fail if the [Layer type](Layer::data) is not `LayerDataType::Folder`.
|
||||
pub fn as_folder_mut(&mut self) -> Result<&mut FolderLayer, DocumentError> {
|
||||
|
@ -462,6 +471,7 @@ impl Clone for Layer {
|
|||
name: self.name.clone(),
|
||||
data: self.data.clone(),
|
||||
transform: self.transform,
|
||||
pivot: self.pivot,
|
||||
cache: String::new(),
|
||||
thumbnail_cache: String::new(),
|
||||
svg_defs_cache: String::new(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue