(G)rab, (R)otate, and (S)cale layers with hotkeys (#356)
* Add handler and input mapping. * Selecting transform type * Add translation and axis constraints * Remove unnecessary Key:: * Add rotation * Centre pivot point for rotation * Add scaling * Select Tool bounding box now updates when transforming * Add typing * Shift to slow mouse movements * Add snapping * Refactor modifier keys * Refactor to apply only 1 operation per frame * Refactor to fix scale 0 issue * Remove logging * Avoid multiple decimol points in queue * Add typing negative * Add typing negative values * Fix bounding box on apply/abort; fix some variable names * Allow transform to daffine2 identity * Revert previous operation when changing operation * Remove repopulate transforms method * Code readability tweaks Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
4f871919f5
commit
134381f1bc
|
|
@ -15,6 +15,11 @@ pub const VIEWPORT_SCROLL_RATE: f64 = 0.6;
|
|||
|
||||
pub const VIEWPORT_ROTATE_SNAP_INTERVAL: f64 = 15.;
|
||||
|
||||
// TRANSFORMING LAYER
|
||||
pub const ROTATE_SNAP_ANGLE: f64 = 15.;
|
||||
pub const SCALE_SNAP_INTERVAL: f64 = 0.1;
|
||||
pub const SLOWING_DIVISOR: f64 = 10.;
|
||||
|
||||
// SELECT TOOL
|
||||
pub const SELECTION_TOLERANCE: f64 = 1.;
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ use log::warn;
|
|||
use std::collections::VecDeque;
|
||||
|
||||
use super::movement_handler::{MovementMessage, MovementMessageHandler};
|
||||
use super::transform_layer_handler::{TransformLayerMessage, TransformLayerMessageHandler};
|
||||
|
||||
type DocumentSave = (InternalDocument, HashMap<Vec<LayerId>, LayerData>);
|
||||
|
||||
|
|
@ -63,6 +64,7 @@ pub struct DocumentMessageHandler {
|
|||
pub name: String,
|
||||
pub layer_data: HashMap<Vec<LayerId>, LayerData>,
|
||||
movement_handler: MovementMessageHandler,
|
||||
transform_layer_handler: TransformLayerMessageHandler,
|
||||
}
|
||||
|
||||
impl Default for DocumentMessageHandler {
|
||||
|
|
@ -74,6 +76,7 @@ impl Default for DocumentMessageHandler {
|
|||
name: String::from("Untitled Document"),
|
||||
layer_data: vec![(vec![], LayerData::new(true))].into_iter().collect(),
|
||||
movement_handler: MovementMessageHandler::default(),
|
||||
transform_layer_handler: TransformLayerMessageHandler::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -83,6 +86,8 @@ impl Default for DocumentMessageHandler {
|
|||
pub enum DocumentMessage {
|
||||
#[child]
|
||||
Movement(MovementMessage),
|
||||
#[child]
|
||||
TransformLayers(TransformLayerMessage),
|
||||
DispatchOperation(Box<DocumentOperation>),
|
||||
SetSelectedLayers(Vec<Vec<LayerId>>),
|
||||
AddSelectedLayers(Vec<Vec<LayerId>>),
|
||||
|
|
@ -260,6 +265,7 @@ impl DocumentMessageHandler {
|
|||
name,
|
||||
layer_data: vec![(vec![], LayerData::new(true))].into_iter().collect(),
|
||||
movement_handler: MovementMessageHandler::default(),
|
||||
transform_layer_handler: TransformLayerMessageHandler::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -361,6 +367,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||
use DocumentMessage::*;
|
||||
match message {
|
||||
Movement(message) => self.movement_handler.process_action(message, (layer_data(&mut self.layer_data, &[]), &self.document, ipp), responses),
|
||||
TransformLayers(message) => self.transform_layer_handler.process_action(message, (&mut self.layer_data, &mut self.document, ipp), responses),
|
||||
DeleteLayer(path) => responses.push_back(DocumentOperation::DeleteLayer { path }.into()),
|
||||
StartTransaction => self.backup(),
|
||||
RollbackTransaction => {
|
||||
|
|
@ -710,6 +717,7 @@ impl MessageHandler<DocumentMessage, &InputPreprocessor> for DocumentMessageHand
|
|||
common.extend(select);
|
||||
}
|
||||
common.extend(self.movement_handler.actions());
|
||||
common.extend(self.transform_layer_handler.actions());
|
||||
common
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crate::input::InputPreprocessor;
|
||||
use crate::message_prelude::*;
|
||||
use graphene::layers::{Layer, LayerDataType};
|
||||
use graphene::layers::Layer;
|
||||
use graphene::{LayerId, Operation as DocumentOperation};
|
||||
use log::warn;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ mod document_file;
|
|||
mod document_message_handler;
|
||||
pub mod layer_panel;
|
||||
mod movement_handler;
|
||||
mod transform_layer_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use document_file::LayerData;
|
||||
|
|
@ -12,3 +13,5 @@ pub use document_file::{AlignAggregate, AlignAxis, DocumentMessage, DocumentMess
|
|||
pub use document_message_handler::{DocumentsMessage, DocumentsMessageDiscriminant, DocumentsMessageHandler};
|
||||
#[doc(inline)]
|
||||
pub use movement_handler::{MovementMessage, MovementMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use transform_layer_handler::{TransformLayerMessage, TransformLayerMessageDiscriminant};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,551 @@
|
|||
pub use super::layer_panel::*;
|
||||
|
||||
use super::LayerData;
|
||||
|
||||
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL, SLOWING_DIVISOR};
|
||||
use crate::input::keyboard::Key;
|
||||
use crate::input::{mouse::ViewportPosition, InputPreprocessor};
|
||||
use crate::message_prelude::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene::document::Document;
|
||||
use graphene::Operation as DocumentOperation;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
type OriginalTransforms = HashMap<Vec<LayerId>, DAffine2>;
|
||||
|
||||
struct Selected<'a> {
|
||||
pub selected: Vec<Vec<LayerId>>,
|
||||
responses: &'a mut VecDeque<Message>,
|
||||
document: &'a mut Document,
|
||||
original_transforms: &'a mut OriginalTransforms,
|
||||
pivot: &'a mut DVec2,
|
||||
}
|
||||
impl<'a> Selected<'a> {
|
||||
pub fn new(
|
||||
original_transforms: &'a mut OriginalTransforms,
|
||||
pivot: &'a mut DVec2,
|
||||
layer_data: &'a mut HashMap<Vec<LayerId>, LayerData>,
|
||||
responses: &'a mut VecDeque<Message>,
|
||||
document: &'a mut Document,
|
||||
) -> Self {
|
||||
let selected = layer_data.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);
|
||||
}
|
||||
}
|
||||
Self {
|
||||
selected,
|
||||
responses,
|
||||
document,
|
||||
original_transforms,
|
||||
pivot,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_pivot(&mut self) -> 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()
|
||||
.current_bounding_box_with_transform(multiplied_transform)
|
||||
.unwrap_or([multiplied_transform.translation; 2]);
|
||||
|
||||
(bounds[0] + bounds[1]) / 2.
|
||||
})
|
||||
.fold(DVec2::ZERO, |summation, next| summation + next);
|
||||
|
||||
xy_summation / self.selected.len() as f64
|
||||
}
|
||||
|
||||
pub fn update_transforms(&mut self, delta: DAffine2) {
|
||||
if !self.selected.is_empty() {
|
||||
let pivot = DAffine2::from_translation(*self.pivot);
|
||||
let transformation = pivot * delta * pivot.inverse();
|
||||
|
||||
for layer_path in &self.selected {
|
||||
let parent_folder_path = &layer_path[..layer_path.len() - 1];
|
||||
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;
|
||||
|
||||
self.responses.push_back(
|
||||
DocumentOperation::SetLayerTransform {
|
||||
path: layer_path.to_vec(),
|
||||
transform: new.to_cols_array(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
self.responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn revert_operation(&mut self) {
|
||||
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(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
enum Axis {
|
||||
Both,
|
||||
X,
|
||||
Y,
|
||||
}
|
||||
|
||||
impl Default for Axis {
|
||||
fn default() -> Self {
|
||||
Self::Both
|
||||
}
|
||||
}
|
||||
|
||||
impl Axis {
|
||||
pub fn set_or_toggle(&mut self, target: Axis) {
|
||||
// If constrained to an axis and target is requesting the same axis, toggle back to Both
|
||||
if *self == target {
|
||||
*self = Axis::Both;
|
||||
}
|
||||
// If current axis is different from the target axis, switch to the target
|
||||
else {
|
||||
*self = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Copy)]
|
||||
struct Translation {
|
||||
pub dragged_distance: DVec2,
|
||||
pub typed_distance: Option<f64>,
|
||||
pub constraint: Axis,
|
||||
}
|
||||
|
||||
impl Translation {
|
||||
pub fn to_dvec(self) -> DVec2 {
|
||||
if let Some(value) = self.typed_distance {
|
||||
if self.constraint == Axis::Y {
|
||||
return DVec2::new(0., value);
|
||||
} else {
|
||||
return DVec2::new(value, 0.);
|
||||
}
|
||||
}
|
||||
|
||||
match self.constraint {
|
||||
Axis::Both => self.dragged_distance,
|
||||
Axis::X => DVec2::new(self.dragged_distance.x, 0.),
|
||||
Axis::Y => DVec2::new(0., self.dragged_distance.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_amount(self, delta: DVec2) -> Self {
|
||||
Self {
|
||||
dragged_distance: self.dragged_distance + delta,
|
||||
typed_distance: None,
|
||||
constraint: self.constraint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
struct Scale {
|
||||
pub dragged_factor: f64,
|
||||
pub typed_factor: Option<f64>,
|
||||
pub constraint: Axis,
|
||||
}
|
||||
|
||||
impl Default for Scale {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
dragged_factor: 1.,
|
||||
typed_factor: None,
|
||||
constraint: Axis::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Scale {
|
||||
pub fn to_dvec(self, snap: bool) -> DVec2 {
|
||||
let factor = if let Some(value) = self.typed_factor { value } else { self.dragged_factor };
|
||||
let factor = if snap { (factor / SCALE_SNAP_INTERVAL).round() * SCALE_SNAP_INTERVAL } else { factor };
|
||||
|
||||
match self.constraint {
|
||||
Axis::Both => DVec2::splat(factor),
|
||||
Axis::X => DVec2::new(factor, 1.),
|
||||
Axis::Y => DVec2::new(1., factor),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_amount(self, delta: f64) -> Self {
|
||||
Self {
|
||||
dragged_factor: self.dragged_factor + delta,
|
||||
typed_factor: None,
|
||||
constraint: self.constraint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Copy)]
|
||||
struct Rotation {
|
||||
pub dragged_angle: f64,
|
||||
pub typed_angle: Option<f64>,
|
||||
}
|
||||
|
||||
impl Rotation {
|
||||
pub fn to_f64(self, snap: bool) -> f64 {
|
||||
if let Some(value) = self.typed_angle {
|
||||
value.to_radians()
|
||||
} else if snap {
|
||||
let snap_resolution = ROTATE_SNAP_ANGLE.to_radians();
|
||||
(self.dragged_angle / snap_resolution).round() * snap_resolution
|
||||
} else {
|
||||
self.dragged_angle
|
||||
}
|
||||
}
|
||||
|
||||
pub fn increment_amount(self, delta: f64) -> Self {
|
||||
Self {
|
||||
dragged_angle: self.dragged_angle + delta,
|
||||
typed_angle: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
enum Operation {
|
||||
None,
|
||||
Grabbing(Translation),
|
||||
Rotating(Rotation),
|
||||
Scaling(Scale),
|
||||
}
|
||||
|
||||
impl Default for Operation {
|
||||
fn default() -> Self {
|
||||
Operation::None
|
||||
}
|
||||
}
|
||||
|
||||
impl Operation {
|
||||
pub fn apply_operation(&self, selected: &mut Selected, snapping: bool) {
|
||||
if self != &Operation::None {
|
||||
let transformation = match self {
|
||||
Operation::Grabbing(translation) => DAffine2::from_translation(translation.to_dvec()),
|
||||
Operation::Rotating(rotation) => DAffine2::from_angle(rotation.to_f64(snapping)),
|
||||
Operation::Scaling(scale) => DAffine2::from_scale(scale.to_dvec(snapping)),
|
||||
Operation::None => unreachable!(),
|
||||
};
|
||||
|
||||
selected.update_transforms(transformation);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constrain_axis(&mut self, axis: Axis, selected: &mut Selected, snapping: bool) {
|
||||
match self {
|
||||
Operation::None => (),
|
||||
Operation::Grabbing(translation) => translation.constraint.set_or_toggle(axis),
|
||||
Operation::Rotating(_) => (),
|
||||
Operation::Scaling(scale) => scale.constraint.set_or_toggle(axis),
|
||||
};
|
||||
|
||||
self.apply_operation(selected, snapping);
|
||||
}
|
||||
|
||||
pub fn handle_typed(&mut self, typed: Option<f64>, selected: &mut Selected, snapping: bool) {
|
||||
match self {
|
||||
Operation::None => (),
|
||||
Operation::Grabbing(translation) => translation.typed_distance = typed,
|
||||
Operation::Rotating(rotation) => rotation.typed_angle = typed,
|
||||
Operation::Scaling(scale) => scale.typed_factor = typed,
|
||||
};
|
||||
|
||||
self.apply_operation(selected, snapping);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
struct Typing {
|
||||
digits: Vec<u8>,
|
||||
contains_decimal: bool,
|
||||
negative: bool,
|
||||
}
|
||||
|
||||
const DECIMAL_POINT: u8 = 10;
|
||||
|
||||
impl Typing {
|
||||
pub fn type_number(&mut self, number: u8) -> Option<f64> {
|
||||
self.digits.push(number);
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn type_backspace(&mut self) -> Option<f64> {
|
||||
if self.digits.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match self.digits.pop() {
|
||||
Some(DECIMAL_POINT) => self.contains_decimal = false,
|
||||
Some(_) => (),
|
||||
None => self.negative = false,
|
||||
}
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn type_decimal_point(&mut self) -> Option<f64> {
|
||||
if !self.contains_decimal {
|
||||
self.contains_decimal = true;
|
||||
self.digits.push(DECIMAL_POINT);
|
||||
}
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn type_negate(&mut self) -> Option<f64> {
|
||||
self.negative = !self.negative;
|
||||
|
||||
self.evaluate()
|
||||
}
|
||||
|
||||
pub fn evaluate(&self) -> Option<f64> {
|
||||
if self.digits.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut result = 0_f64;
|
||||
let mut running_decimal_place = 0_i32;
|
||||
|
||||
for digit in &self.digits {
|
||||
if *digit == DECIMAL_POINT {
|
||||
if running_decimal_place == 0 {
|
||||
running_decimal_place = 1;
|
||||
}
|
||||
} else if running_decimal_place == 0 {
|
||||
result *= 10.;
|
||||
result += *digit as f64;
|
||||
} else {
|
||||
result += *digit as f64 * 0.1_f64.powi(running_decimal_place);
|
||||
running_decimal_place += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if self.negative {
|
||||
result = -result;
|
||||
}
|
||||
|
||||
Some(result)
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.digits.clear();
|
||||
self.contains_decimal = false;
|
||||
self.negative = false;
|
||||
}
|
||||
}
|
||||
|
||||
#[impl_message(Message, DocumentMessage, TransformLayers)]
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum TransformLayerMessage {
|
||||
BeginGrab,
|
||||
BeginScale,
|
||||
BeginRotate,
|
||||
|
||||
CancelOperation,
|
||||
ApplyOperation,
|
||||
|
||||
TypeNumber(u8),
|
||||
TypeBackspace,
|
||||
TypeDecimalPoint,
|
||||
TypeNegate,
|
||||
|
||||
ConstrainX,
|
||||
ConstrainY,
|
||||
|
||||
MouseMove { slow_key: Key, snap_key: Key },
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct TransformLayerMessageHandler {
|
||||
operation: Operation,
|
||||
|
||||
slow: bool,
|
||||
snap: bool,
|
||||
typing: Typing,
|
||||
|
||||
mouse_position: ViewportPosition,
|
||||
start_mouse: ViewportPosition,
|
||||
|
||||
original_transforms: OriginalTransforms,
|
||||
pivot: DVec2,
|
||||
}
|
||||
|
||||
impl MessageHandler<TransformLayerMessage, (&mut HashMap<Vec<LayerId>, LayerData>, &mut Document, &InputPreprocessor)> for TransformLayerMessageHandler {
|
||||
fn process_action(&mut self, message: TransformLayerMessage, data: (&mut HashMap<Vec<LayerId>, LayerData>, &mut Document, &InputPreprocessor), responses: &mut VecDeque<Message>) {
|
||||
use TransformLayerMessage::*;
|
||||
|
||||
let (layer_data, document, ipp) = data;
|
||||
let mut selected = Selected::new(&mut self.original_transforms, &mut self.pivot, layer_data, responses, document);
|
||||
|
||||
let mut begin_operation = |operation: Operation, typing: &mut Typing, mouse_position: &mut DVec2, start_mouse: &mut DVec2| {
|
||||
if !(operation == Operation::None) {
|
||||
selected.revert_operation();
|
||||
typing.clear();
|
||||
} else {
|
||||
*selected.pivot = selected.calculate_pivot();
|
||||
}
|
||||
|
||||
*mouse_position = ipp.mouse.position;
|
||||
*start_mouse = ipp.mouse.position;
|
||||
};
|
||||
|
||||
match message {
|
||||
BeginGrab => {
|
||||
if let Operation::Grabbing(_) = self.operation {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_operation(self.operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
|
||||
|
||||
self.operation = Operation::Grabbing(Default::default());
|
||||
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
}
|
||||
BeginRotate => {
|
||||
if let Operation::Rotating(_) = self.operation {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_operation(self.operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
|
||||
|
||||
self.operation = Operation::Rotating(Default::default());
|
||||
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
}
|
||||
BeginScale => {
|
||||
if let Operation::Scaling(_) = self.operation {
|
||||
return;
|
||||
}
|
||||
|
||||
begin_operation(self.operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse);
|
||||
|
||||
self.operation = Operation::Scaling(Default::default());
|
||||
self.operation.apply_operation(&mut selected, self.snap);
|
||||
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
}
|
||||
CancelOperation => {
|
||||
selected.revert_operation();
|
||||
|
||||
selected.original_transforms.clear();
|
||||
self.typing.clear();
|
||||
|
||||
self.operation = Operation::None;
|
||||
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
}
|
||||
ApplyOperation => {
|
||||
self.original_transforms.clear();
|
||||
self.typing.clear();
|
||||
|
||||
self.operation = Operation::None;
|
||||
|
||||
responses.push_back(ToolMessage::SelectedLayersChanged.into());
|
||||
}
|
||||
MouseMove { slow_key, snap_key } => {
|
||||
self.slow = ipp.keyboard.get(slow_key as usize);
|
||||
|
||||
let new_snap = ipp.keyboard.get(snap_key as usize);
|
||||
if new_snap != self.snap {
|
||||
self.snap = new_snap;
|
||||
self.operation.apply_operation(&mut selected, self.snap);
|
||||
}
|
||||
|
||||
if self.typing.digits.is_empty() {
|
||||
let delta_pos = ipp.mouse.position - self.mouse_position;
|
||||
|
||||
match self.operation {
|
||||
Operation::None => unreachable!(),
|
||||
Operation::Grabbing(translation) => {
|
||||
let change = if self.slow { delta_pos / SLOWING_DIVISOR } else { delta_pos };
|
||||
self.operation = Operation::Grabbing(translation.increment_amount(change));
|
||||
self.operation.apply_operation(&mut selected, self.snap);
|
||||
}
|
||||
Operation::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;
|
||||
|
||||
start_vec.angle_between(end_vec)
|
||||
};
|
||||
|
||||
let change = if self.slow { angle / SLOWING_DIVISOR } else { angle };
|
||||
self.operation = Operation::Rotating(rotation.increment_amount(change));
|
||||
self.operation.apply_operation(&mut selected, self.snap);
|
||||
}
|
||||
Operation::Scaling(scale) => {
|
||||
let change = {
|
||||
let previous_frame_dist = (self.mouse_position - *selected.pivot).length();
|
||||
let current_frame_dist = (ipp.mouse.position - *selected.pivot).length();
|
||||
let start_transform_dist = (self.start_mouse - *selected.pivot).length();
|
||||
|
||||
(current_frame_dist - previous_frame_dist) / start_transform_dist
|
||||
};
|
||||
|
||||
let change = if self.slow { change / SLOWING_DIVISOR } else { change };
|
||||
self.operation = Operation::Scaling(scale.increment_amount(change));
|
||||
self.operation.apply_operation(&mut selected, self.snap);
|
||||
}
|
||||
};
|
||||
}
|
||||
self.mouse_position = ipp.mouse.position;
|
||||
}
|
||||
TypeNumber(number) => self.operation.handle_typed(self.typing.type_number(number), &mut selected, self.snap),
|
||||
TypeBackspace => self.operation.handle_typed(self.typing.type_backspace(), &mut selected, self.snap),
|
||||
TypeDecimalPoint => self.operation.handle_typed(self.typing.type_decimal_point(), &mut selected, self.snap),
|
||||
TypeNegate => self.operation.handle_typed(self.typing.type_negate(), &mut selected, self.snap),
|
||||
ConstrainX => self.operation.constrain_axis(Axis::X, &mut selected, self.snap),
|
||||
ConstrainY => self.operation.constrain_axis(Axis::Y, &mut selected, self.snap),
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut common = actions!(TransformLayerMessageDiscriminant;
|
||||
BeginGrab,
|
||||
BeginScale,
|
||||
BeginRotate,
|
||||
);
|
||||
|
||||
if self.operation != Operation::None {
|
||||
let active = actions!(TransformLayerMessageDiscriminant;
|
||||
MouseMove,
|
||||
CancelOperation,
|
||||
ApplyOperation,
|
||||
TypeNumber,
|
||||
TypeBackspace,
|
||||
TypeDecimalPoint,
|
||||
TypeNegate,
|
||||
ConstrainX,
|
||||
ConstrainY,
|
||||
);
|
||||
common.extend(active);
|
||||
}
|
||||
|
||||
common
|
||||
}
|
||||
}
|
||||
|
|
@ -131,6 +131,21 @@ impl Default for Mapping {
|
|||
// it as an available action in the respective message handler file (such as the bottom of `document_message_handler.rs`)
|
||||
let mappings = mapping![
|
||||
// Higher priority than entries in sections below
|
||||
entry! {action=DocumentsMessage::Paste, key_down=KeyV, modifiers=[KeyControl]},
|
||||
entry! {action=MovementMessage::EnableSnapping, key_down=KeyShift},
|
||||
entry! {action=MovementMessage::DisableSnapping, key_up=KeyShift},
|
||||
// Transform layers
|
||||
entry! {action=TransformLayerMessage::ApplyOperation, key_down=KeyEnter},
|
||||
entry! {action=TransformLayerMessage::ApplyOperation, key_down=Lmb},
|
||||
entry! {action=TransformLayerMessage::CancelOperation, key_down=KeyEscape},
|
||||
entry! {action=TransformLayerMessage::CancelOperation, key_down=Rmb},
|
||||
entry! {action=TransformLayerMessage::ConstrainX, key_down=KeyX},
|
||||
entry! {action=TransformLayerMessage::ConstrainY, key_down=KeyY},
|
||||
entry! {action=TransformLayerMessage::TypeBackspace, key_down=KeyBackspace},
|
||||
entry! {action=TransformLayerMessage::TypeNegate, key_down=KeyMinus},
|
||||
entry! {action=TransformLayerMessage::TypeDecimalPoint, key_down=KeyComma},
|
||||
entry! {action=TransformLayerMessage::TypeDecimalPoint, key_down=KeyPeriod},
|
||||
entry! {action=TransformLayerMessage::MouseMove{slow_key: KeyShift, snap_key: KeyControl}, triggers=[KeyShift, KeyControl]},
|
||||
// Select
|
||||
entry! {action=SelectMessage::MouseMove, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=SelectMessage::DragStart{add_to_selection: KeyShift}, key_down=Lmb},
|
||||
|
|
@ -201,6 +216,10 @@ impl Default for Mapping {
|
|||
entry! {action=DocumentMessage::ExportDocument, key_down=KeyE, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::SaveDocument, key_down=KeyS, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::SaveDocument, key_down=KeyS, modifiers=[KeyControl, KeyShift]},
|
||||
// Initiate Transform Layers
|
||||
entry! {action=TransformLayerMessage::BeginGrab, key_down=KeyG},
|
||||
entry! {action=TransformLayerMessage::BeginRotate, key_down=KeyR},
|
||||
entry! {action=TransformLayerMessage::BeginScale, key_down=KeyS},
|
||||
// Document movement
|
||||
entry! {action=MovementMessage::MouseMove, message=InputMapperMessage::PointerMove},
|
||||
entry! {action=MovementMessage::RotateCanvasBegin{snap:false}, key_down=Mmb, modifiers=[KeyControl]},
|
||||
|
|
@ -231,7 +250,7 @@ impl Default for Mapping {
|
|||
entry! {action=DocumentsMessage::CloseActiveDocumentWithConfirmation, key_down=KeyW, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::DuplicateSelectedLayers, key_down=KeyD, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentsMessage::Copy, key_down=KeyC, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::GroupSelectedLayers, key_down=KeyG},
|
||||
entry! {action=DocumentMessage::GroupSelectedLayers, key_down=KeyG, modifiers=[KeyControl]},
|
||||
// Nudging
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(-SHIFT_NUDGE_AMOUNT, -SHIFT_NUDGE_AMOUNT), key_down=KeyArrowUp, modifiers=[KeyShift, KeyArrowLeft]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(SHIFT_NUDGE_AMOUNT, -SHIFT_NUDGE_AMOUNT), key_down=KeyArrowUp, modifiers=[KeyShift, KeyArrowRight]},
|
||||
|
|
@ -257,6 +276,7 @@ impl Default for Mapping {
|
|||
entry! {action=DocumentMessage::NudgeSelectedLayers(NUDGE_AMOUNT, -NUDGE_AMOUNT), key_down=KeyArrowRight, modifiers=[KeyArrowUp]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(NUDGE_AMOUNT, NUDGE_AMOUNT), key_down=KeyArrowRight, modifiers=[KeyArrowDown]},
|
||||
entry! {action=DocumentMessage::NudgeSelectedLayers(NUDGE_AMOUNT, 0.), key_down=KeyArrowRight},
|
||||
// Reorder Layers
|
||||
entry! {action=DocumentMessage::ReorderSelectedLayers(i32::MAX), key_down=KeyRightCurlyBracket, modifiers=[KeyControl]}, // TODO: Use KeyRightBracket with ctrl+shift modifiers once input system is fixed
|
||||
entry! {action=DocumentMessage::ReorderSelectedLayers(1), key_down=KeyRightBracket, modifiers=[KeyControl]},
|
||||
entry! {action=DocumentMessage::ReorderSelectedLayers(-1), key_down=KeyLeftBracket, modifiers=[KeyControl]},
|
||||
|
|
@ -268,6 +288,17 @@ impl Default for Mapping {
|
|||
];
|
||||
|
||||
let (mut key_up, mut key_down, mut pointer_move, mut mouse_scroll) = mappings;
|
||||
const NUMBER_KEYS: [Key; 10] = [Key0, Key1, Key2, Key3, Key4, Key5, Key6, Key7, Key8, Key9];
|
||||
for (i, key) in NUMBER_KEYS.iter().enumerate() {
|
||||
key_down[*key as usize].0.insert(
|
||||
0,
|
||||
MappingEntry {
|
||||
trigger: InputMapperMessage::KeyDown(*key),
|
||||
modifiers: modifiers! {},
|
||||
action: TransformLayerMessage::TypeNumber(i as u8).into(),
|
||||
},
|
||||
);
|
||||
}
|
||||
let sort = |list: &mut KeyMappingEntries| list.0.sort_by(|u, v| v.modifiers.ones().cmp(&u.modifiers.ones()));
|
||||
for list in [&mut key_up, &mut key_down] {
|
||||
for sublist in list {
|
||||
|
|
|
|||
|
|
@ -80,6 +80,8 @@ pub enum Key {
|
|||
KeyRightCurlyBracket,
|
||||
KeyPageUp,
|
||||
KeyPageDown,
|
||||
KeyComma,
|
||||
KeyPeriod,
|
||||
|
||||
// This has to be the last element in the enum.
|
||||
NumKeys,
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ pub mod message_prelude {
|
|||
pub use crate::document::{DocumentMessage, DocumentMessageDiscriminant};
|
||||
pub use crate::document::{DocumentsMessage, DocumentsMessageDiscriminant};
|
||||
pub use crate::document::{MovementMessage, MovementMessageDiscriminant};
|
||||
pub use crate::document::{TransformLayerMessage, TransformLayerMessageDiscriminant};
|
||||
pub use crate::frontend::{FrontendMessage, FrontendMessageDiscriminant};
|
||||
pub use crate::global::{GlobalMessage, GlobalMessageDiscriminant};
|
||||
pub use crate::input::{InputMapperMessage, InputMapperMessageDiscriminant, InputPreprocessorMessage, InputPreprocessorMessageDiscriminant};
|
||||
|
|
|
|||
|
|
@ -154,6 +154,8 @@ pub fn translate_key(name: &str) -> Key {
|
|||
"}" => KeyRightCurlyBracket,
|
||||
"pageup" => KeyPageUp,
|
||||
"pagedown" => KeyPageDown,
|
||||
"," => KeyComma,
|
||||
"." => KeyPeriod,
|
||||
_ => UnknownKey,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,8 +137,12 @@ impl Layer {
|
|||
self.data.intersects_quad(transformed_quad, path, intersections)
|
||||
}
|
||||
|
||||
pub fn current_bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.data.bounding_box(transform)
|
||||
}
|
||||
|
||||
pub fn current_bounding_box(&self) -> Option<[DVec2; 2]> {
|
||||
self.data.bounding_box(self.transform)
|
||||
self.current_bounding_box_with_transform(self.transform)
|
||||
}
|
||||
|
||||
pub fn as_folder_mut(&mut self) -> Result<&mut Folder, DocumentError> {
|
||||
|
|
|
|||
Loading…
Reference in New Issue