Improve layer positioning in graph upon reordering; improve history system; add selection history (#1945)

* Improve layer positioning

* Collapse space when deleting

* Improve moving layers in layer panel

* Improved transactions

* Selection history

* Code review

* Select previous selection when aborting

* Fix crash and artboard select

* Add mouse forward/back button selection history bindings

Code review

* Menu buttons

* Code review

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
adamgerhant
2024-09-01 02:34:18 -07:00
committed by GitHub
co-authored by Keavon Chambers
parent 432385343e
commit 9adc640f19
29 changed files with 806 additions and 443 deletions
@@ -45,11 +45,8 @@ impl Pivot {
/// Recomputes the pivot position and transform.
fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
let mut layers = document
.network_interface
.selected_nodes(&[])
.unwrap()
.selected_visible_and_unlocked_layers(&document.network_interface);
let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface);
let Some(first) = layers.next() else {
// If no layers are selected then we revert things back to default
self.normalized_pivot = DVec2::splat(0.5);
@@ -146,8 +146,6 @@ impl ArtboardToolData {
}
fn select_artboard(&mut self, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> bool {
responses.add(DocumentMessage::StartTransaction);
if let Some(intersection) = Self::hovered_artboard(document, input) {
self.selected_artboard = Some(intersection);
@@ -238,9 +236,7 @@ impl Fsm for ArtboardToolFsmState {
tool_data.drag_start = to_document.transform_point2(input.mouse.position);
tool_data.drag_current = to_document.transform_point2(input.mouse.position);
if let Some(selected_edges) = tool_data.check_dragging_bounds(input.mouse.position) {
responses.add(DocumentMessage::StartTransaction);
let state = if let Some(selected_edges) = tool_data.check_dragging_bounds(input.mouse.position) {
tool_data.start_resizing(selected_edges, document, input);
tool_data.get_snap_candidates(document, input);
ArtboardToolFsmState::ResizingBounds
@@ -258,7 +254,9 @@ impl Fsm for ArtboardToolFsmState {
tool_data.drag_current = snapped.snapped_point_document;
ArtboardToolFsmState::Drawing
}
};
responses.add(DocumentMessage::StartTransaction);
state
}
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
let from_center = input.keyboard.get(center as usize);
@@ -427,32 +425,15 @@ impl Fsm for ArtboardToolFsmState {
state
}
(ArtboardToolFsmState::ResizingBounds, ArtboardToolMessage::PointerUp) => {
(ArtboardToolFsmState::Drawing | ArtboardToolFsmState::ResizingBounds | ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => {
responses.add(DocumentMessage::EndTransaction);
tool_data.snap_manager.cleanup(responses);
if let Some(bounds) = &mut tool_data.bounding_box_manager {
bounds.original_transforms.clear();
}
ArtboardToolFsmState::Ready { hovered }
}
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerUp) => {
tool_data.snap_manager.cleanup(responses);
if let Some(bounds) = &mut tool_data.bounding_box_manager {
bounds.original_transforms.clear();
}
responses.add(OverlaysMessage::Draw);
ArtboardToolFsmState::Ready { hovered }
}
(ArtboardToolFsmState::Dragging, ArtboardToolMessage::PointerUp) => {
tool_data.snap_manager.cleanup(responses);
if let Some(bounds) = &mut tool_data.bounding_box_manager {
bounds.original_transforms.clear();
}
responses.add(OverlaysMessage::Draw);
ArtboardToolFsmState::Ready { hovered }
@@ -381,7 +381,9 @@ impl Fsm for BrushToolFsmState {
(BrushToolFsmState::Drawing, BrushToolMessage::DragStop) => {
if !tool_data.strokes.is_empty() {
responses.add(DocumentMessage::CommitTransaction);
responses.add(DocumentMessage::EndTransaction);
} else {
responses.add(DocumentMessage::AbortTransaction);
}
tool_data.strokes.clear();
@@ -96,9 +96,8 @@ impl Fsm for FillToolFsmState {
_ => return self,
};
responses.add(DocumentMessage::StartTransaction);
responses.add(DocumentMessage::AddTransaction);
responses.add(GraphOperationMessage::FillSet { layer: layer_identifier, fill });
responses.add(DocumentMessage::CommitTransaction);
FillToolFsmState::Filling
}
@@ -254,7 +254,7 @@ impl Fsm for FreehandToolFsmState {
if tool_data.dragged {
responses.add(DocumentMessage::CommitTransaction);
} else {
responses.add(DocumentMessage::DocumentHistoryBackward);
responses.add(DocumentMessage::EndTransaction);
}
tool_data.end_point = None;
@@ -284,6 +284,8 @@ impl Fsm for GradientToolFsmState {
return self;
}
responses.add(DocumentMessage::AddTransaction);
// Remove the selected point
match selected_gradient.dragging {
GradientDragTarget::Start => selected_gradient.gradient.stops.0.remove(0),
@@ -326,8 +328,8 @@ impl Fsm for GradientToolFsmState {
(_, GradientToolMessage::InsertStop) => {
for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_visible_layers(&document.network_interface) {
let Some(mut gradient) = get_gradient(layer, &document.network_interface) else { continue };
// TODO: This transform is incorrect. I think this is since it is based on the Footprint which has not been updated yet
let transform = gradient_space_transform(layer, document);
let mouse = input.mouse.position;
let (start, end) = (transform.transform_point2(gradient.start), transform.transform_point2(gradient.end));
@@ -338,7 +340,7 @@ impl Fsm for GradientToolFsmState {
if distance < (SELECTION_THRESHOLD * 2.) {
// Try and insert the new stop
if let Some(index) = gradient.insert_stop(mouse, transform) {
document.backup_nonmut(responses);
responses.add(DocumentMessage::AddTransaction);
let mut selected_gradient = SelectedGradient::new(gradient, layer, document);
@@ -366,7 +368,6 @@ impl Fsm for GradientToolFsmState {
for layer in document.network_interface.selected_nodes(&[]).unwrap().selected_visible_layers(&document.network_interface) {
let Some(gradient) = get_gradient(layer, &document.network_interface) else { continue };
let transform = gradient_space_transform(layer, document);
// Check for dragging step
for (index, (pos, _)) in gradient.stops.0.iter().enumerate() {
let pos = transform.transform_point2(gradient.start.lerp(gradient.end, *pos));
@@ -395,8 +396,8 @@ impl Fsm for GradientToolFsmState {
}
}
}
if dragging {
document.backup_nonmut(responses);
let gradient_state = if dragging {
GradientToolFsmState::Drawing
} else {
let selected_layer = document.click(input);
@@ -409,8 +410,6 @@ impl Fsm for GradientToolFsmState {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
}
responses.add(DocumentMessage::StartTransaction);
// Use the already existing gradient if it exists
let gradient = if let Some(gradient) = get_gradient(layer, &document.network_interface) {
gradient.clone()
@@ -433,7 +432,9 @@ impl Fsm for GradientToolFsmState {
} else {
GradientToolFsmState::Ready
}
}
};
responses.add(DocumentMessage::StartTransaction);
gradient_state
}
(GradientToolFsmState::Drawing, GradientToolMessage::PointerMove { constrain_axis }) => {
if let Some(selected_gradient) = &mut tool_data.selected_gradient {
@@ -473,6 +474,11 @@ impl Fsm for GradientToolFsmState {
(GradientToolFsmState::Drawing, GradientToolMessage::PointerUp) => {
input.mouse.finish_transaction(tool_data.drag_start, responses);
tool_data.snap_manager.cleanup(responses);
if let Some(selected_layer) = document.click(input) {
if let Some(gradient) = get_gradient(selected_layer, &document.network_interface) {
tool_data.selected_gradient = Some(SelectedGradient::new(gradient, selected_layer, document));
}
}
GradientToolFsmState::Ready
}
@@ -97,7 +97,7 @@ impl Fsm for ImaginateToolFsmState {
match (self, event) {
(ImaginateToolFsmState::Ready, ImaginateToolMessage::DragStart) => {
shape_data.start(document, input);
responses.add(DocumentMessage::StartTransaction);
// responses.add(DocumentMessage::AddTransaction);
//shape_data.layer = Some(LayerNodeIdentifier::new(NodeId(generate_uuid()), &document.network_interface));
responses.add(DocumentMessage::DeselectAllLayers);
@@ -289,20 +289,25 @@ impl PathToolData {
}
fn end_insertion(&mut self, shape_editor: &mut ShapeState, responses: &mut VecDeque<Message>, kind: InsertEndKind) -> PathToolFsmState {
let mut commit_transaction = false;
match self.segment.as_mut() {
None => {
warn!("Segment was `None` before `end_insertion`")
}
Some(closed_segment) => {
if let InsertEndKind::Add { shift } = kind {
responses.add(DocumentMessage::StartTransaction);
closed_segment.adjusted_insert_and_select(shape_editor, responses, shift);
responses.add(DocumentMessage::CommitTransaction);
commit_transaction = true;
}
}
}
self.segment = None;
if commit_transaction {
responses.add(DocumentMessage::EndTransaction);
} else {
responses.add(DocumentMessage::AbortTransaction);
}
responses.add(OverlaysMessage::Draw);
PathToolFsmState::Ready
}
@@ -323,15 +328,18 @@ impl PathToolData {
// Select the first point within the threshold (in pixels)
if let Some(selected_points) = shape_editor.change_point_selection(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD, add_to_selection) {
responses.add(DocumentMessage::StartTransaction);
if let Some(selected_points) = selected_points {
self.drag_start_pos = input.mouse.position;
self.start_dragging_point(selected_points, input, document, shape_editor, responses);
self.start_dragging_point(selected_points, input, document, shape_editor);
responses.add(OverlaysMessage::Draw);
}
PathToolFsmState::Dragging
}
// We didn't find a point nearby, so now we'll try to add a point into the closest path segment
else if let Some(closed_segment) = shape_editor.upper_closest_segment(&document.network_interface, input.mouse.position, SELECTION_TOLERANCE) {
responses.add(DocumentMessage::StartTransaction);
if direct_insert_without_sliding {
self.start_insertion(responses, closed_segment);
self.end_insertion(shape_editor, responses, InsertEndKind::Add { shift: add_to_selection })
@@ -341,6 +349,7 @@ impl PathToolData {
}
// We didn't find a segment path, so consider selecting the nearest shape instead
else if let Some(layer) = document.click(input) {
responses.add(DocumentMessage::StartTransaction);
if add_to_selection {
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });
} else {
@@ -361,16 +370,7 @@ impl PathToolData {
}
}
fn start_dragging_point(
&mut self,
selected_points: SelectedPointsInfo,
input: &InputPreprocessorMessageHandler,
document: &DocumentMessageHandler,
shape_editor: &mut ShapeState,
responses: &mut VecDeque<Message>,
) {
responses.add(DocumentMessage::StartTransaction);
fn start_dragging_point(&mut self, selected_points: SelectedPointsInfo, input: &InputPreprocessorMessageHandler, document: &DocumentMessageHandler, shape_editor: &mut ShapeState) {
let mut manipulators = HashMap::with_hasher(NoHashBuilder);
let mut unselected = Vec::new();
for (&layer, state) in &shape_editor.selected_shape_state {
@@ -587,7 +587,6 @@ impl Fsm for PathToolFsmState {
PathToolFsmState::Ready
}
(PathToolFsmState::DrawingBox, PathToolMessage::Escape | PathToolMessage::RightClick) => {
responses.add(DocumentMessage::AbortTransaction);
tool_data.snap_manager.cleanup(responses);
PathToolFsmState::Ready
}
@@ -621,6 +620,7 @@ impl Fsm for PathToolFsmState {
}
}
responses.add(DocumentMessage::EndTransaction);
responses.add(PathToolMessage::SelectedPointUpdated);
tool_data.snap_manager.cleanup(responses);
PathToolFsmState::Ready
@@ -629,7 +629,7 @@ impl Fsm for PathToolFsmState {
// Delete key
(_, PathToolMessage::Delete) => {
// Delete the selected points and clean up overlays
responses.add(DocumentMessage::StartTransaction);
responses.add(DocumentMessage::AddTransaction);
shape_editor.delete_selected_points(document, responses);
responses.add(PathToolMessage::SelectionChanged);
@@ -689,14 +689,14 @@ impl Fsm for PathToolFsmState {
(_, PathToolMessage::ManipulatorMakeHandlesColinear) => {
responses.add(DocumentMessage::StartTransaction);
shape_editor.convert_selected_manipulators_to_colinear_handles(responses, document);
responses.add(DocumentMessage::CommitTransaction);
responses.add(DocumentMessage::EndTransaction);
responses.add(PathToolMessage::SelectionChanged);
PathToolFsmState::Ready
}
(_, PathToolMessage::ManipulatorMakeHandlesFree) => {
responses.add(DocumentMessage::StartTransaction);
shape_editor.disable_colinear_handles_state_on_selected(&document.network_interface, responses);
responses.add(DocumentMessage::CommitTransaction);
responses.add(DocumentMessage::EndTransaction);
PathToolFsmState::Ready
}
(_, _) => PathToolFsmState::Ready,
@@ -630,6 +630,7 @@ impl Fsm for PenToolFsmState {
state
}
(PenToolFsmState::DraggingHandle | PenToolFsmState::PlacingAnchor, PenToolMessage::Abort | PenToolMessage::Confirm) => {
responses.add(DocumentMessage::EndTransaction);
tool_data.layer = None;
tool_data.handle_end = None;
tool_data.latest_points.clear();
@@ -639,6 +640,8 @@ impl Fsm for PenToolFsmState {
PenToolFsmState::Ready
}
(_, PenToolMessage::Abort) => {
responses.add(DocumentMessage::AbortTransaction);
responses.add(OverlaysMessage::Draw);
self
@@ -479,7 +479,7 @@ impl Fsm for SelectToolFsmState {
}
// Only highlight layers if the viewport is not being panned (middle mouse button is pressed)
// TODO: Don't use `Key::Mmb` directly, instead take it as a variable from the input mappings list like in all other places
else if !input.keyboard.get(Key::Mmb as usize) {
else if !input.keyboard.get(Key::MouseMiddle as usize) {
// Get the layer the user is hovering over
let click = document.click(input);
let not_selected_click = click.filter(|&hovered_layer| !document.network_interface.selected_nodes(&[]).unwrap().selected_layers_contains(hovered_layer, document.metadata()));
@@ -649,7 +649,6 @@ impl Fsm for SelectToolFsmState {
}
if let Some(intersection) = intersection {
responses.add(DocumentMessage::StartTransaction);
tool_data.layer_selected_on_start = Some(intersection);
selected = intersection_list;
@@ -659,6 +658,8 @@ impl Fsm for SelectToolFsmState {
_ => drag_deepest_manipulation(responses, selected, tool_data, document),
}
tool_data.get_snap_candidates(document, input);
responses.add(DocumentMessage::StartTransaction);
SelectToolFsmState::Dragging
} else {
// Deselect all layers if using shallowest selection behavior
@@ -894,8 +895,8 @@ impl Fsm for SelectToolFsmState {
}
(SelectToolFsmState::Dragging, SelectToolMessage::Enter) => {
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
true => DocumentMessage::AbortTransaction,
false => DocumentMessage::EndTransaction,
};
tool_data.snap_manager.cleanup(responses);
responses.add_front(response);
@@ -905,6 +906,8 @@ impl Fsm for SelectToolFsmState {
}
(SelectToolFsmState::Dragging, SelectToolMessage::DragStop { remove_from_selection }) => {
// Deselect layer if not snap dragging
responses.add(DocumentMessage::EndTransaction);
if !tool_data.has_dragged && input.keyboard.key(remove_from_selection) && tool_data.layer_selected_on_start.is_none() {
let quad = tool_data.selection_quad();
let intersection = document.intersect_quad(quad, input);
@@ -950,7 +953,6 @@ impl Fsm for SelectToolFsmState {
tool_data.has_dragged = false;
tool_data.layer_selected_on_start = None;
responses.add(DocumentMessage::CommitTransaction);
tool_data.snap_manager.cleanup(responses);
tool_data.select_single_layer = None;
@@ -959,8 +961,8 @@ impl Fsm for SelectToolFsmState {
}
(SelectToolFsmState::ResizingBounds, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
true => DocumentMessage::AbortTransaction,
false => DocumentMessage::EndTransaction,
};
responses.add(response);
@@ -975,8 +977,8 @@ impl Fsm for SelectToolFsmState {
}
(SelectToolFsmState::RotatingBounds, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
true => DocumentMessage::AbortTransaction,
false => DocumentMessage::EndTransaction,
};
responses.add(response);
@@ -989,8 +991,8 @@ impl Fsm for SelectToolFsmState {
}
(SelectToolFsmState::DraggingPivot, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter) => {
let response = match input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON {
true => DocumentMessage::Undo,
false => DocumentMessage::CommitTransaction,
true => DocumentMessage::AbortTransaction,
false => DocumentMessage::EndTransaction,
};
responses.add(response);
@@ -1005,7 +1007,6 @@ impl Fsm for SelectToolFsmState {
let current_selected: HashSet<_> = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata()).collect();
if new_selected != current_selected {
tool_data.layers_dragging = new_selected.into_iter().collect();
responses.add(DocumentMessage::StartTransaction);
responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: tool_data
.layers_dragging
@@ -1027,7 +1028,8 @@ impl Fsm for SelectToolFsmState {
SelectToolFsmState::Ready { selection }
}
(SelectToolFsmState::Ready { .. }, SelectToolMessage::Enter) => {
let mut selected_layers = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata());
let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
if let Some(layer) = selected_layers.next() {
// Check that only one layer is selected
@@ -1041,8 +1043,8 @@ impl Fsm for SelectToolFsmState {
SelectToolFsmState::Ready { selection }
}
(SelectToolFsmState::Dragging, SelectToolMessage::Abort) => {
responses.add(DocumentMessage::AbortTransaction);
tool_data.snap_manager.cleanup(responses);
responses.add(DocumentMessage::Undo);
responses.add(OverlaysMessage::Draw);
let selection = tool_data.nested_selection_behavior;
@@ -1200,11 +1202,9 @@ 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) {
tool_data.layers_dragging.append(&mut vec![document.find_deepest(&selected).unwrap_or(LayerNodeIdentifier::new(
document.network_interface.root_node(&[]).expect("Root node should exist when dragging layers").node_id,
&document.network_interface,
&[],
))]);
tool_data.layers_dragging.append(&mut vec![document
.find_deepest(&selected)
.unwrap_or(LayerNodeIdentifier::ROOT_PARENT.children(document.metadata()).next().expect("Child should exist when dragging deepest"))]);
responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: tool_data
.layers_dragging
@@ -228,6 +228,8 @@ impl Fsm for SplineToolFsmState {
SplineToolFsmState::Drawing
}
(SplineToolFsmState::Drawing, SplineToolMessage::DragStop) => {
responses.add(DocumentMessage::EndTransaction);
let Some(layer) = tool_data.layer else {
return SplineToolFsmState::Ready;
};
@@ -279,7 +281,7 @@ impl Fsm for SplineToolFsmState {
(SplineToolFsmState::Drawing, SplineToolMessage::Confirm | SplineToolMessage::Abort) => {
if tool_data.points.len() >= 2 {
update_spline(document, tool_data, false, responses);
responses.add(DocumentMessage::CommitTransaction);
responses.add(DocumentMessage::EndTransaction);
} else {
responses.add(DocumentMessage::AbortTransaction);
}
@@ -259,7 +259,7 @@ impl TextToolData {
self.layer = layer;
self.load_layer_text_node(document);
responses.add(DocumentMessage::StartTransaction);
responses.add(DocumentMessage::AddTransaction);
self.set_editing(true, font_cache, document, responses);
@@ -282,7 +282,7 @@ impl TextToolData {
}
// Create new text
else if let Some(editing_text) = self.editing_text.as_ref().filter(|_| state == TextToolFsmState::Ready) {
responses.add(DocumentMessage::StartTransaction);
responses.add(DocumentMessage::AddTransaction);
self.layer = LayerNodeIdentifier::new_unchecked(NodeId(generate_uuid()));
@@ -320,7 +320,8 @@ impl TextToolData {
}
fn can_edit_selected(document: &DocumentMessageHandler) -> Option<LayerNodeIdentifier> {
let mut selected_layers = document.network_interface.selected_nodes(&[]).unwrap().selected_layers(document.metadata());
let selected_nodes = document.network_interface.selected_nodes(&[]).unwrap();
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
let layer = selected_layers.next()?;
// Check that only one layer is selected