Implement the Crop Tool for artboard resizing (#519)

* Extract transformation cage to a seperate file

* Hook up tool

* Implement resize

* Draw artboards

* centre and constrain

* Bounding box is rotated

* Fix transform handle positions for artboard

* Drag layers

* Snapping

* Fix imports

* Cleanup

* Remove allocation from bounding_boxes

* Round artboard size and position

* Hints

* Fix rotated transform cage

* Code review changes

* Hints changes

Co-authored-by: Dennis <dennis@kobert.dev>
Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2022-02-09 23:22:23 +00:00
committed by Keavon Chambers
parent 29485001e9
commit 45edeb2a2b
29 changed files with 817 additions and 340 deletions

View File

@@ -14,12 +14,16 @@ pub enum ArtboardMessage {
// Messages
AddArtboard {
top: f64,
left: f64,
height: f64,
width: f64,
id: Option<LayerId>,
position: (f64, f64),
size: (f64, f64),
},
RenderArtboards,
ResizeArtboard {
artboard: Vec<LayerId>,
position: (f64, f64),
size: (f64, f64),
},
}
impl From<DocumentOperation> for ArtboardMessage {

View File

@@ -5,7 +5,7 @@ use graphene::document::Document as GrapheneDocument;
use graphene::layers::style::{self, Fill, ViewMode};
use graphene::Operation as DocumentOperation;
use glam::{DAffine2, DVec2};
use glam::DAffine2;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
@@ -36,8 +36,8 @@ impl MessageHandler<ArtboardMessage, ()> for ArtboardMessageHandler {
},
// Messages
AddArtboard { top, left, height, width } => {
let artboard_id = generate_uuid();
AddArtboard { id, position, size } => {
let artboard_id = id.unwrap_or_else(generate_uuid);
self.artboard_ids.push(artboard_id);
responses.push_back(
@@ -45,7 +45,7 @@ impl MessageHandler<ArtboardMessage, ()> for ArtboardMessageHandler {
DocumentOperation::AddRect {
path: vec![artboard_id],
insert_index: -1,
transform: DAffine2::from_scale_angle_translation(DVec2::new(height, width), 0., DVec2::new(top, left)).to_cols_array(),
transform: DAffine2::from_scale_angle_translation(size.into(), 0., position.into()).to_cols_array(),
style: style::PathStyle::new(None, Some(Fill::new(Color::WHITE))),
}
.into(),
@@ -73,6 +73,17 @@ impl MessageHandler<ArtboardMessage, ()> for ArtboardMessageHandler {
);
}
}
ResizeArtboard { artboard, position, size } => {
responses.push_back(
ArtboardMessage::DispatchOperation(Box::new(DocumentOperation::SetLayerTransform {
path: artboard,
transform: DAffine2::from_scale_angle_translation(size.into(), 0., position.into()).to_cols_array(),
}))
.into(),
);
responses.push_back(DocumentMessage::RenderDocument.into());
}
}
}

View File

@@ -42,7 +42,7 @@ pub struct DocumentMessageHandler {
movement_handler: MovementMessageHandler,
#[serde(skip)]
overlays_message_handler: OverlaysMessageHandler,
artboard_message_handler: ArtboardMessageHandler,
pub artboard_message_handler: ArtboardMessageHandler,
#[serde(skip)]
transform_layer_handler: TransformLayerMessageHandler,
pub overlays_visible: bool,
@@ -138,6 +138,10 @@ impl DocumentMessageHandler {
self.graphene_document.combined_viewport_bounding_box(paths)
}
pub fn artboard_bounding_box_and_transform(&self, path: &[LayerId]) -> Option<([DVec2; 2], DAffine2)> {
self.artboard_message_handler.artboards_graphene_document.bounding_box_and_transform(path).unwrap_or(None)
}
/// Create a new vector shape representation with the underlying kurbo data, VectorManipulatorShape
pub fn selected_visible_layers_vector_shapes(&self, responses: &mut VecDeque<Message>) -> Vec<VectorShape> {
let shapes = self.selected_layers().filter_map(|path_to_shape| {
@@ -203,6 +207,20 @@ impl DocumentMessageHandler {
})
}
/// Returns the bounding boxes for all visible layers and artboards, optionally excluding any paths.
pub fn bounding_boxes<'a>(&'a self, ignore_document: Option<&'a Vec<Vec<LayerId>>>, ignore_artboard: Option<LayerId>) -> impl Iterator<Item = [DVec2; 2]> + 'a {
self.visible_layers()
.filter(move |path| ignore_document.map_or(true, |ignore_document| !ignore_document.iter().any(|ig| ig.as_slice() == *path)))
.filter_map(|path| self.graphene_document.viewport_bounding_box(path).ok()?)
.chain(
self.artboard_message_handler
.artboard_ids
.iter()
.filter(move |&&id| Some(id) != ignore_artboard)
.filter_map(|&path| self.artboard_message_handler.artboards_graphene_document.viewport_bounding_box(&[path]).ok()?),
)
}
fn serialize_structure(&self, folder: &Folder, structure: &mut Vec<u64>, data: &mut Vec<LayerId>, path: &mut Vec<LayerId>) {
let mut space = 0;
for (id, layer) in folder.layer_ids.iter().zip(folder.layers()).rev() {

View File

@@ -19,7 +19,7 @@ pub enum MovementMessage {
IncreaseCanvasZoom {
center_on_mouse: bool,
},
MouseMove {
PointerMove {
snap_angle: Key,
wait_for_snap_angle_release: bool,
snap_zoom: Key,

View File

@@ -166,7 +166,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
}
responses.push_back(SetCanvasZoom { zoom_factor: new_scale }.into());
}
MouseMove {
PointerMove {
snap_angle,
wait_for_snap_angle_release,
snap_zoom,
@@ -344,7 +344,7 @@ impl MessageHandler<MovementMessage, (&Document, &InputPreprocessorMessageHandle
if self.panning || self.tilting || self.zooming {
let transforming = actions!(MovementMessageDiscriminant;
MouseMove,
PointerMove,
TransformCanvasEnd,
);
common.extend(transforming);

View File

@@ -14,7 +14,7 @@ pub enum TransformLayerMessage {
CancelTransformOperation,
ConstrainX,
ConstrainY,
MouseMove { slow_key: Key, snap_key: Key },
PointerMove { slow_key: Key, snap_key: Key },
TypeBackspace,
TypeDecimalPoint,
TypeDigit { digit: u8 },

View File

@@ -108,7 +108,7 @@ impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMeta
}
ConstrainX => self.transform_operation.constrain_axis(Axis::X, &mut selected, self.snap),
ConstrainY => self.transform_operation.constrain_axis(Axis::Y, &mut selected, self.snap),
MouseMove { slow_key, snap_key } => {
PointerMove { slow_key, snap_key } => {
self.slow = ipp.keyboard.get(slow_key as usize);
let new_snap = ipp.keyboard.get(snap_key as usize);
@@ -173,7 +173,7 @@ impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerMeta
if self.transform_operation != TransformOperation::None {
let active = actions!(TransformLayerMessageDiscriminant;
MouseMove,
PointerMove,
CancelTransformOperation,
ApplyTransformOperation,
TypeDigit,