Add support for skewing the transform cage by Ctrl-dragging its edges (#2251)
* Skew select bounds with ctrl * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
99a141c2e1
commit
1f836cd2a1
|
|
@ -97,7 +97,7 @@ pub fn input_mappings() -> Mapping {
|
||||||
//
|
//
|
||||||
// SelectToolMessage
|
// SelectToolMessage
|
||||||
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=SelectToolMessage::PointerMove(SelectToolPointerKeys { axis_align: Shift, snap_angle: Control, center: Alt, duplicate: Alt })),
|
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=SelectToolMessage::PointerMove(SelectToolPointerKeys { axis_align: Shift, snap_angle: Control, center: Alt, duplicate: Alt })),
|
||||||
entry!(KeyDown(MouseLeft); action_dispatch=SelectToolMessage::DragStart { extend_selection: Shift, remove_from_selection: Alt, select_deepest: Accel, lasso_select: Control }),
|
entry!(KeyDown(MouseLeft); action_dispatch=SelectToolMessage::DragStart { extend_selection: Shift, remove_from_selection: Alt, select_deepest: Accel, lasso_select: Control, skew: Control }),
|
||||||
entry!(KeyUp(MouseLeft); action_dispatch=SelectToolMessage::DragStop { remove_from_selection: Alt }),
|
entry!(KeyUp(MouseLeft); action_dispatch=SelectToolMessage::DragStop { remove_from_selection: Alt }),
|
||||||
entry!(KeyDown(Enter); action_dispatch=SelectToolMessage::Enter),
|
entry!(KeyDown(Enter); action_dispatch=SelectToolMessage::Enter),
|
||||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=SelectToolMessage::EditLayer),
|
entry!(DoubleClick(MouseButton::Left); action_dispatch=SelectToolMessage::EditLayer),
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration
|
||||||
use graphene_core::renderer::Quad;
|
use graphene_core::renderer::Quad;
|
||||||
use graphene_std::renderer::Rect;
|
use graphene_std::renderer::Rect;
|
||||||
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
|
|
||||||
use super::snapping::{self, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnappedPoint};
|
use super::snapping::{self, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnappedPoint};
|
||||||
|
|
||||||
|
|
@ -241,6 +241,42 @@ impl SelectedEdges {
|
||||||
}
|
}
|
||||||
(DAffine2::from_scale(enlargement_factor), pivot)
|
(DAffine2::from_scale(enlargement_factor), pivot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn skew_transform(&self, mouse: DVec2, to_viewport_transform: DAffine2) -> DAffine2 {
|
||||||
|
// Skip if the matrix is singular (as it isn't really possible to skew).
|
||||||
|
if !to_viewport_transform.matrix2.determinant().recip().is_finite() {
|
||||||
|
return DAffine2::IDENTITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
let opposite = self.pivot_from_bounds(self.bounds[0], self.bounds[1]);
|
||||||
|
// This is the current handle that goes under the mouse.
|
||||||
|
let dragging_point = self.pivot_from_bounds(self.bounds[1], self.bounds[0]);
|
||||||
|
|
||||||
|
let mut new_dragging_point = to_viewport_transform.transform_point2(dragging_point);
|
||||||
|
let parallel_to_x = self.top || self.bottom;
|
||||||
|
let parallel_to_y = !parallel_to_x && (self.left || self.right);
|
||||||
|
|
||||||
|
// The target point is the projection in viewport space onto the line that the skew is parallel to.
|
||||||
|
if parallel_to_x {
|
||||||
|
new_dragging_point += (mouse - new_dragging_point).project_onto(to_viewport_transform.transform_vector2(DVec2::X));
|
||||||
|
} else if parallel_to_y {
|
||||||
|
new_dragging_point += (mouse - new_dragging_point).project_onto(to_viewport_transform.transform_vector2(DVec2::Y));
|
||||||
|
}
|
||||||
|
new_dragging_point = to_viewport_transform.inverse().transform_point2(new_dragging_point);
|
||||||
|
|
||||||
|
let movement = new_dragging_point - dragging_point;
|
||||||
|
|
||||||
|
// Produce a skew that moves the dragging point to the new dragging point (assuming the opposite is origin).
|
||||||
|
let skew = DAffine2::from_mat2(DMat2::from_cols_array(&[
|
||||||
|
1.,
|
||||||
|
if parallel_to_y { movement.y / (dragging_point - opposite).x } else { 0. },
|
||||||
|
if parallel_to_x { movement.x / (dragging_point - opposite).y } else { 0. },
|
||||||
|
1.,
|
||||||
|
]));
|
||||||
|
|
||||||
|
// Combine that with a transform that makes opposite the origin.
|
||||||
|
DAffine2::from_translation(opposite) * skew * DAffine2::from_translation(-opposite)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Aligns the mouse position to the closest axis
|
/// Aligns the mouse position to the closest axis
|
||||||
|
|
@ -497,3 +533,52 @@ impl BoundingBoxManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn skew_transform_singular() {
|
||||||
|
for edge in [
|
||||||
|
SelectedEdges::new(true, false, false, false, [DVec2::NEG_ONE, DVec2::ONE]),
|
||||||
|
SelectedEdges::new(false, true, false, false, [DVec2::NEG_ONE, DVec2::ONE]),
|
||||||
|
SelectedEdges::new(false, false, true, false, [DVec2::NEG_ONE, DVec2::ONE]),
|
||||||
|
SelectedEdges::new(false, false, false, true, [DVec2::NEG_ONE, DVec2::ONE]),
|
||||||
|
] {
|
||||||
|
// The determinant is 0.
|
||||||
|
let transform = DAffine2::from_cols_array(&[2.; 6]);
|
||||||
|
// This shouldn't panic. We don't really care about the behavior in this test.
|
||||||
|
let _ = edge.skew_transform(DVec2::new(1.5, 1.5), transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn skew_transform_correct() {
|
||||||
|
for edge in [
|
||||||
|
SelectedEdges::new(true, false, false, false, [DVec2::NEG_ONE, DVec2::ONE]),
|
||||||
|
SelectedEdges::new(false, true, false, false, [DVec2::NEG_ONE, DVec2::ONE]),
|
||||||
|
SelectedEdges::new(false, false, true, false, [DVec2::NEG_ONE, DVec2::ONE]),
|
||||||
|
SelectedEdges::new(false, false, false, true, [DVec2::NEG_ONE, DVec2::ONE]),
|
||||||
|
] {
|
||||||
|
// Random transform with det != 0.
|
||||||
|
let to_viewport_transform = DAffine2::from_cols_array(&[2., 1., 0., 1., 2., 3.]);
|
||||||
|
// Random mouse position.
|
||||||
|
let mouse = DVec2::new(1.5, 1.5);
|
||||||
|
let final_transform = edge.skew_transform(mouse, to_viewport_transform);
|
||||||
|
|
||||||
|
// This is the current handle that goes under the mouse.
|
||||||
|
let dragging_point = edge.pivot_from_bounds(edge.bounds[1], edge.bounds[0]);
|
||||||
|
|
||||||
|
let parallel_to_x = edge.top || edge.bottom;
|
||||||
|
let parallel_to_y = !parallel_to_x && (edge.left || edge.right);
|
||||||
|
|
||||||
|
// The target point is the projection in viewport space onto the line that the skew is parallel to.
|
||||||
|
let mut target_dragging_point = to_viewport_transform.transform_point2(dragging_point);
|
||||||
|
if parallel_to_x {
|
||||||
|
target_dragging_point += (mouse - target_dragging_point).project_onto(to_viewport_transform.transform_vector2(DVec2::X));
|
||||||
|
} else if parallel_to_y {
|
||||||
|
target_dragging_point += (mouse - target_dragging_point).project_onto(to_viewport_transform.transform_vector2(DVec2::Y));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the final point in viewport space.
|
||||||
|
let final_dragging_point = to_viewport_transform.transform_point2(final_transform.transform_point2(dragging_point));
|
||||||
|
assert_eq!(final_dragging_point, target_dragging_point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,7 @@ pub enum SelectToolMessage {
|
||||||
remove_from_selection: Key,
|
remove_from_selection: Key,
|
||||||
select_deepest: Key,
|
select_deepest: Key,
|
||||||
lasso_select: Key,
|
lasso_select: Key,
|
||||||
|
skew: Key,
|
||||||
},
|
},
|
||||||
DragStop {
|
DragStop {
|
||||||
remove_from_selection: Key,
|
remove_from_selection: Key,
|
||||||
|
|
@ -274,6 +275,7 @@ enum SelectToolFsmState {
|
||||||
Drawing { selection_shape: SelectionShapeType },
|
Drawing { selection_shape: SelectionShapeType },
|
||||||
Dragging,
|
Dragging,
|
||||||
ResizingBounds,
|
ResizingBounds,
|
||||||
|
SkewingBounds,
|
||||||
RotatingBounds,
|
RotatingBounds,
|
||||||
DraggingPivot,
|
DraggingPivot,
|
||||||
}
|
}
|
||||||
|
|
@ -624,6 +626,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
remove_from_selection,
|
remove_from_selection,
|
||||||
select_deepest,
|
select_deepest,
|
||||||
lasso_select,
|
lasso_select,
|
||||||
|
skew,
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
tool_data.drag_start = input.mouse.position;
|
tool_data.drag_start = input.mouse.position;
|
||||||
|
|
@ -705,7 +708,11 @@ impl Fsm for SelectToolFsmState {
|
||||||
}
|
}
|
||||||
tool_data.get_snap_candidates(document, input);
|
tool_data.get_snap_candidates(document, input);
|
||||||
|
|
||||||
SelectToolFsmState::ResizingBounds
|
if input.keyboard.key(skew) {
|
||||||
|
SelectToolFsmState::SkewingBounds
|
||||||
|
}else{
|
||||||
|
SelectToolFsmState::ResizingBounds
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Dragging near the transform cage bounding box to rotate it
|
// Dragging near the transform cage bounding box to rotate it
|
||||||
else if rotating_bounds {
|
else if rotating_bounds {
|
||||||
|
|
@ -877,6 +884,37 @@ impl Fsm for SelectToolFsmState {
|
||||||
}
|
}
|
||||||
SelectToolFsmState::ResizingBounds
|
SelectToolFsmState::ResizingBounds
|
||||||
}
|
}
|
||||||
|
(SelectToolFsmState::SkewingBounds, SelectToolMessage::PointerMove(_)) => {
|
||||||
|
if let Some(ref mut bounds) = &mut tool_data.bounding_box_manager {
|
||||||
|
if let Some(movement) = &mut bounds.selected_edges {
|
||||||
|
let transformation = movement.skew_transform(input.mouse.position, bounds.original_bound_transform);
|
||||||
|
|
||||||
|
tool_data.layers_dragging.retain(|layer| {
|
||||||
|
if *layer != LayerNodeIdentifier::ROOT_PARENT {
|
||||||
|
document.network_interface.network(&[]).unwrap().nodes.contains_key(&layer.to_node())
|
||||||
|
} else {
|
||||||
|
log::error!("ROOT_PARENT should not be part of layers_dragging");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let selected = &tool_data.layers_dragging;
|
||||||
|
let mut pivot = DVec2::ZERO;
|
||||||
|
let mut selected = Selected::new(
|
||||||
|
&mut bounds.original_transforms,
|
||||||
|
&mut pivot,
|
||||||
|
selected,
|
||||||
|
responses,
|
||||||
|
&document.network_interface,
|
||||||
|
None,
|
||||||
|
&ToolType::Select,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
selected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SelectToolFsmState::SkewingBounds
|
||||||
|
}
|
||||||
(SelectToolFsmState::RotatingBounds, SelectToolMessage::PointerMove(modifier_keys)) => {
|
(SelectToolFsmState::RotatingBounds, SelectToolMessage::PointerMove(modifier_keys)) => {
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||||
let angle = {
|
let angle = {
|
||||||
|
|
@ -978,7 +1016,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
|
|
||||||
SelectToolFsmState::Dragging
|
SelectToolFsmState::Dragging
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerOutsideViewport(_)) => {
|
(SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds, SelectToolMessage::PointerOutsideViewport(_)) => {
|
||||||
// AutoPanning
|
// AutoPanning
|
||||||
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
|
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
|
||||||
if let Some(ref mut bounds) = &mut tool_data.bounding_box_manager {
|
if let Some(ref mut bounds) = &mut tool_data.bounding_box_manager {
|
||||||
|
|
@ -1081,7 +1119,7 @@ impl Fsm for SelectToolFsmState {
|
||||||
let selection = tool_data.nested_selection_behavior;
|
let selection = tool_data.nested_selection_behavior;
|
||||||
SelectToolFsmState::Ready { selection }
|
SelectToolFsmState::Ready { selection }
|
||||||
}
|
}
|
||||||
(SelectToolFsmState::ResizingBounds, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
|
(SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
|
||||||
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
|
||||||
true => DocumentMessage::AbortTransaction,
|
true => DocumentMessage::AbortTransaction,
|
||||||
false => DocumentMessage::EndTransaction,
|
false => DocumentMessage::EndTransaction,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue