Fix 'Shallow Select' mode behavior for selection context transference (#2604)

* Fix ancestor always returning None during shallow select

* Fixes

* fix shift remove on both

* cleanup

* one more cleanup

* final(?) fix

* some cleanup

* more stuff

* make shallow the default

* fixes

* fix

* fix

* fix

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
mTvare
2025-04-30 03:24:48 +00:00
committed by GitHub
co-authored by Keavon Chambers
parent 23b2c5bdf2
commit 90be1f42c6
7 changed files with 179 additions and 71 deletions
@@ -310,13 +310,10 @@ impl OverlayContext {
} }
pub fn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) { pub fn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
let color_line = COLOR_OVERLAY_BLUE;
let end_point1 = pivot + radius * DVec2::from_angle(angle + offset_angle); let end_point1 = pivot + radius * DVec2::from_angle(angle + offset_angle);
let end_point2 = pivot + radius * DVec2::from_angle(offset_angle); let end_point2 = pivot + radius * DVec2::from_angle(offset_angle);
self.line(pivot, end_point1, Some(color_line), None); self.line(pivot, end_point1, None, None);
self.line(pivot, end_point2, Some(color_line), None); self.dashed_line(pivot, end_point2, None, None, Some(2.), Some(2.), Some(0.5));
self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle); self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);
} }
@@ -261,11 +261,21 @@ impl LayerNodeIdentifier {
metadata.get_relations(self).and_then(|relations| relations.last_child) metadata.get_relations(self).and_then(|relations| relations.last_child)
} }
/// Does the layer have children? If so, then it is a folder /// Does the layer have children? If so, then it is a folder.
pub fn has_children(self, metadata: &DocumentMetadata) -> bool { pub fn has_children(self, metadata: &DocumentMetadata) -> bool {
self.first_child(metadata).is_some() self.first_child(metadata).is_some()
} }
/// Is the layer a child of the given layer?
pub fn is_child_of(self, metadata: &DocumentMetadata, parent: &LayerNodeIdentifier) -> bool {
parent.children(metadata).any(|child| child == self)
}
/// Is the layer an ancestor of the given layer?
pub fn is_ancestor_of(self, metadata: &DocumentMetadata, child: &LayerNodeIdentifier) -> bool {
child.ancestors(metadata).any(|ancestor| ancestor == self)
}
/// Iterator over all direct children (excluding self and recursive children) /// Iterator over all direct children (excluding self and recursive children)
pub fn children(self, metadata: &DocumentMetadata) -> AxisIter { pub fn children(self, metadata: &DocumentMetadata) -> AxisIter {
AxisIter { AxisIter {
@@ -10,6 +10,7 @@ fn draw_dashed_line(line_start: DVec2, line_end: DVec2, transform: DAffine2, ove
overlay_context.dashed_line(min_viewport, max_viewport, None, None, Some(2.), Some(2.), Some(0.5)); overlay_context.dashed_line(min_viewport, max_viewport, None, None, Some(2.), Some(2.), Some(0.5));
} }
/// Draws a solid line with a length annotation between two points transformed by the given affine transformations. /// Draws a solid line with a length annotation between two points transformed by the given affine transformations.
fn draw_line_with_length(line_start: DVec2, line_end: DVec2, transform: DAffine2, document_to_viewport: DAffine2, overlay_context: &mut OverlayContext, label_alignment: LabelAlignment) { fn draw_line_with_length(line_start: DVec2, line_end: DVec2, transform: DAffine2, document_to_viewport: DAffine2, overlay_context: &mut OverlayContext, label_alignment: LabelAlignment) {
let transform_to_document = document_to_viewport.inverse() * transform; let transform_to_document = document_to_viewport.inverse() * transform;
@@ -267,7 +267,7 @@ impl Fsm for ArtboardToolFsmState {
let constrain_square = input.keyboard.get(constrain_axis_or_aspect as usize); let constrain_square = input.keyboard.get(constrain_axis_or_aspect as usize);
tool_data.resize_artboard(responses, document, input, from_center, constrain_square); tool_data.resize_artboard(responses, document, input, from_center, constrain_square);
// AutoPanning // Auto-panning
let messages = [ let messages = [
ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(), ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(),
ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(), ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(),
@@ -306,7 +306,7 @@ impl Fsm for ArtboardToolFsmState {
bounds.bounds[0] = position.round(); bounds.bounds[0] = position.round();
bounds.bounds[1] = position.round() + size.round(); bounds.bounds[1] = position.round() + size.round();
// AutoPanning // Auto-panning
let messages = [ let messages = [
ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(), ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(),
ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(), ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(),
@@ -345,7 +345,7 @@ impl Fsm for ArtboardToolFsmState {
}) })
} }
// AutoPanning // Auto-panning
let messages = [ let messages = [
ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(), ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(),
ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(), ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(),
@@ -377,25 +377,25 @@ impl Fsm for ArtboardToolFsmState {
ArtboardToolFsmState::Ready { hovered } ArtboardToolFsmState::Ready { hovered }
} }
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerOutsideViewport { .. }) => { (ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerOutsideViewport { .. }) => {
// AutoPanning // Auto-panning
let _ = tool_data.auto_panning.shift_viewport(input, responses); let _ = tool_data.auto_panning.shift_viewport(input, responses);
ArtboardToolFsmState::ResizingBounds ArtboardToolFsmState::ResizingBounds
} }
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerOutsideViewport { .. }) => { (ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerOutsideViewport { .. }) => {
// AutoPanning // Auto-panning
tool_data.auto_panning.shift_viewport(input, responses); tool_data.auto_panning.shift_viewport(input, responses);
ArtboardToolFsmState::Dragging ArtboardToolFsmState::Dragging
} }
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerOutsideViewport { .. }) => { (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerOutsideViewport { .. }) => {
// AutoPanning // Auto-panning
tool_data.auto_panning.shift_viewport(input, responses); tool_data.auto_panning.shift_viewport(input, responses);
ArtboardToolFsmState::Drawing ArtboardToolFsmState::Drawing
} }
(state, ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }) => { (state, ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }) => {
// AutoPanning // Auto-panning
let messages = [ let messages = [
ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(), ArtboardToolMessage::PointerOutsideViewport { constrain_axis_or_aspect, center }.into(),
ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(), ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }.into(),
@@ -52,8 +52,8 @@ pub enum SelectOptionsUpdate {
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum NestedSelectionBehavior { pub enum NestedSelectionBehavior {
#[default] #[default]
Deepest,
Shallowest, Shallowest,
Deepest,
} }
impl fmt::Display for NestedSelectionBehavior { impl fmt::Display for NestedSelectionBehavior {
@@ -115,7 +115,7 @@ impl ToolMetadata for SelectTool {
impl SelectTool { impl SelectTool {
fn deep_selection_widget(&self) -> WidgetHolder { fn deep_selection_widget(&self) -> WidgetHolder {
let layer_selection_behavior_entries = [NestedSelectionBehavior::Deepest, NestedSelectionBehavior::Shallowest] let layer_selection_behavior_entries = [NestedSelectionBehavior::Shallowest, NestedSelectionBehavior::Deepest]
.iter() .iter()
.map(|mode| { .map(|mode| {
MenuListEntry::new(format!("{mode:?}")) MenuListEntry::new(format!("{mode:?}"))
@@ -125,7 +125,7 @@ impl SelectTool {
.collect(); .collect();
DropdownInput::new(vec![layer_selection_behavior_entries]) DropdownInput::new(vec![layer_selection_behavior_entries])
.selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Shallowest) as u32)) .selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest) as u32))
.tooltip("Choose if clicking nested layers directly selects the deepest, or selects the shallowest and deepens by double clicking") .tooltip("Choose if clicking nested layers directly selects the deepest, or selects the shallowest and deepens by double clicking")
.widget_holder() .widget_holder()
} }
@@ -288,11 +288,24 @@ impl ToolTransition for SelectTool {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum SelectToolFsmState { enum SelectToolFsmState {
Ready { selection: NestedSelectionBehavior }, Ready {
Drawing { selection_shape: SelectionShapeType, has_drawn: bool }, selection: NestedSelectionBehavior,
Dragging { axis: Axis, using_compass: bool, has_dragged: bool }, },
Drawing {
selection_shape: SelectionShapeType,
has_drawn: bool,
},
Dragging {
axis: Axis,
using_compass: bool,
has_dragged: bool,
deepest: bool,
remove: bool,
},
ResizingBounds, ResizingBounds,
SkewingBounds { skew: Key }, SkewingBounds {
skew: Key,
},
RotatingBounds, RotatingBounds,
DraggingPivot, DraggingPivot,
} }
@@ -918,7 +931,7 @@ impl Fsm for SelectToolFsmState {
let axis_state = compass_rose_state.axis_type().filter(|_| can_grab_compass_rose); let axis_state = compass_rose_state.axis_type().filter(|_| can_grab_compass_rose);
(axis_state.unwrap_or_default(), axis_state.is_some()) (axis_state.unwrap_or_default(), axis_state.is_some())
}; };
SelectToolFsmState::Dragging { axis, using_compass, has_dragged: false } SelectToolFsmState::Dragging { axis, using_compass, has_dragged: false, deepest: input.keyboard.key(select_deepest), remove: input.keyboard.key(extend_selection) }
} }
// 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 {
@@ -954,7 +967,8 @@ impl Fsm for SelectToolFsmState {
// Dragging a selection box // Dragging a selection box
else { else {
tool_data.layers_dragging = selected; tool_data.layers_dragging = selected;
if !input.keyboard.key(extend_selection) && !input.keyboard.key(remove_from_selection) { let extend = input.keyboard.key(extend_selection);
if !extend && !input.keyboard.key(remove_from_selection) {
responses.add(DocumentMessage::DeselectAllLayers); responses.add(DocumentMessage::DeselectAllLayers);
tool_data.layers_dragging.clear(); tool_data.layers_dragging.clear();
} }
@@ -964,13 +978,13 @@ impl Fsm for SelectToolFsmState {
selected = intersection_list; selected = intersection_list;
match tool_data.nested_selection_behavior { match tool_data.nested_selection_behavior {
NestedSelectionBehavior::Shallowest if !input.keyboard.key(select_deepest) => drag_shallowest_manipulation(responses, selected, tool_data, document), NestedSelectionBehavior::Shallowest if !input.keyboard.key(select_deepest) => drag_shallowest_manipulation(responses, selected, tool_data, document, false, extend),
_ => drag_deepest_manipulation(responses, selected, tool_data, document), _ => drag_deepest_manipulation(responses, selected, tool_data, document, false),
} }
tool_data.get_snap_candidates(document, input); tool_data.get_snap_candidates(document, input);
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
SelectToolFsmState::Dragging { axis: Axis::None, using_compass: false, has_dragged: false } SelectToolFsmState::Dragging { axis: Axis::None, using_compass: false, has_dragged: false, deepest: input.keyboard.key(select_deepest), remove: input.keyboard.key(extend_selection) }
} else { } else {
let selection_shape = if input.keyboard.key(lasso_select) { SelectionShapeType::Lasso } else { SelectionShapeType::Box }; let selection_shape = if input.keyboard.key(lasso_select) { SelectionShapeType::Lasso } else { SelectionShapeType::Box };
SelectToolFsmState::Drawing { selection_shape, has_drawn: false } SelectToolFsmState::Drawing { selection_shape, has_drawn: false }
@@ -986,7 +1000,16 @@ 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::Dragging { axis, using_compass, has_dragged }, SelectToolMessage::PointerMove(modifier_keys)) => { (
SelectToolFsmState::Dragging {
axis,
using_compass,
has_dragged,
deepest,
remove,
},
SelectToolMessage::PointerMove(modifier_keys),
) => {
if !has_dragged { if !has_dragged {
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
} }
@@ -1028,7 +1051,7 @@ impl Fsm for SelectToolFsmState {
} }
tool_data.drag_current += mouse_delta; tool_data.drag_current += mouse_delta;
// AutoPanning // Auto-panning
let messages = [ let messages = [
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(),
SelectToolMessage::PointerMove(modifier_keys).into(), SelectToolMessage::PointerMove(modifier_keys).into(),
@@ -1039,6 +1062,8 @@ impl Fsm for SelectToolFsmState {
axis, axis,
using_compass, using_compass,
has_dragged: true, has_dragged: true,
deepest,
remove,
} }
} }
(SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove(modifier_keys)) => { (SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove(modifier_keys)) => {
@@ -1080,7 +1105,7 @@ impl Fsm for SelectToolFsmState {
selected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse(), None); selected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse(), None);
// AutoPanning // Auto-panning
let messages = [ let messages = [
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(),
SelectToolMessage::PointerMove(modifier_keys).into(), SelectToolMessage::PointerMove(modifier_keys).into(),
@@ -1169,7 +1194,7 @@ impl Fsm for SelectToolFsmState {
let snapped_mouse_position = mouse_position; let snapped_mouse_position = mouse_position;
tool_data.pivot.set_viewport_position(snapped_mouse_position, document, responses); tool_data.pivot.set_viewport_position(snapped_mouse_position, document, responses);
// AutoPanning // Auto-panning
let messages = [ let messages = [
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(),
SelectToolMessage::PointerMove(modifier_keys).into(), SelectToolMessage::PointerMove(modifier_keys).into(),
@@ -1190,7 +1215,7 @@ impl Fsm for SelectToolFsmState {
extend_lasso(&mut tool_data.lasso_polygon, tool_data.drag_current); extend_lasso(&mut tool_data.lasso_polygon, tool_data.drag_current);
} }
// AutoPanning // Auto-panning
let messages = [ let messages = [
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(),
SelectToolMessage::PointerMove(modifier_keys).into(), SelectToolMessage::PointerMove(modifier_keys).into(),
@@ -1227,17 +1252,32 @@ 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::Dragging { axis, using_compass, has_dragged }, SelectToolMessage::PointerOutsideViewport(_)) => { (
// AutoPanning SelectToolFsmState::Dragging {
axis,
using_compass,
has_dragged,
deepest,
remove,
},
SelectToolMessage::PointerOutsideViewport(_),
) => {
// Auto-panning
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
tool_data.drag_current += shift; tool_data.drag_current += shift;
tool_data.drag_start += shift; tool_data.drag_start += shift;
} }
SelectToolFsmState::Dragging { axis, using_compass, has_dragged } SelectToolFsmState::Dragging {
axis,
using_compass,
has_dragged,
deepest,
remove,
}
} }
(SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. }, SelectToolMessage::PointerOutsideViewport(_)) => { (SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. }, SelectToolMessage::PointerOutsideViewport(_)) => {
// AutoPanning // Auto-panning
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(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
bounds.center_of_transformation += shift; bounds.center_of_transformation += shift;
@@ -1248,13 +1288,13 @@ impl Fsm for SelectToolFsmState {
self self
} }
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerOutsideViewport(_)) => { (SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerOutsideViewport(_)) => {
// AutoPanning // Auto-panning
let _ = tool_data.auto_panning.shift_viewport(input, responses); let _ = tool_data.auto_panning.shift_viewport(input, responses);
self self
} }
(SelectToolFsmState::Drawing { .. }, SelectToolMessage::PointerOutsideViewport(_)) => { (SelectToolFsmState::Drawing { .. }, SelectToolMessage::PointerOutsideViewport(_)) => {
// AutoPanning // Auto-panning
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) { if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
tool_data.drag_start += shift; tool_data.drag_start += shift;
} }
@@ -1262,7 +1302,7 @@ impl Fsm for SelectToolFsmState {
self self
} }
(state, SelectToolMessage::PointerOutsideViewport(modifier_keys)) => { (state, SelectToolMessage::PointerOutsideViewport(modifier_keys)) => {
// AutoPanning // Auto-panning
let messages = [ let messages = [
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(), SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(),
SelectToolMessage::PointerMove(modifier_keys).into(), SelectToolMessage::PointerMove(modifier_keys).into(),
@@ -1271,7 +1311,7 @@ impl Fsm for SelectToolFsmState {
state state
} }
(SelectToolFsmState::Dragging { has_dragged, .. }, SelectToolMessage::DragStop { remove_from_selection }) => { (SelectToolFsmState::Dragging { has_dragged, remove, deepest, .. }, SelectToolMessage::DragStop { remove_from_selection }) => {
// Deselect layer if not snap dragging // Deselect layer if not snap dragging
responses.add(DocumentMessage::EndTransaction); responses.add(DocumentMessage::EndTransaction);
tool_data.axis_align = false; tool_data.axis_align = false;
@@ -1306,15 +1346,28 @@ impl Fsm for SelectToolFsmState {
.collect(), .collect(),
}); });
} }
} else if let Some(selecting_layer) = tool_data.select_single_layer.take() { } else if tool_data.select_single_layer.take().is_some() {
// Previously, we may have had many layers selected. If the user clicks without dragging, we should just select the one layer that has been clicked. // Previously, we may have had many layers selected. If the user clicks without dragging, we should just select the one layer that has been clicked.
if !has_dragged { if !has_dragged {
if selecting_layer == LayerNodeIdentifier::ROOT_PARENT { let selected = document.click_list(input).collect::<Vec<_>>();
log::error!("selecting_layer should not be ROOT_PARENT"); let intersection = document.find_deepest(&selected);
} else { if let Some(intersection) = intersection {
responses.add(NodeGraphMessage::SelectedNodesSet { tool_data.layer_selected_on_start = Some(intersection);
nodes: vec![selecting_layer.to_node()],
}); match tool_data.nested_selection_behavior {
NestedSelectionBehavior::Shallowest if remove && !deepest => drag_shallowest_manipulation(responses, selected, tool_data, document, true, true),
NestedSelectionBehavior::Deepest if remove => drag_deepest_manipulation(responses, selected, tool_data, document, true),
NestedSelectionBehavior::Shallowest if !deepest => drag_shallowest_manipulation(responses, selected, tool_data, document, false, true),
_ => {
responses.add(DocumentMessage::DeselectAllLayers);
tool_data.layers_dragging.clear();
drag_deepest_manipulation(responses, selected, tool_data, document, false)
}
}
tool_data.get_snap_candidates(document, input);
responses.add(DocumentMessage::StartTransaction);
} }
} }
} }
@@ -1528,7 +1581,7 @@ impl Fsm for SelectToolFsmState {
]); ]);
responses.add(FrontendMessage::UpdateInputHints { hint_data }); responses.add(FrontendMessage::UpdateInputHints { hint_data });
} }
SelectToolFsmState::Dragging { axis, using_compass, has_dragged } if *has_dragged => { SelectToolFsmState::Dragging { axis, using_compass, has_dragged, .. } if *has_dragged => {
let mut hint_data = vec![ let mut hint_data = vec![
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
HintGroup(vec![ HintGroup(vec![
@@ -1591,16 +1644,59 @@ fn not_artboard(document: &DocumentMessageHandler) -> impl Fn(&LayerNodeIdentifi
|&layer| layer != LayerNodeIdentifier::ROOT_PARENT && !document.network_interface.is_artboard(&layer.to_node(), &[]) |&layer| layer != LayerNodeIdentifier::ROOT_PARENT && !document.network_interface.is_artboard(&layer.to_node(), &[])
} }
fn drag_shallowest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData, document: &DocumentMessageHandler) { fn drag_shallowest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData, document: &DocumentMessageHandler, remove: bool, exists: bool) {
for layer in selected { if selected.is_empty() {
let ancestor = layer return;
.ancestors(document.metadata()) }
.filter(not_artboard(document))
.find(|&ancestor| document.network_interface.selected_nodes().selected_layers_contains(ancestor, document.metadata()));
let new_selected = ancestor.unwrap_or_else(|| layer.ancestors(document.metadata()).filter(not_artboard(document)).last().unwrap_or(layer)); let clicked_layer = document.find_deepest(&selected).unwrap_or_else(|| {
tool_data.layers_dragging.retain(|layer| !layer.ancestors(document.metadata()).any(|ancestor| ancestor == new_selected)); LayerNodeIdentifier::ROOT_PARENT
tool_data.layers_dragging.push(new_selected); .children(document.metadata())
.next()
.expect("ROOT_PARENT should have at least one layer when clicking")
});
let metadata = document.metadata();
let selected_layers = document.network_interface.selected_nodes().selected_layers(document.metadata()).collect::<Vec<_>>();
let final_selection: Option<LayerNodeIdentifier> = (!selected_layers.is_empty() && selected_layers != vec![LayerNodeIdentifier::ROOT_PARENT]).then_some(()).and_then(|_| {
let mut relevant_layers = document.network_interface.selected_nodes().selected_layers(document.metadata()).collect::<Vec<_>>();
if !relevant_layers.contains(&clicked_layer) {
relevant_layers.push(clicked_layer);
}
clicked_layer
.ancestors(metadata)
.filter(not_artboard(document))
.find(|&ancestor| relevant_layers.iter().all(|layer| *layer == ancestor || ancestor.is_ancestor_of(metadata, layer)))
.and_then(|least_common_ancestor| {
let common_siblings: Vec<_> = least_common_ancestor.children(metadata).collect();
(clicked_layer == least_common_ancestor)
.then_some(least_common_ancestor)
.or_else(|| common_siblings.iter().find(|&&child| clicked_layer == child || child.is_ancestor_of(metadata, &clicked_layer)).copied())
})
});
if final_selection.is_some_and(|layer| selected_layers.iter().any(|selected| layer.is_child_of(metadata, selected))) {
if exists && remove && selected_layers.len() == 1 {
responses.add(DocumentMessage::DeselectAllLayers);
tool_data.layers_dragging.clear();
}
return;
}
if !exists && !remove {
responses.add(DocumentMessage::DeselectAllLayers);
tool_data.layers_dragging.clear();
}
let new_selected = final_selection.unwrap_or_else(|| clicked_layer.ancestors(document.metadata()).filter(not_artboard(document)).last().unwrap_or(clicked_layer));
tool_data.layers_dragging.extend(vec![new_selected]);
tool_data.layers_dragging.retain(|&selected_layer| !selected_layer.is_child_of(metadata, &new_selected));
if remove {
tool_data.layers_dragging.retain(|&selected_layer| clicked_layer != selected_layer);
if selected_layers.contains(&new_selected) {
tool_data.layers_dragging.retain(|&selected_layer| new_selected != selected_layer);
}
} }
responses.add(NodeGraphMessage::SelectedNodesSet { responses.add(NodeGraphMessage::SelectedNodesSet {
@@ -1619,15 +1715,18 @@ fn drag_shallowest_manipulation(responses: &mut VecDeque<Message>, selected: Vec
}); });
} }
fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData, document: &DocumentMessageHandler) { fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData, document: &DocumentMessageHandler, remove: bool) {
tool_data.layers_dragging.append(&mut vec![ let layer = document.find_deepest(&selected).unwrap_or(
document.find_deepest(&selected).unwrap_or( LayerNodeIdentifier::ROOT_PARENT
LayerNodeIdentifier::ROOT_PARENT .children(document.metadata())
.children(document.metadata()) .next()
.next() .expect("ROOT_PARENT should have a layer child when clicking"),
.expect("ROOT_PARENT should have a layer child when clicking"), );
), if !remove {
]); tool_data.layers_dragging.extend(vec![layer]);
} else {
tool_data.layers_dragging.retain(|&selected_layer| layer != selected_layer);
}
responses.add(NodeGraphMessage::SelectedNodesSet { responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: tool_data nodes: tool_data
.layers_dragging .layers_dragging
@@ -702,7 +702,7 @@ impl Fsm for TextToolFsmState {
}); });
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
// AutoPanning // Auto-panning
let messages = [ let messages = [
TextToolMessage::PointerOutsideViewport { center, lock_ratio }.into(), TextToolMessage::PointerOutsideViewport { center, lock_ratio }.into(),
TextToolMessage::PointerMove { center, lock_ratio }.into(), TextToolMessage::PointerMove { center, lock_ratio }.into(),
@@ -725,7 +725,7 @@ impl Fsm for TextToolFsmState {
TextToolFsmState::Placing TextToolFsmState::Placing
} }
(TextToolFsmState::ResizingBounds | TextToolFsmState::Dragging, TextToolMessage::PointerOutsideViewport { .. }) => { (TextToolFsmState::ResizingBounds | TextToolFsmState::Dragging, TextToolMessage::PointerOutsideViewport { .. }) => {
// AutoPanning // Auto-panning
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(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
bounds.center_of_transformation += shift; bounds.center_of_transformation += shift;
@@ -242,7 +242,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. { if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. {
let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] }; let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] };
overlay_context.line(quad[0], end, None, None); overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
let x_transform = DAffine2::from_translation((quad[0] + end) / 2.); let x_transform = DAffine2::from_translation((quad[0] + end) / 2.);
overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]); overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);
@@ -250,7 +250,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. { if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. {
let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] }; let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] };
overlay_context.line(quad[0], end, None, None); overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
let x_parameter = viewport_translate.x.clamp(-1., 1.); let x_parameter = viewport_translate.x.clamp(-1., 1.);
let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.); let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.);
let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End }; let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End };
@@ -258,9 +258,10 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]); overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]);
} }
} }
if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. { if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. {
overlay_context.dashed_line(quad[1], quad[2], None, None, Some(2.), Some(2.), Some(0.5)); overlay_context.line(quad[1], quad[2], None, None);
overlay_context.dashed_line(quad[3], quad[2], None, None, Some(2.), Some(2.), Some(0.5)); overlay_context.line(quad[3], quad[2], None, None);
} }
} }
TransformOperation::Scaling(scale) => { TransformOperation::Scaling(scale) => {
@@ -274,7 +275,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
let end_point = pivot + local_edge * scale.max(1.); let end_point = pivot + local_edge * scale.max(1.);
if scale > 0. { if scale > 0. {
overlay_context.dashed_line(pivot, boundary_point, None, None, Some(4.), Some(4.), Some(0.5)); overlay_context.dashed_line(pivot, boundary_point, None, None, Some(2.), Some(2.), Some(0.5));
} }
overlay_context.line(boundary_point, end_point, None, None); overlay_context.line(boundary_point, end_point, None, None);