Skip to content

Commit

Permalink
Improve nudging when tilted and add Artboard tool nudge resizing; dis…
Browse files Browse the repository at this point in the history
…able menu bar entries when no layer is selected (GraphiteEditor#2098)

* Make nudging follow a tilted viewport

* Add artboard nudge resizing
  • Loading branch information
Keavon authored Nov 9, 2024
1 parent 320d030 commit 4576197
Show file tree
Hide file tree
Showing 12 changed files with 240 additions and 118 deletions.
48 changes: 24 additions & 24 deletions editor/src/messages/input_mapper/input_mappings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,30 +109,30 @@ pub fn input_mappings() -> Mapping {
entry!(KeyUp(MouseLeft); action_dispatch=ArtboardToolMessage::PointerUp),
entry!(KeyDown(Delete); action_dispatch=ArtboardToolMessage::DeleteSelected),
entry!(KeyDown(Backspace); action_dispatch=ArtboardToolMessage::DeleteSelected),
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT }),
entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
entry!(KeyDown(ArrowDown); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT }),
entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
entry!(KeyDown(ArrowLeft); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0. }),
entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT }),
entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT }),
entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }),
entry!(KeyDown(ArrowUp); modifiers=[ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
entry!(KeyDown(ArrowUp); modifiers=[ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
entry!(KeyDown(ArrowUp); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: -NUDGE_AMOUNT }),
entry!(KeyDown(ArrowDown); modifiers=[ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
entry!(KeyDown(ArrowDown); modifiers=[ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
entry!(KeyDown(ArrowDown); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: NUDGE_AMOUNT }),
entry!(KeyDown(ArrowLeft); modifiers=[ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
entry!(KeyDown(ArrowLeft); modifiers=[ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
entry!(KeyDown(ArrowLeft); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: 0. }),
entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
entry!(KeyDown(ArrowRight); modifiers=[ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT }),
entry!(KeyDown(ArrowRight); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: 0. }),
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowDown); modifiers=[Shift, ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowDown); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowLeft); modifiers=[Shift, ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowLeft); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -BIG_NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowRight); modifiers=[Shift, ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowUp); modifiers=[ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowUp); modifiers=[ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowUp); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowDown); modifiers=[ArrowLeft], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowDown); modifiers=[ArrowRight], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowDown); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: 0., delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowLeft); modifiers=[ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowLeft); modifiers=[ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowLeft); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: -NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowRight); modifiers=[ArrowDown], action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(ArrowRight); action_dispatch=ArtboardToolMessage::NudgeSelected { delta_x: NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),
entry!(KeyDown(MouseRight); action_dispatch=ArtboardToolMessage::Abort),
entry!(KeyDown(Escape); action_dispatch=ArtboardToolMessage::Abort),
//
Expand Down
123 changes: 69 additions & 54 deletions editor/src/messages/portfolio/document/document_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -650,62 +650,77 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
} => {
responses.add(DocumentMessage::AddTransaction);

let opposite_corner = ipp.keyboard.key(resize_opposite_corner);
let delta = DVec2::new(delta_x, delta_y);
let network_interface = &self.network_interface;
let can_move = move |layer| {
network_interface
let resize = ipp.keyboard.key(resize);
let resize_opposite_corner = ipp.keyboard.key(resize_opposite_corner);

let can_move = |layer| {
self.network_interface
.selected_nodes(&[])
.is_some_and(|selected| selected.layer_visible(layer, network_interface) && !selected.layer_locked(layer, network_interface))
.is_some_and(|selected| selected.layer_visible(layer, &self.network_interface) && !selected.layer_locked(layer, &self.network_interface))
};

match ipp.keyboard.key(resize) {
// Nudge translation
false => {
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
responses.add(GraphOperationMessage::TransformChange {
layer,
transform: DAffine2::from_translation(delta),
transform_in: TransformIn::Local,
skip_rerender: false,
});
}
}
// Nudge resize
true => {
let selected_bounding_box = self.network_interface.selected_bounds_document_space(false, &[]);
let Some([existing_top_left, existing_bottom_right]) = selected_bounding_box else { return };

let size = existing_bottom_right - existing_top_left;
let new_size = size + if opposite_corner { -delta } else { delta };
let enlargement_factor = new_size / size;

let position = existing_top_left + if opposite_corner { delta } else { DVec2::ZERO };
let mut pivot = (existing_top_left * enlargement_factor - position) / (enlargement_factor - DVec2::splat(1.));
if !pivot.x.is_finite() {
pivot.x = 0.;
}
if !pivot.y.is_finite() {
pivot.y = 0.;
}
// Nudge translation without resizing
if !resize {
let transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y)));

let scale = DAffine2::from_scale(enlargement_factor);
let pivot = DAffine2::from_translation(pivot);
let transformation = pivot * scale * pivot.inverse();
let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);

for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
let to = document_to_viewport.inverse() * self.metadata().downstream_transform_to_viewport(layer);
let original_transform = self.metadata().upstream_transform(layer.to_node());
let new = to.inverse() * transformation * to * original_transform;
responses.add(GraphOperationMessage::TransformSet {
layer,
transform: new,
transform_in: TransformIn::Local,
skip_rerender: false,
});
}
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
responses.add(GraphOperationMessage::TransformChange {
layer,
transform,
transform_in: TransformIn::Local,
skip_rerender: false,
});
}

return;
}

let selected_bounding_box = self.network_interface.selected_bounds_document_space(false, &[]);
let Some([existing_top_left, existing_bottom_right]) = selected_bounding_box else { return };

// Swap and negate coordinates as needed to match the resize direction that's closest to the current tilt angle
let tilt = (self.document_ptz.tilt() + std::f64::consts::TAU) % std::f64::consts::TAU;
let (delta_x, delta_y, opposite_x, opposite_y) = match ((tilt + std::f64::consts::FRAC_PI_4) / std::f64::consts::FRAC_PI_2).floor() as i32 % 4 {
0 => (delta_x, delta_y, false, false),
1 => (delta_y, -delta_x, false, true),
2 => (-delta_x, -delta_y, true, true),
3 => (-delta_y, delta_x, true, false),
_ => unreachable!(),
};

let size = existing_bottom_right - existing_top_left;
let enlargement = DVec2::new(
if resize_opposite_corner != opposite_x { -delta_x } else { delta_x },
if resize_opposite_corner != opposite_y { -delta_y } else { delta_y },
);
let enlargement_factor = (enlargement + size) / size;

let position = DVec2::new(
existing_top_left.x + if resize_opposite_corner != opposite_x { delta_x } else { 0. },
existing_top_left.y + if resize_opposite_corner != opposite_y { delta_y } else { 0. },
);
let mut pivot = (existing_top_left * enlargement_factor - position) / (enlargement_factor - DVec2::ONE);
if !pivot.x.is_finite() {
pivot.x = 0.;
}
if !pivot.y.is_finite() {
pivot.y = 0.;
}
let scale = DAffine2::from_scale(enlargement_factor);
let pivot = DAffine2::from_translation(pivot);
let transformation = pivot * scale * pivot.inverse();
let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);

