Transformation cage (#502)

* Render corners and edges of selection box

* Refactor

* Add drag detection

* Implement the transform handles

* Implement rotation by dragging <40px from bounds

* Refine clustered handle behaviour

* Add cursors

* Add snap angle

* Fix MMB drag whilst in select tool

* Convert calculate_pivot into a seperate function

* rename start_vec to start_offset

* Fix typo

* Remove Undo transaction on <10px mouse move

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2022-02-04 16:46:44 +00:00
committed by Keavon Chambers
parent 2a313471d8
commit 599a9d076b
12 changed files with 401 additions and 62 deletions

View File

@@ -191,9 +191,9 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
let half_viewport = ipp.viewport_bounds.size() / 2.;
let rotation = {
let start_vec = self.mouse_position - half_viewport;
let end_vec = ipp.mouse.position - half_viewport;
start_vec.angle_between(end_vec)
let start_offset = self.mouse_position - half_viewport;
let end_offset = ipp.mouse.position - half_viewport;
start_offset.angle_between(end_offset)
};
responses.push_back(SetCanvasRotation { angle_radians: self.tilt + rotation }.into());
@@ -329,7 +329,6 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
fn actions(&self) -> ActionList {
let mut common = actions!(MovementMessageDiscriminant;
MouseMove,
TranslateCanvasBegin,
RotateCanvasBegin,
ZoomCanvasBegin,
@@ -345,6 +344,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
if self.panning || self.tilting || self.zooming {
let transforming = actions!(MovementMessageDiscriminant;
MouseMove,
TransformCanvasEnd,
);
common.extend(transforming);

View File

@@ -36,7 +36,9 @@ impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMeta
use TransformLayerMessage::*;
let (layer_metadata, document, ipp) = data;
let mut selected = Selected::new(&mut self.original_transforms, &mut self.pivot, layer_metadata, responses, document);
let selected_layers = layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then(|| layer_path)).collect::<Vec<_>>();
let mut selected = Selected::new(&mut self.original_transforms, &mut self.pivot, &selected_layers, responses, document);
let mut begin_operation = |operation: TransformOperation, typing: &mut Typing, mouse_position: &mut DVec2, start_mouse: &mut DVec2| {
if operation != TransformOperation::None {
@@ -128,10 +130,10 @@ impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMeta
TransformOperation::Rotating(rotation) => {
let selected_pivot = selected.calculate_pivot();
let angle = {
let start_vec = self.mouse_position - selected_pivot;
let end_vec = ipp.mouse.position - selected_pivot;
let start_offset = self.mouse_position - selected_pivot;
let end_offset = ipp.mouse.position - selected_pivot;
start_vec.angle_between(end_vec)
start_offset.angle_between(end_offset)
};
let change = if self.slow { angle / SLOWING_DIVISOR } else { angle };

View File

@@ -1,4 +1,3 @@
use super::layer_panel::LayerMetadata;
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL};
use crate::message_prelude::*;
@@ -188,25 +187,18 @@ impl TransformOperation {
}
pub struct Selected<'a> {
pub selected: Vec<Vec<LayerId>>,
pub selected: &'a [&'a Vec<LayerId>],
pub responses: &'a mut VecDeque<Message>,
pub document: &'a mut Document,
pub document: &'a Document,
pub original_transforms: &'a mut OriginalTransforms,
pub pivot: &'a mut DVec2,
}
impl<'a> Selected<'a> {
pub fn new(
original_transforms: &'a mut OriginalTransforms,
pivot: &'a mut DVec2,
layer_metadata: &'a mut HashMap<Vec<LayerId>, LayerMetadata>,
responses: &'a mut VecDeque<Message>,
document: &'a mut Document,
) -> Self {
let selected = layer_metadata.iter().filter_map(|(layer_path, data)| data.selected.then(|| layer_path.to_owned())).collect();
for path in &selected {
if !original_transforms.contains_key::<Vec<LayerId>>(path) {
original_transforms.insert(path.clone(), document.layer(path).unwrap().transform);
pub fn new(original_transforms: &'a mut OriginalTransforms, pivot: &'a mut DVec2, selected: &'a [&'a Vec<LayerId>], responses: &'a mut VecDeque<Message>, document: &'a Document) -> Self {
for path in selected {
if !original_transforms.contains_key(*path) {
original_transforms.insert(path.to_vec(), document.layer(path).unwrap().transform);
}
}
Self {
@@ -247,7 +239,7 @@ 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_path in Document::shallowest_unique_layers(self.selected.iter()) {
let parent_folder_path = &layer_path[..layer_path.len() - 1];
let original_layer_transforms = *self.original_transforms.get(layer_path).unwrap();
let original_layer_transforms = *self.original_transforms.get(*layer_path).unwrap();
let to = self.document.generate_transform_across_scope(parent_folder_path, None).unwrap();
let new = to.inverse() * transformation * to * original_layer_transforms;
@@ -266,11 +258,11 @@ impl<'a> Selected<'a> {
}
pub fn revert_operation(&mut self) {
for path in &self.selected {
for path in self.selected {
self.responses.push_back(
DocumentOperation::SetLayerTransform {
path: path.to_vec(),
transform: (*self.original_transforms.get(path).unwrap()).to_cols_array(),
transform: (*self.original_transforms.get(*path).unwrap()).to_cols_array(),
}
.into(),
);