Improve nudging when tilted and add Artboard tool nudge resizing; disable menu bar entries when no layer is selected (#2098)
* Make nudging follow a tilted viewport * Add artboard nudge resizing
This commit is contained in:
parent
320d030c08
commit
457619794b
|
|
@ -109,30 +109,30 @@ pub fn input_mappings() -> Mapping {
|
||||||
entry!(KeyUp(MouseLeft); action_dispatch=ArtboardToolMessage::PointerUp),
|
entry!(KeyUp(MouseLeft); action_dispatch=ArtboardToolMessage::PointerUp),
|
||||||
entry!(KeyDown(Delete); action_dispatch=ArtboardToolMessage::DeleteSelected),
|
entry!(KeyDown(Delete); action_dispatch=ArtboardToolMessage::DeleteSelected),
|
||||||
entry!(KeyDown(Backspace); 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, 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 }),
|
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 }),
|
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 }),
|
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 }),
|
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 }),
|
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 }),
|
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 }),
|
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. }),
|
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 }),
|
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 }),
|
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. }),
|
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 }),
|
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 }),
|
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 }),
|
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 }),
|
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 }),
|
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 }),
|
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 }),
|
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 }),
|
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. }),
|
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 }),
|
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 }),
|
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. }),
|
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(MouseRight); action_dispatch=ArtboardToolMessage::Abort),
|
||||||
entry!(KeyDown(Escape); action_dispatch=ArtboardToolMessage::Abort),
|
entry!(KeyDown(Escape); action_dispatch=ArtboardToolMessage::Abort),
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -650,62 +650,77 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
||||||
} => {
|
} => {
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
|
|
||||||
let opposite_corner = ipp.keyboard.key(resize_opposite_corner);
|
let resize = ipp.keyboard.key(resize);
|
||||||
let delta = DVec2::new(delta_x, delta_y);
|
let resize_opposite_corner = ipp.keyboard.key(resize_opposite_corner);
|
||||||
let network_interface = &self.network_interface;
|
|
||||||
let can_move = move |layer| {
|
let can_move = |layer| {
|
||||||
network_interface
|
self.network_interface
|
||||||
.selected_nodes(&[])
|
.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 without resizing
|
||||||
// Nudge translation
|
if !resize {
|
||||||
false => {
|
let transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y)));
|
||||||
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
|
|
||||||
responses.add(GraphOperationMessage::TransformChange {
|
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
|
||||||
layer,
|
responses.add(GraphOperationMessage::TransformChange {
|
||||||
transform: DAffine2::from_translation(delta),
|
layer,
|
||||||
transform_in: TransformIn::Local,
|
transform,
|
||||||
skip_rerender: false,
|
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;
|
return;
|
||||||
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 selected_bounding_box = self.network_interface.selected_bounds_document_space(false, &[]);
|
||||||
let mut pivot = (existing_top_left * enlargement_factor - position) / (enlargement_factor - DVec2::splat(1.));
|
let Some([existing_top_left, existing_bottom_right]) = selected_bounding_box else { return };
|
||||||
if !pivot.x.is_finite() {
|
|
||||||
pivot.x = 0.;
|
|
||||||
}
|
|
||||||
if !pivot.y.is_finite() {
|
|
||||||
pivot.y = 0.;
|
|
||||||
}
|
|
||||||
|
|
||||||
let scale = DAffine2::from_scale(enlargement_factor);
|
// Swap and negate coordinates as needed to match the resize direction that's closest to the current tilt angle
|
||||||
let pivot = DAffine2::from_translation(pivot);
|
let tilt = (self.document_ptz.tilt() + std::f64::consts::TAU) % std::f64::consts::TAU;
|
||||||
let transformation = pivot * scale * pivot.inverse();
|
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 {
|
||||||
let document_to_viewport = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
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!(),
|
||||||
|
};
|
||||||
|
|
||||||
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
|
let size = existing_bottom_right - existing_top_left;
|
||||||
let to = document_to_viewport.inverse() * self.metadata().downstream_transform_to_viewport(layer);
|
let enlargement = DVec2::new(
|
||||||
let original_transform = self.metadata().upstream_transform(layer.to_node());
|
if resize_opposite_corner != opposite_x { -delta_x } else { delta_x },
|
||||||
let new = to.inverse() * transformation * to * original_transform;
|
if resize_opposite_corner != opposite_y { -delta_y } else { delta_y },
|
||||||
responses.add(GraphOperationMessage::TransformSet {
|
);
|
||||||
layer,
|
let enlargement_factor = (enlargement + size) / size;
|
||||||
transform: new,
|
|
||||||
transform_in: TransformIn::Local,
|
let position = DVec2::new(
|
||||||
skip_rerender: false,
|
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 {
|
DocumentMessage::PasteImage {
|
||||||
|
|
@ -1814,7 +1829,7 @@ impl DocumentMessageHandler {
|
||||||
|
|
||||||
widgets.extend(navigation_controls(&self.document_ptz, &self.navigation_handler, "Canvas"));
|
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 {
|
if tilt_value.abs() > 0.00001 {
|
||||||
widgets.extend([
|
widgets.extend([
|
||||||
Separator::new(SeparatorType::Related).widget_holder(),
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
|
|
@ -1835,7 +1850,7 @@ impl DocumentMessageHandler {
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
.tooltip("Document tilt within the viewport")
|
.tooltip("Canvas Tilt")
|
||||||
.on_update(|number_input: &NumberInput| {
|
.on_update(|number_input: &NumberInput| {
|
||||||
NavigationMessage::CanvasTiltSet {
|
NavigationMessage::CanvasTiltSet {
|
||||||
angle_radians: number_input.value.unwrap().to_radians(),
|
angle_radians: number_input.value.unwrap().to_radians(),
|
||||||
|
|
@ -2178,7 +2193,7 @@ pub fn navigation_controls(ptz: &PTZ, navigation_handler: &NavigationMessageHand
|
||||||
.tooltip("Reset Tilt and Zoom to 100%")
|
.tooltip("Reset Tilt and Zoom to 100%")
|
||||||
.tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasTiltResetAndZoomTo100Percent))
|
.tooltip_shortcut(action_keys!(NavigationMessageDiscriminant::CanvasTiltResetAndZoomTo100Percent))
|
||||||
.on_update(|_| NavigationMessage::CanvasTiltResetAndZoomTo100Percent.into())
|
.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(),
|
.widget_holder(),
|
||||||
PopoverButton::new()
|
PopoverButton::new()
|
||||||
.popover_layout(vec![
|
.popover_layout(vec![
|
||||||
|
|
|
||||||
|
|
@ -107,8 +107,8 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||||
});
|
});
|
||||||
|
|
||||||
self.navigation_operation = NavigationOperation::Tilt {
|
self.navigation_operation = NavigationOperation::Tilt {
|
||||||
tilt_original_for_abort: ptz.tilt,
|
tilt_original_for_abort: ptz.tilt(),
|
||||||
tilt_raw_not_snapped: ptz.tilt,
|
tilt_raw_not_snapped: ptz.tilt(),
|
||||||
snap: false,
|
snap: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -179,7 +179,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||||
log::error!("Could not get mutable PTZ in CanvasTiltResetAndZoomTo100Percent");
|
log::error!("Could not get mutable PTZ in CanvasTiltResetAndZoomTo100Percent");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
ptz.tilt = 0.;
|
ptz.set_tilt(0.);
|
||||||
ptz.set_zoom(1.);
|
ptz.set_zoom(1.);
|
||||||
if graph_view_overlay_open {
|
if graph_view_overlay_open {
|
||||||
responses.add(NodeGraphMessage::UpdateGraphBarRight);
|
responses.add(NodeGraphMessage::UpdateGraphBarRight);
|
||||||
|
|
@ -194,7 +194,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||||
log::error!("Could not get mutable PTZ in CanvasTiltSet");
|
log::error!("Could not get mutable PTZ in CanvasTiltSet");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
ptz.tilt = angle_radians;
|
ptz.set_tilt(angle_radians);
|
||||||
responses.add(DocumentMessage::PTZUpdate);
|
responses.add(DocumentMessage::PTZUpdate);
|
||||||
if !graph_view_overlay_open {
|
if !graph_view_overlay_open {
|
||||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||||
|
|
@ -277,7 +277,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||||
match self.navigation_operation {
|
match self.navigation_operation {
|
||||||
NavigationOperation::None => {}
|
NavigationOperation::None => {}
|
||||||
NavigationOperation::Tilt { tilt_original_for_abort, .. } => {
|
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, .. } => {
|
NavigationOperation::Pan { pan_original_for_abort, .. } => {
|
||||||
ptz.pan = pan_original_for_abort;
|
ptz.pan = pan_original_for_abort;
|
||||||
|
|
@ -289,7 +289,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final chance to apply snapping if the key was pressed during this final frame
|
// 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()));
|
ptz.set_zoom(self.snapped_zoom(ptz.zoom()));
|
||||||
responses.add(DocumentMessage::PTZUpdate);
|
responses.add(DocumentMessage::PTZUpdate);
|
||||||
if graph_view_overlay_open {
|
if graph_view_overlay_open {
|
||||||
|
|
@ -397,7 +397,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||||
log::error!("Could not get mutable PTZ in Tilt");
|
log::error!("Could not get mutable PTZ in Tilt");
|
||||||
return;
|
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);
|
let snap = ipp.keyboard.get(snap as usize);
|
||||||
|
|
||||||
|
|
@ -407,7 +407,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
||||||
snap,
|
snap,
|
||||||
};
|
};
|
||||||
|
|
||||||
responses.add(NavigationMessage::CanvasTiltSet { angle_radians: ptz.tilt });
|
responses.add(NavigationMessage::CanvasTiltSet { angle_radians: ptz.tilt() });
|
||||||
}
|
}
|
||||||
NavigationOperation::Zoom {
|
NavigationOperation::Zoom {
|
||||||
zoom_raw_not_snapped,
|
zoom_raw_not_snapped,
|
||||||
|
|
@ -503,7 +503,7 @@ impl NavigationMessageHandler {
|
||||||
|
|
||||||
pub fn calculate_offset_transform(&self, viewport_center: DVec2, ptz: &PTZ) -> DAffine2 {
|
pub fn calculate_offset_transform(&self, viewport_center: DVec2, ptz: &PTZ) -> DAffine2 {
|
||||||
let pan = ptz.pan;
|
let pan = ptz.pan;
|
||||||
let tilt = ptz.tilt;
|
let tilt = ptz.tilt();
|
||||||
let zoom = ptz.zoom();
|
let zoom = ptz.zoom();
|
||||||
|
|
||||||
let scaled_center = viewport_center / self.snapped_zoom(zoom);
|
let scaled_center = viewport_center / self.snapped_zoom(zoom);
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||||
new_layer: selected_layers.first().cloned(),
|
new_layer: selected_layers.first().cloned(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
responses.add(MenuBarMessage::SendLayout);
|
||||||
responses.add(NodeGraphMessage::UpdateLayerPanel);
|
responses.add(NodeGraphMessage::UpdateLayerPanel);
|
||||||
responses.add(NodeGraphMessage::SendSelectedNodes);
|
responses.add(NodeGraphMessage::SendSelectedNodes);
|
||||||
responses.add(ArtboardToolMessage::UpdateSelectedArtboard);
|
responses.add(ArtboardToolMessage::UpdateSelectedArtboard);
|
||||||
|
|
@ -1701,6 +1702,7 @@ impl NodeGraphMessageHandler {
|
||||||
IconLabel::new("File").tooltip("Name of the current document").widget_holder(),
|
IconLabel::new("File").tooltip("Name of the current document").widget_holder(),
|
||||||
Separator::new(SeparatorType::Related).widget_holder(),
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
TextInput::new(context.document_name)
|
TextInput::new(context.document_name)
|
||||||
|
.tooltip("Name of the current document")
|
||||||
.on_update(|text_input| DocumentMessage::RenameDocument { new_name: text_input.value.clone() }.into())
|
.on_update(|text_input| DocumentMessage::RenameDocument { new_name: text_input.value.clone() }.into())
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
Separator::new(SeparatorType::Related).widget_holder(),
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
|
|
@ -1748,6 +1750,7 @@ impl NodeGraphMessageHandler {
|
||||||
IconLabel::new("Layer").tooltip("Name of the selected layer").widget_holder(),
|
IconLabel::new("Layer").tooltip("Name of the selected layer").widget_holder(),
|
||||||
Separator::new(SeparatorType::Related).widget_holder(),
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
TextInput::new(context.network_interface.frontend_display_name(&layer, context.selection_network_path))
|
TextInput::new(context.network_interface.frontend_display_name(&layer, context.selection_network_path))
|
||||||
|
.tooltip("Name of the selected layer")
|
||||||
.on_update(move |text_input| {
|
.on_update(move |text_input| {
|
||||||
NodeGraphMessage::SetDisplayName {
|
NodeGraphMessage::SetDisplayName {
|
||||||
node_id: layer,
|
node_id: layer,
|
||||||
|
|
|
||||||
|
|
@ -444,8 +444,11 @@ impl fmt::Display for SnappingOptions {
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct PTZ {
|
pub struct PTZ {
|
||||||
|
/// Offset distance.
|
||||||
pub pan: DVec2,
|
pub pan: DVec2,
|
||||||
pub tilt: f64,
|
/// Angle in radians.
|
||||||
|
tilt: f64,
|
||||||
|
/// Scale factor.
|
||||||
zoom: f64,
|
zoom: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -456,10 +459,22 @@ impl Default for PTZ {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PTZ {
|
impl PTZ {
|
||||||
|
/// Get the tilt angle between -180° and 180° in radians.
|
||||||
|
pub fn tilt(&self) -> f64 {
|
||||||
|
(((self.tilt + std::f64::consts::PI) % std::f64::consts::TAU) + std::f64::consts::TAU) % std::f64::consts::TAU - std::f64::consts::PI
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a new tilt angle in radians.
|
||||||
|
pub fn set_tilt(&mut self, tilt: f64) {
|
||||||
|
self.tilt = tilt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the scale factor.
|
||||||
pub fn zoom(&self) -> f64 {
|
pub fn zoom(&self) -> f64 {
|
||||||
self.zoom
|
self.zoom
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a new scale factor.
|
||||||
pub fn set_zoom(&mut self, zoom: f64) {
|
pub fn set_zoom(&mut self, zoom: f64) {
|
||||||
self.zoom = zoom.clamp(crate::consts::VIEWPORT_ZOOM_SCALE_MIN, crate::consts::VIEWPORT_ZOOM_SCALE_MAX)
|
self.zoom = zoom.clamp(crate::consts::VIEWPORT_ZOOM_SCALE_MIN, crate::consts::VIEWPORT_ZOOM_SCALE_MAX)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ pub struct MenuBarMessageData {
|
||||||
pub has_active_document: bool,
|
pub has_active_document: bool,
|
||||||
pub rulers_visible: bool,
|
pub rulers_visible: bool,
|
||||||
pub node_graph_open: bool,
|
pub node_graph_open: bool,
|
||||||
|
pub has_selected_nodes: bool,
|
||||||
|
pub has_selected_layers: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
|
@ -14,6 +16,8 @@ pub struct MenuBarMessageHandler {
|
||||||
has_active_document: bool,
|
has_active_document: bool,
|
||||||
rulers_visible: bool,
|
rulers_visible: bool,
|
||||||
node_graph_open: bool,
|
node_graph_open: bool,
|
||||||
|
has_selected_nodes: bool,
|
||||||
|
has_selected_layers: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageHandler<MenuBarMessage, MenuBarMessageData> for MenuBarMessageHandler {
|
impl MessageHandler<MenuBarMessage, MenuBarMessageData> for MenuBarMessageHandler {
|
||||||
|
|
@ -22,10 +26,14 @@ impl MessageHandler<MenuBarMessage, MenuBarMessageData> for MenuBarMessageHandle
|
||||||
has_active_document,
|
has_active_document,
|
||||||
rulers_visible,
|
rulers_visible,
|
||||||
node_graph_open,
|
node_graph_open,
|
||||||
|
has_selected_nodes,
|
||||||
|
has_selected_layers,
|
||||||
} = data;
|
} = data;
|
||||||
self.has_active_document = has_active_document;
|
self.has_active_document = has_active_document;
|
||||||
self.rulers_visible = rulers_visible;
|
self.rulers_visible = rulers_visible;
|
||||||
self.node_graph_open = node_graph_open;
|
self.node_graph_open = node_graph_open;
|
||||||
|
self.has_selected_nodes = has_selected_nodes;
|
||||||
|
self.has_selected_layers = has_selected_layers;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
MenuBarMessage::SendLayout => self.send_layout(responses, LayoutTarget::MenuBar),
|
MenuBarMessage::SendLayout => self.send_layout(responses, LayoutTarget::MenuBar),
|
||||||
|
|
@ -41,6 +49,8 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||||
fn layout(&self) -> Layout {
|
fn layout(&self) -> Layout {
|
||||||
let no_active_document = !self.has_active_document;
|
let no_active_document = !self.has_active_document;
|
||||||
let node_graph_open = self.node_graph_open;
|
let node_graph_open = self.node_graph_open;
|
||||||
|
let has_selected_nodes = self.has_selected_nodes;
|
||||||
|
let has_selected_layers = self.has_selected_layers;
|
||||||
|
|
||||||
let menu_bar_entries = vec![
|
let menu_bar_entries = vec![
|
||||||
MenuBarEntry {
|
MenuBarEntry {
|
||||||
|
|
@ -147,7 +157,7 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||||
label: "Cut".into(),
|
label: "Cut".into(),
|
||||||
shortcut: action_keys!(PortfolioMessageDiscriminant::Cut),
|
shortcut: action_keys!(PortfolioMessageDiscriminant::Cut),
|
||||||
action: MenuBarEntry::create_action(|_| PortfolioMessage::Cut { clipboard: Clipboard::Device }.into()),
|
action: MenuBarEntry::create_action(|_| PortfolioMessage::Cut { clipboard: Clipboard::Device }.into()),
|
||||||
disabled: no_active_document,
|
disabled: no_active_document || !has_selected_layers,
|
||||||
..MenuBarEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuBarEntry {
|
MenuBarEntry {
|
||||||
|
|
@ -155,7 +165,7 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||||
icon: Some("Copy".into()),
|
icon: Some("Copy".into()),
|
||||||
shortcut: action_keys!(PortfolioMessageDiscriminant::Copy),
|
shortcut: action_keys!(PortfolioMessageDiscriminant::Copy),
|
||||||
action: MenuBarEntry::create_action(|_| PortfolioMessage::Copy { clipboard: Clipboard::Device }.into()),
|
action: MenuBarEntry::create_action(|_| PortfolioMessage::Copy { clipboard: Clipboard::Device }.into()),
|
||||||
disabled: no_active_document,
|
disabled: no_active_document || !has_selected_layers,
|
||||||
..MenuBarEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuBarEntry {
|
MenuBarEntry {
|
||||||
|
|
@ -185,7 +195,7 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||||
label: "Deselect All".into(),
|
label: "Deselect All".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::DeselectAllLayers),
|
shortcut: action_keys!(DocumentMessageDiscriminant::DeselectAllLayers),
|
||||||
action: MenuBarEntry::create_action(|_| DocumentMessage::DeselectAllLayers.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::DeselectAllLayers.into()),
|
||||||
disabled: no_active_document,
|
disabled: no_active_document || !has_selected_nodes,
|
||||||
..MenuBarEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -208,7 +218,7 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||||
icon: Some("Trash".into()),
|
icon: Some("Trash".into()),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers),
|
shortcut: action_keys!(DocumentMessageDiscriminant::DeleteSelectedLayers),
|
||||||
action: MenuBarEntry::create_action(|_| DocumentMessage::DeleteSelectedLayers.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::DeleteSelectedLayers.into()),
|
||||||
disabled: no_active_document,
|
disabled: no_active_document || !has_selected_nodes,
|
||||||
..MenuBarEntry::default()
|
..MenuBarEntry::default()
|
||||||
}],
|
}],
|
||||||
vec![
|
vec![
|
||||||
|
|
@ -216,55 +226,55 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||||
label: "Grab Selected".into(),
|
label: "Grab Selected".into(),
|
||||||
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginGrab),
|
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginGrab),
|
||||||
action: MenuBarEntry::create_action(|_| TransformLayerMessage::BeginGrab.into()),
|
action: MenuBarEntry::create_action(|_| TransformLayerMessage::BeginGrab.into()),
|
||||||
disabled: no_active_document,
|
disabled: no_active_document || !has_selected_layers,
|
||||||
..MenuBarEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuBarEntry {
|
MenuBarEntry {
|
||||||
label: "Rotate Selected".into(),
|
label: "Rotate Selected".into(),
|
||||||
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginRotate),
|
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginRotate),
|
||||||
action: MenuBarEntry::create_action(|_| TransformLayerMessage::BeginRotate.into()),
|
action: MenuBarEntry::create_action(|_| TransformLayerMessage::BeginRotate.into()),
|
||||||
disabled: no_active_document,
|
disabled: no_active_document || !has_selected_layers,
|
||||||
..MenuBarEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuBarEntry {
|
MenuBarEntry {
|
||||||
label: "Scale Selected".into(),
|
label: "Scale Selected".into(),
|
||||||
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginScale),
|
shortcut: action_keys!(TransformLayerMessageDiscriminant::BeginScale),
|
||||||
action: MenuBarEntry::create_action(|_| TransformLayerMessage::BeginScale.into()),
|
action: MenuBarEntry::create_action(|_| TransformLayerMessage::BeginScale.into()),
|
||||||
disabled: no_active_document,
|
disabled: no_active_document || !has_selected_layers,
|
||||||
..MenuBarEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
vec![MenuBarEntry {
|
vec![MenuBarEntry {
|
||||||
label: "Order".into(),
|
label: "Order".into(),
|
||||||
action: MenuBarEntry::no_action(),
|
action: MenuBarEntry::no_action(),
|
||||||
disabled: no_active_document,
|
disabled: no_active_document || !has_selected_layers,
|
||||||
children: MenuBarEntryChildren(vec![vec![
|
children: MenuBarEntryChildren(vec![vec![
|
||||||
MenuBarEntry {
|
MenuBarEntry {
|
||||||
label: "Raise To Front".into(),
|
label: "Raise To Front".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersRaiseToFront),
|
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersRaiseToFront),
|
||||||
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersRaiseToFront.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersRaiseToFront.into()),
|
||||||
disabled: no_active_document,
|
disabled: no_active_document || !has_selected_layers,
|
||||||
..MenuBarEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuBarEntry {
|
MenuBarEntry {
|
||||||
label: "Raise".into(),
|
label: "Raise".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersRaise),
|
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersRaise),
|
||||||
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersRaise.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersRaise.into()),
|
||||||
disabled: no_active_document,
|
disabled: no_active_document || !has_selected_layers,
|
||||||
..MenuBarEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuBarEntry {
|
MenuBarEntry {
|
||||||
label: "Lower".into(),
|
label: "Lower".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersLower),
|
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersLower),
|
||||||
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersLower.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersLower.into()),
|
||||||
disabled: no_active_document,
|
disabled: no_active_document || !has_selected_layers,
|
||||||
..MenuBarEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuBarEntry {
|
MenuBarEntry {
|
||||||
label: "Lower to Back".into(),
|
label: "Lower to Back".into(),
|
||||||
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersLowerToBack),
|
shortcut: action_keys!(DocumentMessageDiscriminant::SelectedLayersLowerToBack),
|
||||||
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersLowerToBack.into()),
|
action: MenuBarEntry::create_action(|_| DocumentMessage::SelectedLayersLowerToBack.into()),
|
||||||
disabled: no_active_document,
|
disabled: no_active_document || !has_selected_layers,
|
||||||
..MenuBarEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
]]),
|
]]),
|
||||||
|
|
@ -325,7 +335,7 @@ impl LayoutHolder for MenuBarMessageHandler {
|
||||||
label: "Zoom to Fit Selection".into(),
|
label: "Zoom to Fit Selection".into(),
|
||||||
shortcut: action_keys!(NavigationMessageDiscriminant::FitViewportToSelection),
|
shortcut: action_keys!(NavigationMessageDiscriminant::FitViewportToSelection),
|
||||||
action: MenuBarEntry::create_action(|_| NavigationMessage::FitViewportToSelection.into()),
|
action: MenuBarEntry::create_action(|_| NavigationMessage::FitViewportToSelection.into()),
|
||||||
disabled: no_active_document,
|
disabled: no_active_document || !has_selected_layers,
|
||||||
..MenuBarEntry::default()
|
..MenuBarEntry::default()
|
||||||
},
|
},
|
||||||
MenuBarEntry {
|
MenuBarEntry {
|
||||||
|
|
|
||||||
|
|
@ -50,11 +50,16 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
let mut has_active_document = false;
|
let mut has_active_document = false;
|
||||||
let mut rulers_visible = false;
|
let mut rulers_visible = false;
|
||||||
let mut node_graph_open = false;
|
let mut node_graph_open = false;
|
||||||
|
let mut has_selected_nodes = false;
|
||||||
|
let mut has_selected_layers = false;
|
||||||
|
|
||||||
if let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get_mut(&document_id)) {
|
if let Some(document) = self.active_document_id.and_then(|document_id| self.documents.get_mut(&document_id)) {
|
||||||
has_active_document = true;
|
has_active_document = true;
|
||||||
rulers_visible = document.rulers_visible;
|
rulers_visible = document.rulers_visible;
|
||||||
node_graph_open = document.is_graph_overlay_open();
|
node_graph_open = document.is_graph_overlay_open();
|
||||||
|
let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
|
||||||
|
has_selected_nodes = selected_nodes.selected_nodes().next().is_some();
|
||||||
|
has_selected_layers = selected_nodes.selected_visible_layers(&document.network_interface).next().is_some();
|
||||||
}
|
}
|
||||||
self.menu_bar_message_handler.process_message(
|
self.menu_bar_message_handler.process_message(
|
||||||
message,
|
message,
|
||||||
|
|
@ -63,6 +68,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
||||||
has_active_document,
|
has_active_document,
|
||||||
rulers_visible,
|
rulers_visible,
|
||||||
node_graph_open,
|
node_graph_open,
|
||||||
|
has_selected_nodes,
|
||||||
|
has_selected_layers,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ use bezier_rs::{Bezier, BezierHandles, TValue};
|
||||||
use graphene_core::transform::Transform;
|
use graphene_core::transform::Transform;
|
||||||
use graphene_core::vector::{ManipulatorPointId, PointId, VectorData, VectorModificationType};
|
use graphene_core::vector::{ManipulatorPointId, PointId, VectorData, VectorModificationType};
|
||||||
|
|
||||||
use glam::DVec2;
|
use glam::{DAffine2, DVec2};
|
||||||
use graphene_std::vector::{HandleId, SegmentId};
|
use graphene_std::vector::{HandleId, SegmentId};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, Default)]
|
#[derive(Debug, PartialEq, Eq, Copy, Clone, Default)]
|
||||||
|
|
@ -628,7 +628,11 @@ impl ShapeState {
|
||||||
|
|
||||||
let transform_to_viewport_space = document.metadata().transform_to_viewport(layer);
|
let transform_to_viewport_space = document.metadata().transform_to_viewport(layer);
|
||||||
let transform_to_document_space = document.metadata().transform_to_document(layer);
|
let transform_to_document_space = document.metadata().transform_to_document(layer);
|
||||||
let delta_transform = if in_viewport_space { transform_to_viewport_space } else { transform_to_document_space };
|
let delta_transform = if in_viewport_space {
|
||||||
|
transform_to_viewport_space
|
||||||
|
} else {
|
||||||
|
DAffine2::from_angle(document.document_ptz.tilt()) * transform_to_document_space
|
||||||
|
};
|
||||||
let delta = delta_transform.inverse().transform_vector2(delta);
|
let delta = delta_transform.inverse().transform_vector2(delta);
|
||||||
|
|
||||||
for &point in state.selected_points.iter() {
|
for &point in state.selected_points.iter() {
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@ impl SelectedEdges {
|
||||||
if !enlargement_factor.y.is_finite() || old_size.y.abs() < f64::EPSILON * 1000. {
|
if !enlargement_factor.y.is_finite() || old_size.y.abs() < f64::EPSILON * 1000. {
|
||||||
enlargement_factor.y = 1.;
|
enlargement_factor.y = 1.;
|
||||||
}
|
}
|
||||||
let mut pivot = (self.bounds[0] * enlargement_factor - position) / (enlargement_factor - DVec2::splat(1.));
|
let mut pivot = (self.bounds[0] * enlargement_factor - position) / (enlargement_factor - DVec2::ONE);
|
||||||
if !pivot.x.is_finite() {
|
if !pivot.x.is_finite() {
|
||||||
pivot.x = 0.;
|
pivot.x = 0.;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ pub enum ArtboardToolMessage {
|
||||||
// Tool-specific messages
|
// Tool-specific messages
|
||||||
UpdateSelectedArtboard,
|
UpdateSelectedArtboard,
|
||||||
DeleteSelected,
|
DeleteSelected,
|
||||||
NudgeSelected { delta_x: f64, delta_y: f64 },
|
NudgeSelected { delta_x: f64, delta_y: f64, resize: Key, resize_opposite_corner: Key },
|
||||||
PointerDown,
|
PointerDown,
|
||||||
PointerMove { constrain_axis_or_aspect: Key, center: Key },
|
PointerMove { constrain_axis_or_aspect: Key, center: Key },
|
||||||
PointerOutsideViewport { constrain_axis_or_aspect: Key, center: Key },
|
PointerOutsideViewport { constrain_axis_or_aspect: Key, center: Key },
|
||||||
|
|
@ -465,19 +465,85 @@ impl Fsm for ArtboardToolFsmState {
|
||||||
|
|
||||||
ArtboardToolFsmState::Ready { hovered }
|
ArtboardToolFsmState::Ready { hovered }
|
||||||
}
|
}
|
||||||
(_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => {
|
(
|
||||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
_,
|
||||||
if tool_data.selected_artboard.unwrap() == LayerNodeIdentifier::ROOT_PARENT {
|
ArtboardToolMessage::NudgeSelected {
|
||||||
log::error!("Selected artboard cannot be ROOT_PARENT");
|
delta_x,
|
||||||
} else {
|
delta_y,
|
||||||
responses.add(GraphOperationMessage::ResizeArtboard {
|
resize,
|
||||||
layer: tool_data.selected_artboard.unwrap(),
|
resize_opposite_corner,
|
||||||
location: DVec2::new(bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y).round().as_ivec2(),
|
},
|
||||||
dimensions: (bounds.bounds[1] - bounds.bounds[0]).round().as_ivec2(),
|
) => {
|
||||||
});
|
let Some(bounds) = &mut tool_data.bounding_box_manager else {
|
||||||
}
|
return ArtboardToolFsmState::Ready { hovered };
|
||||||
|
};
|
||||||
|
let Some(selected_artboard) = tool_data.selected_artboard else {
|
||||||
|
return ArtboardToolFsmState::Ready { hovered };
|
||||||
|
};
|
||||||
|
if selected_artboard == LayerNodeIdentifier::ROOT_PARENT {
|
||||||
|
log::error!("Selected artboard cannot be ROOT_PARENT");
|
||||||
|
return ArtboardToolFsmState::Ready { hovered };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let resize = input.keyboard.key(resize);
|
||||||
|
let resize_opposite_corner = input.keyboard.key(resize_opposite_corner);
|
||||||
|
let [existing_top_left, existing_bottom_right] = bounds.bounds;
|
||||||
|
|
||||||
|
// Nudge translation without resizing
|
||||||
|
if !resize {
|
||||||
|
let delta = DVec2::from_angle(-document.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y));
|
||||||
|
|
||||||
|
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||||
|
layer: selected_artboard,
|
||||||
|
location: DVec2::new(existing_top_left.x + delta.x, existing_top_left.y + delta.y).round().as_ivec2(),
|
||||||
|
dimensions: (existing_bottom_right - existing_top_left).round().as_ivec2(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return ArtboardToolFsmState::Ready { hovered };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap and negate coordinates as needed to match the resize direction that's closest to the current tilt angle
|
||||||
|
let tilt = (document.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 = document.navigation_handler.calculate_offset_transform(input.viewport_bounds.center(), &document.document_ptz);
|
||||||
|
let to = document_to_viewport.inverse() * document.metadata().downstream_transform_to_viewport(selected_artboard);
|
||||||
|
let original_transform = document.metadata().upstream_transform(selected_artboard.to_node());
|
||||||
|
let new = to.inverse() * transformation * to * original_transform;
|
||||||
|
|
||||||
|
responses.add(GraphOperationMessage::ResizeArtboard {
|
||||||
|
layer: selected_artboard,
|
||||||
|
location: position.round().as_ivec2(),
|
||||||
|
dimensions: new.transform_vector2(existing_bottom_right - existing_top_left).round().as_ivec2(),
|
||||||
|
});
|
||||||
|
|
||||||
ArtboardToolFsmState::Ready { hovered }
|
ArtboardToolFsmState::Ready { hovered }
|
||||||
}
|
}
|
||||||
(ArtboardToolFsmState::Dragging | ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::Abort) => {
|
(ArtboardToolFsmState::Dragging | ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::Abort) => {
|
||||||
|
|
|
||||||
|
|
@ -426,9 +426,9 @@ pub fn solve_spline_first_handle_closed(points: &[DVec2]) -> Vec<DVec2> {
|
||||||
|
|
||||||
// Matrix coefficients `a`, `b` and `c` (see https://mathworld.wolfram.com/CubicSpline.html).
|
// Matrix coefficients `a`, `b` and `c` (see https://mathworld.wolfram.com/CubicSpline.html).
|
||||||
// We don't really need to allocate them but it keeps the maths understandable.
|
// We don't really need to allocate them but it keeps the maths understandable.
|
||||||
let a = vec![DVec2::splat(1.); len_points];
|
let a = vec![DVec2::ONE; len_points];
|
||||||
let b = vec![DVec2::splat(4.); len_points];
|
let b = vec![DVec2::splat(4.); len_points];
|
||||||
let c = vec![DVec2::splat(1.); len_points];
|
let c = vec![DVec2::ONE; len_points];
|
||||||
|
|
||||||
let mut cmod = vec![DVec2::ZERO; len_points];
|
let mut cmod = vec![DVec2::ZERO; len_points];
|
||||||
let mut u = vec![DVec2::ZERO; len_points];
|
let mut u = vec![DVec2::ZERO; len_points];
|
||||||
|
|
|
||||||
|
|
@ -163,12 +163,14 @@ fn random<U: num_traits::float::Float>(
|
||||||
// To u32
|
// To u32
|
||||||
#[node_macro::node(name("To u32"), category("Math: Numeric"))]
|
#[node_macro::node(name("To u32"), category("Math: Numeric"))]
|
||||||
fn to_u32<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> u32 {
|
fn to_u32<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> u32 {
|
||||||
|
let value = U::clamp(value, U::from(0.).unwrap(), U::from(u32::MAX as f64).unwrap());
|
||||||
value.to_u32().unwrap()
|
value.to_u32().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
// To u64
|
// To u64
|
||||||
#[node_macro::node(name("To u64"), category("Math: Numeric"))]
|
#[node_macro::node(name("To u64"), category("Math: Numeric"))]
|
||||||
fn to_u64<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> u64 {
|
fn to_u64<U: num_traits::float::Float>(_: (), #[implementations(f64, f32)] value: U) -> u64 {
|
||||||
|
let value = U::clamp(value, U::from(0.).unwrap(), U::from(u64::MAX as f64).unwrap());
|
||||||
value.to_u64().unwrap()
|
value.to_u64().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue