Further polishing of G/R/S visualization and features (#2243)
* Further polishing of G/R/S visualisation and features Followup to #2229. * Begin typing only if constrained or not in G * Prevent adding empty group in R mode. Order fn alphabetically as was before * Always show typing hints unless can't begin typing * Fix one frame bug * Add cancel and confirm groups for GRS hints * Fix inconsistency in call increments, snaps * Use top/bottom left/right methods with quads where more readable * Fix inconsistent use of narrow/flat * Add hints to transform cage Fixes https://discord.com/channels/731730685944922173/881073965047636018/939265895509925898. * Rename some hints * Fix scale radial behaviour, grab constraints and local edge orientation * Fix not being able to remove the whole selection with delete modifier Fixes https://discord.com/channels/731730685944922173/881073965047636018/1336221441716391937. * Fix compiling * Fix crash when single point bbox Fixes #2267 * Fix the same crash in scale and use better name for bbox * cargo fmt --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
da752e5324
commit
4de65c292a
|
|
@ -41,8 +41,8 @@ pub const MAX_LAYER_SNAP_POINTS: usize = 100;
|
||||||
pub const DRAG_THRESHOLD: f64 = 1.;
|
pub const DRAG_THRESHOLD: f64 = 1.;
|
||||||
|
|
||||||
// TRANSFORMING LAYER
|
// TRANSFORMING LAYER
|
||||||
pub const ROTATE_SNAP_ANGLE: f64 = 15.;
|
pub const ROTATE_INCREMENT: f64 = 15.;
|
||||||
pub const SCALE_SNAP_INTERVAL: f64 = 0.1;
|
pub const SCALE_INCREMENT: f64 = 0.1;
|
||||||
pub const SLOWING_DIVISOR: f64 = 10.;
|
pub const SLOWING_DIVISOR: f64 = 10.;
|
||||||
pub const NUDGE_AMOUNT: f64 = 1.;
|
pub const NUDGE_AMOUNT: f64 = 1.;
|
||||||
pub const BIG_NUDGE_AMOUNT: f64 = 10.;
|
pub const BIG_NUDGE_AMOUNT: f64 = 10.;
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ impl OverlayContext {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn polygon(&mut self, polygon: &[DVec2], color_fill: Option<&str>) {
|
pub fn polygon(&mut self, polygon: &[DVec2], color_fill: Option<&str>) {
|
||||||
self.dashed_polygon(&polygon, color_fill, None, None, None);
|
self.dashed_polygon(polygon, color_fill, None, None, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dashed_polygon(&mut self, polygon: &[DVec2], color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
pub fn dashed_polygon(&mut self, polygon: &[DVec2], color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use super::network_interface::NodeNetworkInterface;
|
use super::network_interface::NodeNetworkInterface;
|
||||||
use crate::consts::{ROTATE_SNAP_ANGLE, SCALE_SNAP_INTERVAL};
|
use crate::consts::{ROTATE_INCREMENT, SCALE_INCREMENT};
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
@ -12,7 +12,7 @@ use graphene_core::vector::ManipulatorPointId;
|
||||||
use graphene_core::vector::VectorModificationType;
|
use graphene_core::vector::VectorModificationType;
|
||||||
use graphene_std::vector::{HandleId, PointId};
|
use graphene_std::vector::{HandleId, PointId};
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
use std::collections::{HashMap, VecDeque};
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
|
@ -143,10 +143,13 @@ pub struct Translation {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Translation {
|
impl Translation {
|
||||||
pub fn to_dvec(self, transform: DAffine2, snap: bool) -> DVec2 {
|
pub fn to_dvec(self, transform: DAffine2, increment_mode: bool) -> DVec2 {
|
||||||
let displacement = if let Some(value) = self.typed_distance {
|
let displacement = if let Some(value) = self.typed_distance {
|
||||||
let document_displacement = if self.constraint == Axis::Y { DVec2::new(0., value) } else { DVec2::new(value, 0.) };
|
match self.constraint {
|
||||||
transform.transform_vector2(document_displacement)
|
Axis::X => transform.transform_vector2(DVec2::new(value, 0.)),
|
||||||
|
Axis::Y => transform.transform_vector2(DVec2::new(0., value)),
|
||||||
|
Axis::Both => self.dragged_distance,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
match self.constraint {
|
match self.constraint {
|
||||||
Axis::Both => self.dragged_distance,
|
Axis::Both => self.dragged_distance,
|
||||||
|
|
@ -155,7 +158,7 @@ impl Translation {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let displacement = transform.inverse().transform_vector2(displacement);
|
let displacement = transform.inverse().transform_vector2(displacement);
|
||||||
if snap {
|
if increment_mode {
|
||||||
displacement.round()
|
displacement.round()
|
||||||
} else {
|
} else {
|
||||||
displacement
|
displacement
|
||||||
|
|
@ -196,12 +199,12 @@ pub struct Rotation {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Rotation {
|
impl Rotation {
|
||||||
pub fn to_f64(self, snap: bool) -> f64 {
|
pub fn to_f64(self, increment_mode: bool) -> f64 {
|
||||||
if let Some(value) = self.typed_angle {
|
if let Some(value) = self.typed_angle {
|
||||||
value.to_radians()
|
value.to_radians()
|
||||||
} else if snap {
|
} else if increment_mode {
|
||||||
let snap_resolution = ROTATE_SNAP_ANGLE.to_radians();
|
let increment_resolution = ROTATE_INCREMENT.to_radians();
|
||||||
(self.dragged_angle / snap_resolution).round() * snap_resolution
|
(self.dragged_angle / increment_resolution).round() * increment_resolution
|
||||||
} else {
|
} else {
|
||||||
self.dragged_angle
|
self.dragged_angle
|
||||||
}
|
}
|
||||||
|
|
@ -245,17 +248,17 @@ impl Default for Scale {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scale {
|
impl Scale {
|
||||||
pub fn to_f64(self, snap: bool) -> f64 {
|
pub fn to_f64(self, increment: bool) -> f64 {
|
||||||
let factor = if let Some(value) = self.typed_factor { value } else { self.dragged_factor };
|
let factor = if let Some(value) = self.typed_factor { value } else { self.dragged_factor };
|
||||||
if snap {
|
if increment {
|
||||||
(factor / SCALE_SNAP_INTERVAL).round() * SCALE_SNAP_INTERVAL
|
(factor / SCALE_INCREMENT).round() * SCALE_INCREMENT
|
||||||
} else {
|
} else {
|
||||||
factor
|
factor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_dvec(self, snap: bool) -> DVec2 {
|
pub fn to_dvec(self, increment_mode: bool) -> DVec2 {
|
||||||
let factor = self.to_f64(snap);
|
let factor = self.to_f64(increment_mode);
|
||||||
|
|
||||||
match self.constraint {
|
match self.constraint {
|
||||||
Axis::Both => DVec2::splat(factor),
|
Axis::Both => DVec2::splat(factor),
|
||||||
|
|
@ -272,7 +275,7 @@ impl Scale {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn increment_amount(self, delta: f64) -> Self {
|
pub fn increment_amount(self, delta: f64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
dragged_factor: self.dragged_factor + delta,
|
dragged_factor: (self.dragged_factor + delta),
|
||||||
typed_factor: None,
|
typed_factor: None,
|
||||||
constraint: self.constraint,
|
constraint: self.constraint,
|
||||||
}
|
}
|
||||||
|
|
@ -302,25 +305,29 @@ pub enum TransformOperation {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TransformOperation {
|
impl TransformOperation {
|
||||||
pub fn apply_transform_operation(&self, selected: &mut Selected, snapping: bool, local: bool, quad: Quad, transform: DAffine2) {
|
pub fn apply_transform_operation(&self, selected: &mut Selected, increment_mode: bool, local: bool, quad: Quad, transform: DAffine2) {
|
||||||
let quad = quad.0;
|
let local_axis_transform_angle = (quad.top_left() - quad.top_right()).to_angle();
|
||||||
let edge = quad[1] - quad[0];
|
|
||||||
if self != &TransformOperation::None {
|
if self != &TransformOperation::None {
|
||||||
let transformation = match self {
|
let transformation = match self {
|
||||||
TransformOperation::Grabbing(translation) => {
|
TransformOperation::Grabbing(translation) => {
|
||||||
let translate = DAffine2::from_translation(transform.transform_vector2(translation.to_dvec(transform, snapping)));
|
let translate = DAffine2::from_translation(transform.transform_vector2(translation.to_dvec(transform, increment_mode)));
|
||||||
if local {
|
if local {
|
||||||
DAffine2::from_angle(edge.to_angle()) * translate * DAffine2::from_angle(-edge.to_angle())
|
let resolved_angle = if local_axis_transform_angle > 0. {
|
||||||
|
local_axis_transform_angle - std::f64::consts::PI
|
||||||
|
} else {
|
||||||
|
local_axis_transform_angle
|
||||||
|
};
|
||||||
|
DAffine2::from_angle(resolved_angle) * translate * DAffine2::from_angle(-resolved_angle)
|
||||||
} else {
|
} else {
|
||||||
translate
|
translate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TransformOperation::Rotating(rotation) => DAffine2::from_angle(rotation.to_f64(snapping)),
|
TransformOperation::Rotating(rotation) => DAffine2::from_angle(rotation.to_f64(increment_mode)),
|
||||||
TransformOperation::Scaling(scale) => {
|
TransformOperation::Scaling(scale) => {
|
||||||
if local {
|
if local {
|
||||||
DAffine2::from_angle(edge.to_angle()) * DAffine2::from_scale(scale.to_dvec(snapping)) * DAffine2::from_angle(-edge.to_angle())
|
DAffine2::from_angle(local_axis_transform_angle) * DAffine2::from_scale(scale.to_dvec(increment_mode)) * DAffine2::from_angle(-local_axis_transform_angle)
|
||||||
} else {
|
} else {
|
||||||
DAffine2::from_scale(scale.to_dvec(snapping))
|
DAffine2::from_scale(scale.to_dvec(increment_mode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TransformOperation::None => unreachable!(),
|
TransformOperation::None => unreachable!(),
|
||||||
|
|
@ -331,16 +338,19 @@ impl TransformOperation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_typing(&self) -> bool {
|
pub fn axis_constraint(&self) -> Axis {
|
||||||
match self {
|
match self {
|
||||||
TransformOperation::None => false,
|
TransformOperation::Grabbing(grabbing) => grabbing.constraint,
|
||||||
TransformOperation::Grabbing(translation) => translation.typed_distance.is_some(),
|
TransformOperation::Scaling(scaling) => scaling.constraint,
|
||||||
TransformOperation::Rotating(rotation) => rotation.typed_angle.is_some(),
|
_ => Axis::Both,
|
||||||
TransformOperation::Scaling(scale) => scale.typed_factor.is_some(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn constrain_axis(&mut self, axis: Axis, selected: &mut Selected, snapping: bool, mut local: bool, quad: Quad, transform: DAffine2) -> bool {
|
pub fn can_begin_typing(&self) -> bool {
|
||||||
|
self.is_constraint_to_axis() || !matches!(self, TransformOperation::Grabbing(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn constrain_axis(&mut self, axis: Axis, selected: &mut Selected, increment_mode: bool, mut local: bool, quad: Quad, transform: DAffine2) -> bool {
|
||||||
(*self, local) = match self {
|
(*self, local) = match self {
|
||||||
TransformOperation::Grabbing(translation) => {
|
TransformOperation::Grabbing(translation) => {
|
||||||
let (translation, local) = translation.with_constraint(axis, local);
|
let (translation, local) = translation.with_constraint(axis, local);
|
||||||
|
|
@ -352,11 +362,11 @@ impl TransformOperation {
|
||||||
}
|
}
|
||||||
_ => (*self, false),
|
_ => (*self, false),
|
||||||
};
|
};
|
||||||
self.apply_transform_operation(selected, snapping, local, quad, transform);
|
self.apply_transform_operation(selected, increment_mode, local, quad, transform);
|
||||||
local
|
local
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn grs_typed(&mut self, typed: Option<f64>, selected: &mut Selected, snapping: bool, local: bool, quad: Quad, transform: DAffine2) {
|
pub fn grs_typed(&mut self, typed: Option<f64>, selected: &mut Selected, increment_mode: bool, local: bool, quad: Quad, transform: DAffine2) {
|
||||||
match self {
|
match self {
|
||||||
TransformOperation::None => (),
|
TransformOperation::None => (),
|
||||||
TransformOperation::Grabbing(translation) => translation.typed_distance = typed,
|
TransformOperation::Grabbing(translation) => translation.typed_distance = typed,
|
||||||
|
|
@ -364,45 +374,34 @@ impl TransformOperation {
|
||||||
TransformOperation::Scaling(scale) => scale.typed_factor = typed,
|
TransformOperation::Scaling(scale) => scale.typed_factor = typed,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.apply_transform_operation(selected, snapping, local, quad, transform);
|
self.apply_transform_operation(selected, increment_mode, local, quad, transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hints(&self, responses: &mut VecDeque<Message>, local: bool) {
|
pub fn hints(&self, responses: &mut VecDeque<Message>, local: bool) {
|
||||||
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
|
||||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||||
|
|
||||||
let mut input_hints = Vec::new();
|
let mut input_hints = Vec::new();
|
||||||
if self.is_typing() {
|
let clear_constraint = "Clear Constraint";
|
||||||
input_hints.push(HintInfo::keys([Key::Minus], "Negate Direction"));
|
match self.axis_constraint() {
|
||||||
input_hints.push(HintInfo::keys([Key::Backspace], "Delete Digit"));
|
Axis::Both => {
|
||||||
input_hints.push(HintInfo::keys([Key::NumKeys], "Enter Number"));
|
input_hints.push(HintInfo::keys([Key::KeyX], "X-Axis Constraint"));
|
||||||
} else if matches!(self, TransformOperation::Grabbing(_) | TransformOperation::Scaling(_)) {
|
input_hints.push(HintInfo::keys([Key::KeyY], "Y-Axis Constraint"));
|
||||||
let axis_constraint = match self {
|
}
|
||||||
TransformOperation::Grabbing(grabbing) => grabbing.constraint,
|
Axis::X => {
|
||||||
TransformOperation::Scaling(scaling) => scaling.constraint,
|
let x_label = if local { clear_constraint } else { "Local X-Axis Constraint" };
|
||||||
_ => Axis::Both,
|
input_hints.push(HintInfo::keys([Key::KeyX], x_label));
|
||||||
};
|
input_hints.push(HintInfo::keys([Key::KeyY], "Y-Axis Constraint"));
|
||||||
let clear_constraint = "Clear Constraint";
|
if !local {
|
||||||
match axis_constraint {
|
input_hints.push(HintInfo::keys([Key::KeyX, Key::KeyX], clear_constraint));
|
||||||
Axis::Both => {
|
|
||||||
input_hints.push(HintInfo::keys([Key::KeyX], "Along X Axis"));
|
|
||||||
input_hints.push(HintInfo::keys([Key::KeyY], "Along Y Axis"));
|
|
||||||
}
|
}
|
||||||
Axis::X => {
|
}
|
||||||
let x_label = if local { clear_constraint } else { "Along Local X Axis" };
|
Axis::Y => {
|
||||||
input_hints.push(HintInfo::keys([Key::KeyX], x_label));
|
let y_label = if local { clear_constraint } else { "Local Y-Axis Constraint" };
|
||||||
input_hints.push(HintInfo::keys([Key::KeyY], "Along Y Axis"));
|
input_hints.push(HintInfo::keys([Key::KeyX], "X-Axis Constraint"));
|
||||||
if !local {
|
input_hints.push(HintInfo::keys([Key::KeyY], y_label));
|
||||||
input_hints.push(HintInfo::keys([Key::KeyX, Key::KeyX], clear_constraint));
|
if !local {
|
||||||
}
|
input_hints.push(HintInfo::keys([Key::KeyY, Key::KeyY], clear_constraint));
|
||||||
}
|
|
||||||
Axis::Y => {
|
|
||||||
let y_label = if local { clear_constraint } else { "Along Local Y Axis" };
|
|
||||||
input_hints.push(HintInfo::keys([Key::KeyX], "Along X Axis"));
|
|
||||||
input_hints.push(HintInfo::keys([Key::KeyY], y_label));
|
|
||||||
if !local {
|
|
||||||
input_hints.push(HintInfo::keys([Key::KeyY, Key::KeyY], clear_constraint));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -414,18 +413,50 @@ impl TransformOperation {
|
||||||
TransformOperation::Rotating(_) => HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyS]], "Grab/Scale Selected")]),
|
TransformOperation::Rotating(_) => HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyS]], "Grab/Scale Selected")]),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut hint_groups = vec![grs_hint_group];
|
let confirm_and_cancel_group = HintGroup(vec![
|
||||||
|
HintInfo::mouse(MouseMotion::Lmb, ""),
|
||||||
|
HintInfo::keys([Key::Enter], "Confirm").prepend_slash(),
|
||||||
|
HintInfo::mouse(MouseMotion::Rmb, ""),
|
||||||
|
HintInfo::keys([Key::Escape], "Cancel").prepend_slash(),
|
||||||
|
]);
|
||||||
|
let mut hint_groups = vec![confirm_and_cancel_group, grs_hint_group];
|
||||||
if !self.is_typing() {
|
if !self.is_typing() {
|
||||||
let modifiers = vec![HintInfo::keys([Key::Shift], "Slow"), HintInfo::keys([Key::Control], "Increments")];
|
let modifiers = vec![
|
||||||
|
HintInfo::keys([Key::Shift], "Slow"),
|
||||||
|
HintInfo::keys([Key::Control], if matches!(self, TransformOperation::Rotating(_)) { "15° Increments" } else { "Increments" }),
|
||||||
|
];
|
||||||
hint_groups.push(HintGroup(modifiers));
|
hint_groups.push(HintGroup(modifiers));
|
||||||
}
|
}
|
||||||
hint_groups.push(HintGroup(input_hints));
|
if !matches!(self, TransformOperation::Rotating(_)) {
|
||||||
|
hint_groups.push(HintGroup(input_hints));
|
||||||
|
}
|
||||||
|
let mut typing_hints = vec![HintInfo::keys([Key::Minus], "Negate Direction")];
|
||||||
|
if self.can_begin_typing() {
|
||||||
|
typing_hints.push(HintInfo::keys([Key::NumKeys], "Enter Number"));
|
||||||
|
if self.is_typing() {
|
||||||
|
typing_hints.push(HintInfo::keys([Key::Backspace], "Delete Digit"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hint_groups.push(HintGroup(typing_hints));
|
||||||
|
|
||||||
let hint_data = HintData(hint_groups);
|
let hint_data = HintData(hint_groups);
|
||||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn negate(&mut self, selected: &mut Selected, snapping: bool, local: bool, quad: Quad, transform: DAffine2) {
|
pub fn is_constraint_to_axis(&self) -> bool {
|
||||||
|
self.axis_constraint() != Axis::Both
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_typing(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
TransformOperation::None => false,
|
||||||
|
TransformOperation::Grabbing(translation) => translation.typed_distance.is_some(),
|
||||||
|
TransformOperation::Rotating(rotation) => rotation.typed_angle.is_some(),
|
||||||
|
TransformOperation::Scaling(scale) => scale.typed_factor.is_some(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn negate(&mut self, selected: &mut Selected, increment_mode: bool, local: bool, quad: Quad, transform: DAffine2) {
|
||||||
if *self != TransformOperation::None {
|
if *self != TransformOperation::None {
|
||||||
*self = match self {
|
*self = match self {
|
||||||
TransformOperation::Scaling(scale) => TransformOperation::Scaling(scale.negate()),
|
TransformOperation::Scaling(scale) => TransformOperation::Scaling(scale.negate()),
|
||||||
|
|
@ -433,7 +464,7 @@ impl TransformOperation {
|
||||||
TransformOperation::Grabbing(translation) => TransformOperation::Grabbing(translation.negate()),
|
TransformOperation::Grabbing(translation) => TransformOperation::Grabbing(translation.negate()),
|
||||||
_ => *self,
|
_ => *self,
|
||||||
};
|
};
|
||||||
self.apply_transform_operation(selected, snapping, local, quad, transform);
|
self.apply_transform_operation(selected, increment_mode, local, quad, transform);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -505,7 +536,7 @@ impl<'a> Selected<'a> {
|
||||||
pub fn bounding_box(&mut self) -> Quad {
|
pub fn bounding_box(&mut self) -> Quad {
|
||||||
let metadata = self.network_interface.document_metadata();
|
let metadata = self.network_interface.document_metadata();
|
||||||
|
|
||||||
let transform = self
|
let mut transform = self
|
||||||
.network_interface
|
.network_interface
|
||||||
.selected_nodes(&[])
|
.selected_nodes(&[])
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -514,8 +545,8 @@ impl<'a> Selected<'a> {
|
||||||
.map(|layer| metadata.transform_to_viewport(layer))
|
.map(|layer| metadata.transform_to_viewport(layer))
|
||||||
.unwrap_or(DAffine2::IDENTITY);
|
.unwrap_or(DAffine2::IDENTITY);
|
||||||
|
|
||||||
if transform.matrix2.determinant() == 0. {
|
if transform.matrix2.determinant().abs() <= f64::EPSILON {
|
||||||
return Default::default();
|
transform.matrix2 += DMat2::IDENTITY * 1e-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
let bounds = self
|
let bounds = self
|
||||||
|
|
|
||||||
|
|
@ -507,9 +507,9 @@ impl BoundingBoxManager {
|
||||||
let cursor = self.transform.inverse().transform_point2(cursor);
|
let cursor = self.transform.inverse().transform_point2(cursor);
|
||||||
let [threshold_x, threshold_y] = self.compute_viewport_threshold(BOUNDS_ROTATE_THRESHOLD);
|
let [threshold_x, threshold_y] = self.compute_viewport_threshold(BOUNDS_ROTATE_THRESHOLD);
|
||||||
|
|
||||||
let narrow = (self.bounds[0] - self.bounds[1]).abs().cmple(DVec2::splat(1e-4)).any();
|
let flat = (self.bounds[0] - self.bounds[1]).abs().cmple(DVec2::splat(1e-4)).any();
|
||||||
let within_square_bounds = |center: &DVec2| center.x - threshold_x < cursor.x && cursor.x < center.x + threshold_x && center.y - threshold_y < cursor.y && cursor.y < center.y + threshold_y;
|
let within_square_bounds = |center: &DVec2| center.x - threshold_x < cursor.x && cursor.x < center.x + threshold_x && center.y - threshold_y < cursor.y && cursor.y < center.y + threshold_y;
|
||||||
if narrow {
|
if flat {
|
||||||
[self.bounds[0], self.bounds[1]].iter().any(within_square_bounds)
|
[self.bounds[0], self.bounds[1]].iter().any(within_square_bounds)
|
||||||
} else {
|
} else {
|
||||||
self.evaluate_transform_handle_positions().iter().any(within_square_bounds)
|
self.evaluate_transform_handle_positions().iter().any(within_square_bounds)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#![allow(clippy::too_many_arguments)]
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
|
||||||
use super::tool_prelude::*;
|
use super::tool_prelude::*;
|
||||||
use crate::consts::{DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, ROTATE_SNAP_ANGLE, SELECTION_TOLERANCE};
|
use crate::consts::{DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, ROTATE_INCREMENT, SELECTION_TOLERANCE};
|
||||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
use crate::messages::input_mapper::utility_types::input_mouse::ViewportPosition;
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
|
|
@ -935,7 +935,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
};
|
};
|
||||||
|
|
||||||
let snapped_angle = if input.keyboard.key(modifier_keys.snap_angle) {
|
let snapped_angle = if input.keyboard.key(modifier_keys.snap_angle) {
|
||||||
let snap_resolution = ROTATE_SNAP_ANGLE.to_radians();
|
let snap_resolution = ROTATE_INCREMENT.to_radians();
|
||||||
(angle / snap_resolution).round() * snap_resolution
|
(angle / snap_resolution).round() * snap_resolution
|
||||||
} else {
|
} else {
|
||||||
angle
|
angle
|
||||||
|
|
@ -1194,24 +1194,27 @@ impl Fsm for SelectToolFsmState {
|
||||||
};
|
};
|
||||||
|
|
||||||
let current_selected: HashSet<_> = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()).collect();
|
let current_selected: HashSet<_> = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()).collect();
|
||||||
if new_selected != current_selected {
|
let negative_selection = input.keyboard.key(remove_from_selection);
|
||||||
// Negative selection when both Shift and Ctrl are pressed
|
let selection_modified = new_selected != current_selected;
|
||||||
if input.keyboard.key(remove_from_selection) {
|
// Negative selection when both Shift and Ctrl are pressed
|
||||||
let updated_selection = current_selected
|
if negative_selection {
|
||||||
.into_iter()
|
let updated_selection = current_selected
|
||||||
.filter(|layer| !new_selected.iter().any(|selected| layer.starts_with(*selected, document.metadata())))
|
.into_iter()
|
||||||
.collect();
|
.filter(|layer| !new_selected.iter().any(|selected| layer.starts_with(*selected, document.metadata())))
|
||||||
tool_data.layers_dragging = updated_selection;
|
.collect();
|
||||||
} else {
|
tool_data.layers_dragging = updated_selection;
|
||||||
let parent_selected: HashSet<_> = new_selected
|
} else if selection_modified {
|
||||||
.into_iter()
|
let parent_selected: HashSet<_> = new_selected
|
||||||
.map(|layer| {
|
.into_iter()
|
||||||
// Find the parent node
|
.map(|layer| {
|
||||||
layer.ancestors(document.metadata()).filter(not_artboard(document)).last().unwrap_or(layer)
|
// Find the parent node
|
||||||
})
|
layer.ancestors(document.metadata()).filter(not_artboard(document)).last().unwrap_or(layer)
|
||||||
.collect();
|
})
|
||||||
tool_data.layers_dragging.extend(parent_selected.iter().copied());
|
.collect();
|
||||||
}
|
tool_data.layers_dragging.extend(parent_selected.iter().copied());
|
||||||
|
}
|
||||||
|
|
||||||
|
if negative_selection || selection_modified {
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet {
|
responses.add(NodeGraphMessage::SelectedNodesSet {
|
||||||
nodes: tool_data
|
nodes: tool_data
|
||||||
.layers_dragging
|
.layers_dragging
|
||||||
|
|
@ -1354,7 +1357,25 @@ impl Fsm for SelectToolFsmState {
|
||||||
]);
|
]);
|
||||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||||
}
|
}
|
||||||
_ => {}
|
SelectToolFsmState::Drawing { .. } | SelectToolFsmState::Dragging => {}
|
||||||
|
SelectToolFsmState::ResizingBounds => {
|
||||||
|
let hint_data = HintData(vec![
|
||||||
|
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||||
|
HintGroup(vec![HintInfo::keys([Key::Alt], "From Pivot"), HintInfo::keys([Key::Shift], "Preserve Aspect Ratio")]),
|
||||||
|
]);
|
||||||
|
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||||
|
}
|
||||||
|
SelectToolFsmState::RotatingBounds => {
|
||||||
|
let hint_data = HintData(vec![
|
||||||
|
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||||
|
HintGroup(vec![HintInfo::keys([Key::Control], "Snap")]),
|
||||||
|
]);
|
||||||
|
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||||
|
}
|
||||||
|
SelectToolFsmState::DraggingPivot | SelectToolFsmState::SkewingBounds => {
|
||||||
|
let hint_data = HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]);
|
||||||
|
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ pub struct TransformLayerMessageHandler {
|
||||||
slow: bool,
|
slow: bool,
|
||||||
increments: bool,
|
increments: bool,
|
||||||
local: bool,
|
local: bool,
|
||||||
fixed_bbox: Quad,
|
layer_bounding_box: Quad,
|
||||||
typing: Typing,
|
typing: Typing,
|
||||||
|
|
||||||
mouse_position: ViewportPosition,
|
mouse_position: ViewportPosition,
|
||||||
|
|
@ -79,18 +79,17 @@ fn calculate_pivot(selected_points: &Vec<&ManipulatorPointId>, vector_data: &Vec
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_edge_to_quad(edge: DVec2, quad: &Quad, local: bool, axis_constraint: Axis) -> DVec2 {
|
fn project_edge_to_quad(edge: DVec2, quad: &Quad, local: bool, axis_constraint: Axis) -> DVec2 {
|
||||||
let quad = quad.0;
|
|
||||||
match axis_constraint {
|
match axis_constraint {
|
||||||
Axis::X => {
|
Axis::X => {
|
||||||
if local {
|
if local {
|
||||||
edge.project_onto(quad[1] - quad[0])
|
edge.project_onto(quad.top_right() - quad.top_left())
|
||||||
} else {
|
} else {
|
||||||
edge.with_y(0.)
|
edge.with_y(0.)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Axis::Y => {
|
Axis::Y => {
|
||||||
if local {
|
if local {
|
||||||
edge.project_onto(quad[3] - quad[0])
|
edge.project_onto(quad.bottom_left() - quad.top_left())
|
||||||
} else {
|
} else {
|
||||||
edge.with_x(0.)
|
edge.with_x(0.)
|
||||||
}
|
}
|
||||||
|
|
@ -177,14 +176,10 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||||
};
|
};
|
||||||
|
|
||||||
let viewport_box = input.viewport_bounds.size();
|
let viewport_box = input.viewport_bounds.size();
|
||||||
let axis_constraint = match self.transform_operation {
|
let axis_constraint = self.transform_operation.axis_constraint();
|
||||||
TransformOperation::Grabbing(grabbing) => grabbing.constraint,
|
|
||||||
TransformOperation::Scaling(scaling) => scaling.constraint,
|
|
||||||
_ => Axis::Both,
|
|
||||||
};
|
|
||||||
|
|
||||||
let format_rounded = |value: f64, precision: usize| {
|
let format_rounded = |value: f64, precision: usize| {
|
||||||
if self.typing.digits.is_empty() {
|
if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
||||||
format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string()
|
format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string()
|
||||||
} else {
|
} else {
|
||||||
self.typing.string.clone()
|
self.typing.string.clone()
|
||||||
|
|
@ -197,7 +192,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||||
let translation = translation.to_dvec(document_to_viewport, self.increments);
|
let translation = translation.to_dvec(document_to_viewport, self.increments);
|
||||||
let viewport_translate = document_to_viewport.transform_vector2(translation);
|
let viewport_translate = document_to_viewport.transform_vector2(translation);
|
||||||
let quad = Quad::from_box([self.grab_target, self.grab_target + viewport_translate]).0;
|
let quad = Quad::from_box([self.grab_target, self.grab_target + viewport_translate]).0;
|
||||||
let e1 = (self.fixed_bbox.0[1] - self.fixed_bbox.0[0]).normalize();
|
let e1 = (self.layer_bounding_box.0[1] - self.layer_bounding_box.0[0]).normalize_or(DVec2::X);
|
||||||
|
|
||||||
if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. {
|
if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. {
|
||||||
let end = if self.local {
|
let end = if self.local {
|
||||||
|
|
@ -221,7 +216,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||||
let x_parameter = viewport_translate.x.clamp(-1., 1.);
|
let x_parameter = viewport_translate.x.clamp(-1., 1.);
|
||||||
let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.);
|
let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.);
|
||||||
let pivot_selection = if x_parameter >= 0. { Pivot::Start } else { Pivot::End };
|
let pivot_selection = if x_parameter >= 0. { Pivot::Start } else { Pivot::End };
|
||||||
if axis_constraint != Axis::Both || self.typing.digits.is_empty() {
|
if axis_constraint != Axis::Both || self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
||||||
overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]);
|
overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -231,16 +226,10 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TransformOperation::Scaling(scale) => {
|
TransformOperation::Scaling(scale) => {
|
||||||
let to_mouse_final = self.mouse_position - self.pivot;
|
let scale = scale.to_f64(self.increments);
|
||||||
let to_mouse_start = self.start_mouse - self.pivot;
|
|
||||||
|
|
||||||
let to_mouse_final = project_edge_to_quad(to_mouse_final, &self.fixed_bbox, self.local, axis_constraint);
|
|
||||||
let to_mouse_start = project_edge_to_quad(to_mouse_start, &self.fixed_bbox, self.local, axis_constraint);
|
|
||||||
|
|
||||||
let scale = scale.to_f64(self.increments) * to_mouse_final.dot(to_mouse_start).signum();
|
|
||||||
let text = format!("{}x", format_rounded(scale, 3));
|
let text = format!("{}x", format_rounded(scale, 3));
|
||||||
let local_edge = self.start_mouse - self.pivot;
|
let local_edge = self.start_mouse - self.pivot;
|
||||||
let local_edge = project_edge_to_quad(local_edge, &self.fixed_bbox, self.local, axis_constraint);
|
let local_edge = project_edge_to_quad(local_edge, &self.layer_bounding_box, self.local, axis_constraint);
|
||||||
let boundary_point = self.pivot + local_edge * scale.min(1.);
|
let boundary_point = self.pivot + local_edge * scale.min(1.);
|
||||||
let end_point = self.pivot + local_edge * scale.max(1.);
|
let end_point = self.pivot + local_edge * scale.max(1.);
|
||||||
|
|
||||||
|
|
@ -249,18 +238,17 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||||
}
|
}
|
||||||
overlay_context.line(boundary_point, end_point, None);
|
overlay_context.line(boundary_point, end_point, None);
|
||||||
|
|
||||||
let transform = DAffine2::from_translation(boundary_point.midpoint(self.pivot) + local_edge.perp().normalize() * local_edge.element_product().signum() * 24.);
|
let transform = DAffine2::from_translation(boundary_point.midpoint(self.pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.);
|
||||||
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||||
}
|
}
|
||||||
TransformOperation::Rotating(rotation) => {
|
TransformOperation::Rotating(rotation) => {
|
||||||
let angle = rotation.to_f64(self.increments);
|
let angle = rotation.to_f64(self.increments);
|
||||||
let quad = self.fixed_bbox.0;
|
|
||||||
let offset_angle = if self.grs_pen_handle {
|
let offset_angle = if self.grs_pen_handle {
|
||||||
self.handle - self.last_point
|
self.handle - self.last_point
|
||||||
} else if using_path_tool {
|
} else if using_path_tool {
|
||||||
self.start_mouse - self.pivot
|
self.start_mouse - self.pivot
|
||||||
} else {
|
} else {
|
||||||
quad[1] - quad[0]
|
self.layer_bounding_box.top_right() - self.layer_bounding_box.top_right()
|
||||||
};
|
};
|
||||||
let offset_angle = offset_angle.to_angle();
|
let offset_angle = offset_angle.to_angle();
|
||||||
let width = viewport_box.max_element();
|
let width = viewport_box.max_element();
|
||||||
|
|
@ -316,7 +304,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||||
let top_left = DVec2::new(last_point.x, handle.y);
|
let top_left = DVec2::new(last_point.x, handle.y);
|
||||||
let bottom_right = DVec2::new(handle.x, last_point.y);
|
let bottom_right = DVec2::new(handle.x, last_point.y);
|
||||||
self.local = false;
|
self.local = false;
|
||||||
self.fixed_bbox = Quad::from_box([top_left, bottom_right]);
|
self.layer_bounding_box = Quad::from_box([top_left, bottom_right]);
|
||||||
self.grab_target = handle;
|
self.grab_target = handle;
|
||||||
self.pivot = last_point;
|
self.pivot = last_point;
|
||||||
self.handle = handle;
|
self.handle = handle;
|
||||||
|
|
@ -351,7 +339,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||||
|
|
||||||
self.transform_operation = TransformOperation::Grabbing(Default::default());
|
self.transform_operation = TransformOperation::Grabbing(Default::default());
|
||||||
self.local = false;
|
self.local = false;
|
||||||
self.fixed_bbox = selected.bounding_box();
|
self.layer_bounding_box = selected.bounding_box();
|
||||||
|
|
||||||
selected.original_transforms.clear();
|
selected.original_transforms.clear();
|
||||||
|
|
||||||
|
|
@ -405,7 +393,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||||
self.transform_operation = TransformOperation::Rotating(Default::default());
|
self.transform_operation = TransformOperation::Rotating(Default::default());
|
||||||
|
|
||||||
self.local = false;
|
self.local = false;
|
||||||
self.fixed_bbox = selected.bounding_box();
|
self.layer_bounding_box = selected.bounding_box();
|
||||||
|
|
||||||
selected.original_transforms.clear();
|
selected.original_transforms.clear();
|
||||||
|
|
||||||
|
|
@ -458,7 +446,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||||
self.transform_operation = TransformOperation::Scaling(Default::default());
|
self.transform_operation = TransformOperation::Scaling(Default::default());
|
||||||
|
|
||||||
self.local = false;
|
self.local = false;
|
||||||
self.fixed_bbox = selected.bounding_box();
|
self.layer_bounding_box = selected.bounding_box();
|
||||||
|
|
||||||
selected.original_transforms.clear();
|
selected.original_transforms.clear();
|
||||||
|
|
||||||
|
|
@ -477,6 +465,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||||
self.handle = DVec2::ZERO;
|
self.handle = DVec2::ZERO;
|
||||||
|
|
||||||
responses.add(PenToolMessage::Abort);
|
responses.add(PenToolMessage::Abort);
|
||||||
|
responses.add(ToolMessage::UpdateHints);
|
||||||
} else {
|
} else {
|
||||||
selected.revert_operation();
|
selected.revert_operation();
|
||||||
selected.original_transforms.clear();
|
selected.original_transforms.clear();
|
||||||
|
|
@ -492,12 +481,16 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||||
TransformLayerMessage::ConstrainX => {
|
TransformLayerMessage::ConstrainX => {
|
||||||
self.local = self
|
self.local = self
|
||||||
.transform_operation
|
.transform_operation
|
||||||
.constrain_axis(Axis::X, &mut selected, self.increments, self.local, self.fixed_bbox, document_to_viewport)
|
.constrain_axis(Axis::X, &mut selected, self.increments, self.local, self.layer_bounding_box, document_to_viewport);
|
||||||
|
self.transform_operation
|
||||||
|
.grs_typed(self.typing.evaluate(), &mut selected, self.increments, self.local, self.layer_bounding_box, document_to_viewport);
|
||||||
}
|
}
|
||||||
TransformLayerMessage::ConstrainY => {
|
TransformLayerMessage::ConstrainY => {
|
||||||
self.local = self
|
self.local = self
|
||||||
.transform_operation
|
.transform_operation
|
||||||
.constrain_axis(Axis::Y, &mut selected, self.increments, self.local, self.fixed_bbox, document_to_viewport)
|
.constrain_axis(Axis::Y, &mut selected, self.increments, self.local, self.layer_bounding_box, document_to_viewport);
|
||||||
|
self.transform_operation
|
||||||
|
.grs_typed(self.typing.evaluate(), &mut selected, self.increments, self.local, self.layer_bounding_box, document_to_viewport);
|
||||||
}
|
}
|
||||||
TransformLayerMessage::PointerMove { slow_key, increments_key } => {
|
TransformLayerMessage::PointerMove { slow_key, increments_key } => {
|
||||||
self.slow = input.keyboard.get(slow_key as usize);
|
self.slow = input.keyboard.get(slow_key as usize);
|
||||||
|
|
@ -506,10 +499,10 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||||
if new_increments != self.increments {
|
if new_increments != self.increments {
|
||||||
self.increments = new_increments;
|
self.increments = new_increments;
|
||||||
self.transform_operation
|
self.transform_operation
|
||||||
.apply_transform_operation(&mut selected, self.increments, self.local, self.fixed_bbox, document_to_viewport);
|
.apply_transform_operation(&mut selected, self.increments, self.local, self.layer_bounding_box, document_to_viewport);
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.typing.digits.is_empty() {
|
if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
||||||
let delta_pos = input.mouse.position - self.mouse_position;
|
let delta_pos = input.mouse.position - self.mouse_position;
|
||||||
|
|
||||||
match self.transform_operation {
|
match self.transform_operation {
|
||||||
|
|
@ -518,7 +511,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||||
let change = if self.slow { delta_pos / SLOWING_DIVISOR } else { delta_pos };
|
let change = if self.slow { delta_pos / SLOWING_DIVISOR } else { delta_pos };
|
||||||
self.transform_operation = TransformOperation::Grabbing(translation.increment_amount(change));
|
self.transform_operation = TransformOperation::Grabbing(translation.increment_amount(change));
|
||||||
self.transform_operation
|
self.transform_operation
|
||||||
.apply_transform_operation(&mut selected, self.increments, self.local, self.fixed_bbox, document_to_viewport);
|
.apply_transform_operation(&mut selected, self.increments, self.local, self.layer_bounding_box, document_to_viewport);
|
||||||
}
|
}
|
||||||
TransformOperation::Rotating(rotation) => {
|
TransformOperation::Rotating(rotation) => {
|
||||||
let start_offset = *selected.pivot - self.mouse_position;
|
let start_offset = *selected.pivot - self.mouse_position;
|
||||||
|
|
@ -529,33 +522,31 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||||
|
|
||||||
self.transform_operation = TransformOperation::Rotating(rotation.increment_amount(change));
|
self.transform_operation = TransformOperation::Rotating(rotation.increment_amount(change));
|
||||||
self.transform_operation
|
self.transform_operation
|
||||||
.apply_transform_operation(&mut selected, self.increments, self.local, self.fixed_bbox, document_to_viewport);
|
.apply_transform_operation(&mut selected, self.increments, self.local, self.layer_bounding_box, document_to_viewport);
|
||||||
}
|
}
|
||||||
TransformOperation::Scaling(scale) => {
|
TransformOperation::Scaling(mut scale) => {
|
||||||
let axis_constraint = scale.constraint;
|
let axis_constraint = scale.constraint;
|
||||||
let to_mouse_final = self.mouse_position - *selected.pivot;
|
let to_mouse_final = self.mouse_position - *selected.pivot;
|
||||||
let to_mouse_final_old = input.mouse.position - *selected.pivot;
|
let to_mouse_final_old = input.mouse.position - *selected.pivot;
|
||||||
let to_mouse_start = self.start_mouse - *selected.pivot;
|
let to_mouse_start = self.start_mouse - *selected.pivot;
|
||||||
|
|
||||||
let to_mouse_final = project_edge_to_quad(to_mouse_final, &self.fixed_bbox, self.local, axis_constraint);
|
let to_mouse_final = project_edge_to_quad(to_mouse_final, &self.layer_bounding_box, self.local, axis_constraint);
|
||||||
let to_mouse_final_old = project_edge_to_quad(to_mouse_final_old, &self.fixed_bbox, self.local, axis_constraint);
|
let to_mouse_final_old = project_edge_to_quad(to_mouse_final_old, &self.layer_bounding_box, self.local, axis_constraint);
|
||||||
let to_mouse_start = project_edge_to_quad(to_mouse_start, &self.fixed_bbox, self.local, axis_constraint);
|
let to_mouse_start = project_edge_to_quad(to_mouse_start, &self.layer_bounding_box, self.local, axis_constraint);
|
||||||
|
|
||||||
let change = {
|
let change = {
|
||||||
let previous_frame_dist = to_mouse_final.length();
|
let previous_frame_dist = to_mouse_final.dot(to_mouse_start);
|
||||||
let current_frame_dist = to_mouse_final_old.length();
|
let current_frame_dist = to_mouse_final_old.dot(to_mouse_start);
|
||||||
let start_transform_dist = to_mouse_start.length();
|
let start_transform_dist = to_mouse_start.length_squared();
|
||||||
|
|
||||||
(current_frame_dist - previous_frame_dist) / start_transform_dist
|
(current_frame_dist - previous_frame_dist) / start_transform_dist
|
||||||
};
|
};
|
||||||
let change = if self.slow { change / SLOWING_DIVISOR } else { change };
|
let change = if self.slow { change / SLOWING_DIVISOR } else { change };
|
||||||
|
|
||||||
let sign = to_mouse_final.dot(to_mouse_start).signum();
|
scale = scale.increment_amount(change);
|
||||||
let scale = scale.increment_amount(change * scale.to_f64(self.increments).signum());
|
|
||||||
self.transform_operation = TransformOperation::Scaling(scale);
|
self.transform_operation = TransformOperation::Scaling(scale);
|
||||||
|
self.transform_operation
|
||||||
let op = TransformOperation::Scaling(if sign > 0. { scale } else { scale.negate() });
|
.apply_transform_operation(&mut selected, self.increments, self.local, self.layer_bounding_box, document_to_viewport);
|
||||||
op.apply_transform_operation(&mut selected, self.increments, self.local, self.fixed_bbox, document_to_viewport);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -568,30 +559,44 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||||
}
|
}
|
||||||
TransformLayerMessage::TypeBackspace => {
|
TransformLayerMessage::TypeBackspace => {
|
||||||
if self.typing.digits.is_empty() && self.typing.negative {
|
if self.typing.digits.is_empty() && self.typing.negative {
|
||||||
self.transform_operation.negate(&mut selected, self.increments, self.local, self.fixed_bbox, document_to_viewport);
|
self.transform_operation
|
||||||
|
.negate(&mut selected, self.increments, self.local, self.layer_bounding_box, document_to_viewport);
|
||||||
self.typing.type_negate();
|
self.typing.type_negate();
|
||||||
}
|
}
|
||||||
self.transform_operation
|
self.transform_operation
|
||||||
.grs_typed(self.typing.type_backspace(), &mut selected, self.increments, self.local, self.fixed_bbox, document_to_viewport);
|
.grs_typed(self.typing.type_backspace(), &mut selected, self.increments, self.local, self.layer_bounding_box, document_to_viewport);
|
||||||
}
|
}
|
||||||
TransformLayerMessage::TypeDecimalPoint => {
|
TransformLayerMessage::TypeDecimalPoint => {
|
||||||
if self.typing.digits.is_empty() {
|
if self.transform_operation.can_begin_typing() {
|
||||||
self.typing.negative = false;
|
self.transform_operation.grs_typed(
|
||||||
} else {
|
self.typing.type_decimal_point(),
|
||||||
self.transform_operation
|
&mut selected,
|
||||||
.grs_typed(self.typing.type_decimal_point(), &mut selected, self.increments, self.local, self.fixed_bbox, document_to_viewport)
|
self.increments,
|
||||||
|
self.local,
|
||||||
|
self.layer_bounding_box,
|
||||||
|
document_to_viewport,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TransformLayerMessage::TypeDigit { digit } => {
|
TransformLayerMessage::TypeDigit { digit } => {
|
||||||
self.transform_operation
|
if self.transform_operation.can_begin_typing() {
|
||||||
.grs_typed(self.typing.type_number(digit), &mut selected, self.increments, self.local, self.fixed_bbox, document_to_viewport)
|
self.transform_operation.grs_typed(
|
||||||
|
self.typing.type_number(digit),
|
||||||
|
&mut selected,
|
||||||
|
self.increments,
|
||||||
|
self.local,
|
||||||
|
self.layer_bounding_box,
|
||||||
|
document_to_viewport,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
TransformLayerMessage::TypeNegate => {
|
TransformLayerMessage::TypeNegate => {
|
||||||
if self.typing.digits.is_empty() {
|
if self.typing.digits.is_empty() {
|
||||||
self.transform_operation.negate(&mut selected, self.increments, self.local, self.fixed_bbox, document_to_viewport);
|
self.transform_operation
|
||||||
|
.negate(&mut selected, self.increments, self.local, self.layer_bounding_box, document_to_viewport);
|
||||||
}
|
}
|
||||||
self.transform_operation
|
self.transform_operation
|
||||||
.grs_typed(self.typing.type_negate(), &mut selected, self.increments, self.local, self.fixed_bbox, document_to_viewport)
|
.grs_typed(self.typing.type_negate(), &mut selected, self.increments, self.local, self.layer_bounding_box, document_to_viewport)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue