mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-16 23:08:05 +08:00
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:
committed by
Keavon Chambers
parent
29485001e9
commit
45edeb2a2b
@@ -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 {
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user