Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
279 changes: 278 additions & 1 deletion editor/src/messages/tool/tool_messages/pen_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2438,11 +2438,27 @@ impl Fsm for PenToolFsmState {
mod test_pen_tool {
use crate::messages::input_mapper::utility_types::pointer::EditorPointerState;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::utility_types::misc::GroupFolderType;
use crate::test_utils::test_prelude::*;
use glam::DAffine2;
use graphene_std::vector::misc::ManipulatorPointId;
use graphene_std::vector::misc::{BooleanOperation, ManipulatorPointId};
use graphene_std::vector::{SegmentId, Vector};

fn assert_expected_anchors(editor: &EditorTestUtils, expected_anchors: &[DVec2]) {
let (layer, vector) = drawn_path(editor).expect("Expected a drawn path");
let layer_to_viewport = editor.active_document().metadata().transform_to_viewport(layer);
let mut viewport_points: Vec<DVec2> = vector.point_domain.positions().iter().map(|&pos| layer_to_viewport.transform_point2(pos)).collect();

for (expected_index, &expected_position) in expected_anchors.iter().enumerate() {
let Some(viewport_index) = viewport_points.iter().position(|viewport| viewport.distance_squared(expected_position) < 1e-10) else {
panic!("The expected anchor index {expected_index} and position {expected_position} was not found in the actual anchors {viewport_points:?}");
};
println!("Successfully found expected position {expected_position} (index {expected_index}) in viewport points as index {viewport_index}");
viewport_points.remove(viewport_index);
}
assert!(viewport_points.is_empty(), "Viewport point(s) were not matched: {viewport_points:?}");
}

/// The single Pen-drawn path layer and its vector, or `None` once the layer has been undone away.
fn drawn_path(editor: &EditorTestUtils) -> Option<(LayerNodeIdentifier, Vector)> {
let document = editor.active_document();
Expand Down Expand Up @@ -2504,6 +2520,267 @@ mod test_pen_tool {
const C: DVec2 = DVec2::new(200., 200.);
const D: DVec2 = DVec2::new(100., 200.);

async fn create_a_rectangle() -> EditorTestUtils {
let mut editor = EditorTestUtils::create();

editor.new_document().await;

editor.select_primary_color(Color::RED).await;
// Draw a rectangle not at the origin (so will end up with a non-identity transform)
editor.draw_rect(A.x, A.y, C.x, C.y).await;

editor
}

/// Using the path tool to merge layers (by setting the endpoint to an anchor of another layer) should produce only expected anchor positions.
#[tokio::test]
async fn merging_layers_simple() {
let mut editor = create_a_rectangle().await;

// Start the pen somewhere random
let pen_start = DVec2::new(999., 999.);
click_pen(&mut editor, pen_start).await;
// Connect to the top right of the rectangle
click_pen(&mut editor, B).await;

// Validate that these anchors are the only ones that exist
let expected_anchors = [A, B, C, D, pen_start];
assert_expected_anchors(&editor, &expected_anchors);
}

#[tokio::test]
async fn merging_two_pen_paths() {
let mut editor = EditorTestUtils::create();
editor.new_document().await;

// Create First Pen path
click_pen(&mut editor, A).await;
click_pen(&mut editor, C).await;

// Abort the first pen process
editor.handle_message(ToolMessage::Pen(PenToolMessage::Abort)).await;

// Create a second pen stroke
click_pen(&mut editor, B).await;
click_pen(&mut editor, C).await;

// Validate that these anchors are the only ones that exist
let expected_anchors = [A, B, C];
assert_expected_anchors(&editor, &expected_anchors);
}

#[tokio::test]
async fn merging_layers_in_group_with_offset() {
let mut editor = create_a_rectangle().await;

// Group rectangle
editor
.handle_message(DocumentMessage::GroupSelectedLayers {
group_folder_type: GroupFolderType::Layer,
})
.await;

let group = editor.active_document().metadata().all_layers().next().unwrap();
let offset = DVec2::new(50., 60.);
editor
.handle_message(GraphOperationMessage::TransformSet {
layer: group,
transform: DAffine2::from_translation(offset),
transform_in: TransformIn::Local,
skip_rerender: false,
})
.await;

// Start the pen somewhere random
let pen_start = DVec2::new(999., 999.);
click_pen(&mut editor, pen_start).await;
// Connect to the top right of the rectangle (offset by the group transform)
click_pen(&mut editor, B + offset).await;

let expected_anchors = [A + offset, B + offset, C + offset, D + offset, pen_start];
assert_expected_anchors(&editor, &expected_anchors);
}

#[tokio::test]
async fn merging_layers_in_different_groups_with_offsets() {
let mut editor = create_a_rectangle().await;

// Group rectangle into Group 1
editor
.handle_message(DocumentMessage::GroupSelectedLayers {
group_folder_type: GroupFolderType::Layer,
})
.await;

let group1 = editor.active_document().metadata().all_layers().next().unwrap();
let offset1 = DVec2::new(50., 60.);
editor
.handle_message(GraphOperationMessage::TransformSet {
layer: group1,
transform: DAffine2::from_translation(offset1),
transform_in: TransformIn::Local,
skip_rerender: false,
})
.await;

// Nest Group 1 inside Group 2 with a different offset
editor
.handle_message(DocumentMessage::GroupSelectedLayers {
group_folder_type: GroupFolderType::Layer,
})
.await;

let group2 = editor.active_document().metadata().all_layers().next().unwrap();
let offset2 = DVec2::new(30., -20.);
editor
.handle_message(GraphOperationMessage::TransformSet {
layer: group2,
transform: DAffine2::from_translation(offset2),
transform_in: TransformIn::Local,
skip_rerender: false,
})
.await;

let total_offset = offset1 + offset2;

// Start the pen somewhere random
let pen_start = DVec2::new(999., 999.);
click_pen(&mut editor, pen_start).await;
// Connect to the top right of the rectangle (offset by the nested group transforms)
click_pen(&mut editor, B + total_offset).await;

let expected_anchors = [A + total_offset, B + total_offset, C + total_offset, D + total_offset, pen_start];
assert_expected_anchors(&editor, &expected_anchors);
}

#[tokio::test]
async fn merging_layers_in_artboard_with_offset() {
let mut editor = EditorTestUtils::create();
editor.new_document().await;

let offset = DVec2::new(60., 70.);
editor.drag_tool(ToolType::Artboard, 0., 0., 500., 500., ModifierKeys::empty()).await;
let artboard = editor.active_document().metadata().all_layers().next().unwrap();
editor
.handle_message(GraphOperationMessage::TransformSet {
layer: artboard,
transform: DAffine2::from_translation(offset),
transform_in: TransformIn::Local,
skip_rerender: false,
})
.await;

editor.select_primary_color(Color::RED).await;
editor.draw_rect(A.x + offset.x, A.y + offset.y, C.x + offset.x, C.y + offset.y).await;

let pen_start = DVec2::new(999., 999.);
click_pen(&mut editor, pen_start).await;
click_pen(&mut editor, B + offset).await;

let expected_anchors = [A + offset, B + offset, C + offset, D + offset, pen_start];
assert_expected_anchors(&editor, &expected_anchors);
}

#[tokio::test]
async fn merging_layers_in_artboards_with_different_offsets() {
let mut editor = EditorTestUtils::create();
editor.new_document().await;

// Create first artboard with offset
editor.drag_tool(ToolType::Artboard, 0., 0., 400., 400., ModifierKeys::empty()).await;
let artboard1 = editor.active_document().metadata().all_layers().next().unwrap();
let offset1 = DVec2::new(50., 60.);
editor
.handle_message(GraphOperationMessage::TransformSet {
layer: artboard1,
transform: DAffine2::from_translation(offset1),
transform_in: TransformIn::Local,
skip_rerender: false,
})
.await;

// Draw rectangle in first artboard
editor.select_primary_color(Color::RED).await;
editor.draw_rect(A.x + offset1.x, A.y + offset1.y, C.x + offset1.x, C.y + offset1.y).await;

// Create second artboard with different offset
let offset2 = DVec2::new(-30., 40.);
let artboard2_origin = DVec2::new(600., 600.);
editor
.drag_tool(
ToolType::Artboard,
artboard2_origin.x,
artboard2_origin.y,
artboard2_origin.x + 400.,
artboard2_origin.y + 400.,
ModifierKeys::empty(),
)
.await;
let artboard2 = editor.active_document().metadata().all_layers().next().unwrap();
editor
.handle_message(GraphOperationMessage::TransformSet {
layer: artboard2,
transform: DAffine2::from_translation(offset2),
transform_in: TransformIn::Local,
skip_rerender: false,
})
.await;

// Start pen in second artboard
let pen_start = artboard2_origin + offset2 + DVec2::new(50., 50.);
click_pen(&mut editor, pen_start).await;

// Connect to anchor B of the rectangle in the first artboard
click_pen(&mut editor, B + offset1).await;

let expected_anchors = [A + offset1, B + offset1, C + offset1, D + offset1, pen_start];
assert_expected_anchors(&editor, &expected_anchors);
}

async fn test_boolean_operation(operation: BooleanOperation) {
let mut editor = EditorTestUtils::create();
editor.new_document().await;

editor.select_primary_color(Color::RED).await;
editor.draw_rect(A.x, A.y, C.x, C.y).await;
editor.draw_rect(150., 150., 250., 250.).await;

// Select both rectangles and create boolean operation layer
let layers: Vec<_> = editor.active_document().metadata().all_layers().collect();
editor
.handle_message(NodeGraphMessage::SelectedNodesSet {
nodes: layers.iter().map(|l| l.to_node()).collect(),
})
.await;

editor
.handle_message(DocumentMessage::GroupSelectedLayers {
group_folder_type: GroupFolderType::BooleanOperation(operation),
})
.await;

let bool_layer = editor.active_document().metadata().all_layers().next().unwrap();
let bool_vector = editor.active_document().network_interface.compute_modified_vector(bool_layer).expect("Boolean layer vector");
let bool_to_viewport = editor.active_document().metadata().transform_to_viewport(bool_layer);
let mut expected_anchors: Vec<DVec2> = bool_vector.point_domain.positions().iter().map(|&pos| bool_to_viewport.transform_point2(pos)).collect();

// Connect pen to one of the anchors of the boolean operation
let connect_anchor = expected_anchors[0];
let pen_start = DVec2::new(999., 999.);
click_pen(&mut editor, pen_start).await;
click_pen(&mut editor, connect_anchor).await;

expected_anchors.push(pen_start);
assert_expected_anchors(&editor, &expected_anchors);
}

#[tokio::test]
async fn merging_layers_with_different_boolean_outputs() {
for operation in [BooleanOperation::Union, BooleanOperation::SubtractFront, BooleanOperation::Intersect, BooleanOperation::Difference] {
test_boolean_operation(operation).await;
}
}

#[tokio::test]
async fn each_segment_and_the_closing_click_are_their_own_history_steps() {
let mut editor = EditorTestUtils::create();
Expand Down