mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-26 03:28:12 +08:00
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:
co-authored by
Keavon Chambers
parent
da752e5324
commit
4de65c292a
@@ -45,7 +45,7 @@ impl OverlayContext {
|
||||
}
|
||||
|
||||
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>) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::prelude::*;
|
||||
@@ -12,7 +12,7 @@ use graphene_core::vector::ManipulatorPointId;
|
||||
use graphene_core::vector::VectorModificationType;
|
||||
use graphene_std::vector::{HandleId, PointId};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
@@ -143,10 +143,13 @@ pub struct 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 document_displacement = if self.constraint == Axis::Y { DVec2::new(0., value) } else { DVec2::new(value, 0.) };
|
||||
transform.transform_vector2(document_displacement)
|
||||
match self.constraint {
|
||||
Axis::X => transform.transform_vector2(DVec2::new(value, 0.)),
|
||||
Axis::Y => transform.transform_vector2(DVec2::new(0., value)),
|
||||
Axis::Both => self.dragged_distance,
|
||||
}
|
||||
} else {
|
||||
match self.constraint {
|
||||
Axis::Both => self.dragged_distance,
|
||||
@@ -155,7 +158,7 @@ impl Translation {
|
||||
}
|
||||
};
|
||||
let displacement = transform.inverse().transform_vector2(displacement);
|
||||
if snap {
|
||||
if increment_mode {
|
||||
displacement.round()
|
||||
} else {
|
||||
displacement
|
||||
@@ -196,12 +199,12 @@ pub struct 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 {
|
||||
value.to_radians()
|
||||
} else if snap {
|
||||
let snap_resolution = ROTATE_SNAP_ANGLE.to_radians();
|
||||
(self.dragged_angle / snap_resolution).round() * snap_resolution
|
||||
} else if increment_mode {
|
||||
let increment_resolution = ROTATE_INCREMENT.to_radians();
|
||||
(self.dragged_angle / increment_resolution).round() * increment_resolution
|
||||
} else {
|
||||
self.dragged_angle
|
||||
}
|
||||
@@ -245,17 +248,17 @@ impl Default for 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 };
|
||||
if snap {
|
||||
(factor / SCALE_SNAP_INTERVAL).round() * SCALE_SNAP_INTERVAL
|
||||
if increment {
|
||||
(factor / SCALE_INCREMENT).round() * SCALE_INCREMENT
|
||||
} else {
|
||||
factor
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_dvec(self, snap: bool) -> DVec2 {
|
||||
let factor = self.to_f64(snap);
|
||||
pub fn to_dvec(self, increment_mode: bool) -> DVec2 {
|
||||
let factor = self.to_f64(increment_mode);
|
||||
|
||||
match self.constraint {
|
||||
Axis::Both => DVec2::splat(factor),
|
||||
@@ -272,7 +275,7 @@ impl Scale {
|
||||
#[must_use]
|
||||
pub fn increment_amount(self, delta: f64) -> Self {
|
||||
Self {
|
||||
dragged_factor: self.dragged_factor + delta,
|
||||
dragged_factor: (self.dragged_factor + delta),
|
||||
typed_factor: None,
|
||||
constraint: self.constraint,
|
||||
}
|
||||
@@ -302,25 +305,29 @@ pub enum TransformOperation {
|
||||
}
|
||||
|
||||
impl TransformOperation {
|
||||
pub fn apply_transform_operation(&self, selected: &mut Selected, snapping: bool, local: bool, quad: Quad, transform: DAffine2) {
|
||||
let quad = quad.0;
|
||||
let edge = quad[1] - quad[0];
|
||||
pub fn apply_transform_operation(&self, selected: &mut Selected, increment_mode: bool, local: bool, quad: Quad, transform: DAffine2) {
|
||||
let local_axis_transform_angle = (quad.top_left() - quad.top_right()).to_angle();
|
||||
if self != &TransformOperation::None {
|
||||
let transformation = match self {
|
||||
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 {
|
||||
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 {
|
||||
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) => {
|
||||
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 {
|
||||
DAffine2::from_scale(scale.to_dvec(snapping))
|
||||
DAffine2::from_scale(scale.to_dvec(increment_mode))
|
||||
}
|
||||
}
|
||||
TransformOperation::None => unreachable!(),
|
||||
@@ -331,16 +338,19 @@ impl TransformOperation {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_typing(&self) -> bool {
|
||||
pub fn axis_constraint(&self) -> Axis {
|
||||
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(),
|
||||
TransformOperation::Grabbing(grabbing) => grabbing.constraint,
|
||||
TransformOperation::Scaling(scaling) => scaling.constraint,
|
||||
_ => Axis::Both,
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
TransformOperation::Grabbing(translation) => {
|
||||
let (translation, local) = translation.with_constraint(axis, local);
|
||||
@@ -352,11 +362,11 @@ impl TransformOperation {
|
||||
}
|
||||
_ => (*self, false),
|
||||
};
|
||||
self.apply_transform_operation(selected, snapping, local, quad, transform);
|
||||
self.apply_transform_operation(selected, increment_mode, local, quad, transform);
|
||||
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 {
|
||||
TransformOperation::None => (),
|
||||
TransformOperation::Grabbing(translation) => translation.typed_distance = typed,
|
||||
@@ -364,45 +374,34 @@ impl TransformOperation {
|
||||
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) {
|
||||
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};
|
||||
|
||||
let mut input_hints = Vec::new();
|
||||
if self.is_typing() {
|
||||
input_hints.push(HintInfo::keys([Key::Minus], "Negate Direction"));
|
||||
input_hints.push(HintInfo::keys([Key::Backspace], "Delete Digit"));
|
||||
input_hints.push(HintInfo::keys([Key::NumKeys], "Enter Number"));
|
||||
} else if matches!(self, TransformOperation::Grabbing(_) | TransformOperation::Scaling(_)) {
|
||||
let axis_constraint = match self {
|
||||
TransformOperation::Grabbing(grabbing) => grabbing.constraint,
|
||||
TransformOperation::Scaling(scaling) => scaling.constraint,
|
||||
_ => Axis::Both,
|
||||
};
|
||||
let clear_constraint = "Clear Constraint";
|
||||
match axis_constraint {
|
||||
Axis::Both => {
|
||||
input_hints.push(HintInfo::keys([Key::KeyX], "Along X Axis"));
|
||||
input_hints.push(HintInfo::keys([Key::KeyY], "Along Y Axis"));
|
||||
let clear_constraint = "Clear Constraint";
|
||||
match self.axis_constraint() {
|
||||
Axis::Both => {
|
||||
input_hints.push(HintInfo::keys([Key::KeyX], "X-Axis Constraint"));
|
||||
input_hints.push(HintInfo::keys([Key::KeyY], "Y-Axis Constraint"));
|
||||
}
|
||||
Axis::X => {
|
||||
let x_label = if local { clear_constraint } else { "Local X-Axis Constraint" };
|
||||
input_hints.push(HintInfo::keys([Key::KeyX], x_label));
|
||||
input_hints.push(HintInfo::keys([Key::KeyY], "Y-Axis Constraint"));
|
||||
if !local {
|
||||
input_hints.push(HintInfo::keys([Key::KeyX, Key::KeyX], clear_constraint));
|
||||
}
|
||||
Axis::X => {
|
||||
let x_label = if local { clear_constraint } else { "Along Local X Axis" };
|
||||
input_hints.push(HintInfo::keys([Key::KeyX], x_label));
|
||||
input_hints.push(HintInfo::keys([Key::KeyY], "Along Y Axis"));
|
||||
if !local {
|
||||
input_hints.push(HintInfo::keys([Key::KeyX, Key::KeyX], 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));
|
||||
}
|
||||
}
|
||||
Axis::Y => {
|
||||
let y_label = if local { clear_constraint } else { "Local Y-Axis Constraint" };
|
||||
input_hints.push(HintInfo::keys([Key::KeyX], "X-Axis Constraint"));
|
||||
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")]),
|
||||
};
|
||||
|
||||
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() {
|
||||
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(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);
|
||||
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 {
|
||||
*self = match self {
|
||||
TransformOperation::Scaling(scale) => TransformOperation::Scaling(scale.negate()),
|
||||
@@ -433,7 +464,7 @@ impl TransformOperation {
|
||||
TransformOperation::Grabbing(translation) => TransformOperation::Grabbing(translation.negate()),
|
||||
_ => *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 {
|
||||
let metadata = self.network_interface.document_metadata();
|
||||
|
||||
let transform = self
|
||||
let mut transform = self
|
||||
.network_interface
|
||||
.selected_nodes(&[])
|
||||
.unwrap()
|
||||
@@ -514,8 +545,8 @@ impl<'a> Selected<'a> {
|
||||
.map(|layer| metadata.transform_to_viewport(layer))
|
||||
.unwrap_or(DAffine2::IDENTITY);
|
||||
|
||||
if transform.matrix2.determinant() == 0. {
|
||||
return Default::default();
|
||||
if transform.matrix2.determinant().abs() <= f64::EPSILON {
|
||||
transform.matrix2 += DMat2::IDENTITY * 1e-4;
|
||||
}
|
||||
|
||||
let bounds = self
|
||||
|
||||
Reference in New Issue
Block a user