for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
let to = document_to_viewport.inverse() * self.metadata().downstream_transform_to_viewport(layer);
let original_transform = self.metadata().upstream_transform(layer.to_node());
let new = to.inverse() * transformation * to * original_transform;
responses.add(GraphOperationMessage::TransformSet {
layer,
transform: new,
transform_in: TransformIn::Local,
skip_rerender: false,
});
}
}
DocumentMessage::PasteImage {
Expand Down Expand Up @@ -1814,7 +1829,7 @@ impl DocumentMessageHandler {

widgets.extend(navigation_controls(&self.document_ptz, &self.navigation_handler, "Canvas"));

let tilt_value = self.navigation_handler.snapped_tilt(self.document_ptz.tilt) / (std::f64::consts::PI / 180.);
let tilt_value = self.navigation_handler.snapped_tilt(self.document_ptz.tilt()) / (std::f64::consts::PI / 180.);
if tilt_value.abs() > 0.00001 {
widgets.extend([
Separator::new(SeparatorType::Related).widget_holder(),
Expand All @@ -1835,7 +1850,7 @@ impl DocumentMessageHandler {
}
.into()
})
.tooltip("Document tilt within the viewport")
.tooltip("Canvas Tilt")
.on_update(|number_input: &NumberInput| {
NavigationMessage::CanvasTiltSet {
angle_radians: number_input.value.unwrap().to_radians(),
Expand Down Expand Up @@ -2178,7 +2193,7 @@ pub fn navigation_controls(ptz: &PTZ, navigation_handler: &NavigationMessageHand
.tooltip("Reset Tilt and Zoom to 100%")
.tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasTiltResetAndZoomTo100Percent))
.on_update(|_| NavigationMessage::CanvasTiltResetAndZoomTo100Percent.into())
.disabled(ptz.tilt.abs() < 1e-4 && (ptz.zoom() - 1.).abs() < 1e-4)
.disabled(ptz.tilt().abs() < 1e-4 && (ptz.zoom() - 1.).abs() < 1e-4)
.widget_holder(),
PopoverButton::new()
.popover_layout(vec![
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
});

self.navigation_operation = NavigationOperation::Tilt {
tilt_original_for_abort: ptz.tilt,
tilt_raw_not_snapped: ptz.tilt,
tilt_original_for_abort: ptz.tilt(),
tilt_raw_not_snapped: ptz.tilt(),
snap: false,
};

Expand Down Expand Up @@ -179,7 +179,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
log::error!("Could not get mutable PTZ in CanvasTiltResetAndZoomTo100Percent");
return;
};
ptz.tilt = 0.;
ptz.set_tilt(0.);
ptz.set_zoom(1.);
if graph_view_overlay_open {
responses.add(NodeGraphMessage::UpdateGraphBarRight);
Expand All @@ -194,7 +194,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
log::error!("Could not get mutable PTZ in CanvasTiltSet");
return;
};
ptz.tilt = angle_radians;
ptz.set_tilt(angle_radians);
responses.add(DocumentMessage::PTZUpdate);
if !graph_view_overlay_open {
responses.add(PortfolioMessage::UpdateDocumentWidgets);
Expand Down Expand Up @@ -277,7 +277,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
match self.navigation_operation {
NavigationOperation::None => {}
NavigationOperation::Tilt { tilt_original_for_abort, .. } => {
ptz.tilt = tilt_original_for_abort;
ptz.set_tilt(tilt_original_for_abort);
}
NavigationOperation::Pan { pan_original_for_abort, .. } => {
ptz.pan = pan_original_for_abort;
Expand All @@ -289,7 +289,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
}

// Final chance to apply snapping if the key was pressed during this final frame
ptz.tilt = self.snapped_tilt(ptz.tilt);
ptz.set_tilt(self.snapped_tilt(ptz.tilt()));
ptz.set_zoom(self.snapped_zoom(ptz.zoom()));
responses.add(DocumentMessage::PTZUpdate);
if graph_view_overlay_open {
Expand Down Expand Up @@ -397,7 +397,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
log::error!("Could not get mutable PTZ in Tilt");
return;
};
ptz.tilt = self.snapped_tilt(tilt_raw_not_snapped);
ptz.set_tilt(self.snapped_tilt(tilt_raw_not_snapped));

let snap = ipp.keyboard.get(snap as usize);

Expand All @@ -407,7 +407,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
snap,
};

responses.add(NavigationMessage::CanvasTiltSet { angle_radians: ptz.tilt });
responses.add(NavigationMessage::CanvasTiltSet { angle_radians: ptz.tilt() });
}
NavigationOperation::Zoom {
zoom_raw_not_snapped,
Expand Down Expand Up @@ -503,7 +503,7 @@ impl NavigationMessageHandler {

pub fn calculate_offset_transform(&self, viewport_center: DVec2, ptz: &PTZ) -> DAffine2 {
let pan = ptz.pan;
let tilt = ptz.tilt;
let tilt = ptz.tilt();
let zoom = ptz.zoom();

let scaled_center = viewport_center / self.snapped_zoom(zoom);
Expand Down
Loading

0 comments on commit 4576197

Please sign in to comment.