Skip to content

Commit 2a99d60

Browse files
authored
Merge branch 'master' into fix-min-max-macro
2 parents ad7a721 + c486c6e commit 2a99d60

File tree

22 files changed

+645
-42
lines changed

22 files changed

+645
-42
lines changed

demo-artwork/parametric-dunescape.graphite

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

editor/src/dispatcher.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,7 @@ mod test {
515515
// include_str!("../../demo-artwork/isometric-fountain.graphite"),
516516
// include_str!("../../demo-artwork/painted-dreams.graphite"),
517517
// include_str!("../../demo-artwork/procedural-string-lights.graphite"),
518+
// include_str!("../../demo-artwork/parametric-dunescape.graphite"),
518519
// include_str!("../../demo-artwork/red-dress.graphite"),
519520
// include_str!("../../demo-artwork/valley-of-spires.graphite"),
520521
// ];

editor/src/messages/dialog/simple_dialogs/demo_artwork_dialog.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ use crate::messages::prelude::*;
55
pub struct DemoArtworkDialog;
66

77
/// `(name, thumbnail, filename)`
8-
pub const ARTWORK: [(&str, &str, &str); 6] = [
8+
pub const ARTWORK: [(&str, &str, &str); 7] = [
99
("Isometric Fountain", "ThumbnailIsometricFountain", "isometric-fountain.graphite"),
1010
("Changing Seasons", "ThumbnailChangingSeasons", "changing-seasons.graphite"),
1111
("Painted Dreams", "ThumbnailPaintedDreams", "painted-dreams.graphite"),
12+
("Parametric Dunescape", "ThumbnailParametricDunescape", "parametric-dunescape.graphite"),
1213
("Red Dress", "ThumbnailRedDress", "red-dress.graphite"),
1314
("Procedural String Lights", "ThumbnailProceduralStringLights", "procedural-string-lights.graphite"),
1415
("Valley of Spires", "ThumbnailValleyOfSpires", "valley-of-spires.graphite"),
@@ -28,7 +29,7 @@ impl DialogLayoutHolder for DemoArtworkDialog {
2829
impl LayoutHolder for DemoArtworkDialog {
2930
fn layout(&self) -> Layout {
3031
let mut rows_of_images_with_buttons: Vec<_> = ARTWORK
31-
.chunks(3)
32+
.chunks(4)
3233
.flat_map(|chunk| {
3334
fn make_dialog(name: &str, filename: &str) -> Message {
3435
DialogMessage::CloseDialogAndThen {

editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use super::node_properties;
22
use super::utility_types::FrontendNodeType;
33
use crate::messages::layout::utility_types::widget_prelude::*;
44
use crate::messages::portfolio::document::utility_types::network_interface::{
5-
DocumentNodeMetadata, DocumentNodePersistentMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodePersistentMetadata, NodePosition, NodeTemplate,
6-
NodeTypePersistentMetadata, NumberInputSettings, PropertiesRow, Vec2InputSettings, WidgetOverride,
5+
DocumentNodeMetadata, DocumentNodePersistentMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata, NumberInputSettings,
6+
PropertiesRow, Vec2InputSettings, WidgetOverride,
77
};
88
use crate::messages::portfolio::utility_types::PersistentData;
99
use crate::messages::prelude::Message;

editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,7 @@ pub struct OverlaysMessageHandler {
1616

1717
impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessageHandler {
1818
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, data: OverlaysMessageData) {
19-
let OverlaysMessageData {
20-
overlays_visible,
21-
ipp,
22-
device_pixel_ratio,
23-
} = data;
19+
let OverlaysMessageData { overlays_visible, ipp, .. } = data;
2420

2521
match message {
2622
#[cfg(target_arch = "wasm32")]
@@ -30,6 +26,8 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessag
3026
use glam::{DAffine2, DVec2};
3127
use wasm_bindgen::JsCast;
3228

29+
let device_pixel_ratio = data.device_pixel_ratio;
30+
3331
let canvas = match &self.canvas {
3432
Some(canvas) => canvas,
3533
None => {

editor/src/messages/tool/tool_messages/line_tool.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,3 +421,103 @@ fn generate_line(tool_data: &mut LineToolData, snap_data: SnapData, lock_angle:
421421

422422
document_points
423423
}
424+
425+
#[cfg(test)]
426+
mod test_line_tool {
427+
use crate::{messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer, test_utils::test_prelude::*};
428+
use graph_craft::document::value::TaggedValue;
429+
430+
async fn get_line_node_inputs(editor: &mut EditorTestUtils) -> Option<(DVec2, DVec2)> {
431+
let document = editor.active_document();
432+
let network_interface = &document.network_interface;
433+
let node_id = network_interface
434+
.selected_nodes()
435+
.selected_visible_and_unlocked_layers(network_interface)
436+
.filter_map(|layer| {
437+
let node_inputs = NodeGraphLayer::new(layer, &network_interface).find_node_inputs("Line")?;
438+
let (Some(&TaggedValue::DVec2(start)), Some(&TaggedValue::DVec2(end))) = (node_inputs[1].as_value(), node_inputs[2].as_value()) else {
439+
return None;
440+
};
441+
Some((start, end))
442+
})
443+
.next();
444+
node_id
445+
}
446+
447+
#[tokio::test]
448+
async fn test_line_tool_basicdraw() {
449+
let mut editor = EditorTestUtils::create();
450+
editor.new_document().await;
451+
editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::empty()).await;
452+
if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await {
453+
match (start_input, end_input) {
454+
(start_input, end_input) => {
455+
assert!((start_input - DVec2::ZERO).length() < 1.0, "Start point should be near (0,0)");
456+
assert!((end_input - DVec2::new(100.0, 100.0)).length() < 1.0, "End point should be near (100,100)");
457+
}
458+
}
459+
}
460+
}
461+
462+
#[tokio::test]
463+
async fn test_line_tool_ctrl_anglelock() {
464+
let mut editor = EditorTestUtils::create();
465+
editor.new_document().await;
466+
editor.drag_tool(ToolType::Line, 0., 0., 100., 100., ModifierKeys::CONTROL).await;
467+
if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await {
468+
match (start_input, end_input) {
469+
(start_input, end_input) => {
470+
let line_vec = end_input - start_input;
471+
let original_angle = line_vec.angle_to(DVec2::X);
472+
editor.drag_tool(ToolType::Line, 0., 0., 200., 50., ModifierKeys::CONTROL).await;
473+
if let Some((updated_start, updated_end)) = get_line_node_inputs(&mut editor).await {
474+
match (updated_start, updated_end) {
475+
(updated_start, updated_end) => {
476+
let updated_line_vec = updated_end - updated_start;
477+
let updated_angle = updated_line_vec.angle_to(DVec2::X);
478+
assert!((original_angle - updated_angle).abs() < 0.1, "Line angle should be locked when Ctrl is kept pressed");
479+
assert!((updated_start - updated_end).length() > 1.0, "Line should be able to change length when Ctrl is kept pressed");
480+
}
481+
}
482+
}
483+
}
484+
}
485+
}
486+
}
487+
488+
#[tokio::test]
489+
async fn test_line_tool_alt() {
490+
let mut editor = EditorTestUtils::create();
491+
editor.new_document().await;
492+
editor.drag_tool(ToolType::Line, 100., 100., 200., 100., ModifierKeys::ALT).await;
493+
if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await {
494+
match (start_input, end_input) {
495+
(start_input, end_input) => {
496+
let expected_start = DVec2::new(0., 100.);
497+
let expected_end = DVec2::new(200., 100.);
498+
assert!((start_input - expected_start).length() < 1.0, "start point should be near (0,100)");
499+
assert!((end_input - expected_end).length() < 1.0, "end point should be near (200,100)");
500+
}
501+
}
502+
}
503+
}
504+
505+
#[tokio::test]
506+
async fn test_line_tool_alt_shift_drag() {
507+
let mut editor = EditorTestUtils::create();
508+
editor.new_document().await;
509+
editor.drag_tool(ToolType::Line, 100., 100., 150., 120., ModifierKeys::ALT | ModifierKeys::SHIFT).await;
510+
if let Some((start_input, end_input)) = get_line_node_inputs(&mut editor).await {
511+
match (start_input, end_input) {
512+
(start_input, end_input) => {
513+
let line_vec = end_input - start_input;
514+
let angle_radians = line_vec.angle_to(DVec2::X);
515+
let angle_degrees = angle_radians.to_degrees();
516+
let nearest_angle = (angle_degrees / 15.0).round() * 15.0;
517+
518+
assert!((angle_degrees - nearest_angle).abs() < 1.0, "Angle should snap to the nearest 15 degrees");
519+
}
520+
}
521+
}
522+
}
523+
}

editor/src/messages/tool/tool_messages/pen_tool.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,13 +1601,22 @@ impl Fsm for PenToolFsmState {
16011601
PenToolFsmState::Ready
16021602
}
16031603
(_, PenToolMessage::Abort) => {
1604+
let should_delete_layer = tool_data.latest_points.len() == 1;
1605+
16041606
responses.add(DocumentMessage::AbortTransaction);
16051607
tool_data.handle_end = None;
16061608
tool_data.latest_points.clear();
16071609
tool_data.point_index = 0;
16081610
tool_data.draw_mode = DrawMode::BreakPath;
16091611
tool_data.snap_manager.cleanup(responses);
16101612

1613+
if should_delete_layer && layer.is_some() {
1614+
responses.add(NodeGraphMessage::DeleteNodes {
1615+
node_ids: vec![layer.unwrap().to_node()],
1616+
delete_children: true,
1617+
});
1618+
responses.add(NodeGraphMessage::RunDocumentGraph);
1619+
}
16111620
responses.add(OverlaysMessage::Draw);
16121621

16131622
PenToolFsmState::Ready

editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -702,12 +702,14 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
702702

703703
#[cfg(test)]
704704
mod test_transform_layer {
705-
use crate::messages::portfolio::document::graph_operation::transform_utils;
705+
use crate::messages::{
706+
portfolio::document::graph_operation::{transform_utils, utility_types::ModifyInputsContext},
707+
prelude::Message,
708+
tool::transform_layer::transform_layer_message_handler::VectorModificationType,
709+
};
706710
use crate::test_utils::test_prelude::*;
707-
// Use ModifyInputsContext to locate the transform node
708-
use crate::messages::portfolio::document::graph_operation::utility_types::{ModifyInputsContext, TransformIn};
709-
use crate::messages::prelude::Message;
710711
use glam::DAffine2;
712+
use graphene_core::vector::PointId;
711713
use std::collections::VecDeque;
712714

713715
async fn get_layer_transform(editor: &mut EditorTestUtils, layer: LayerNodeIdentifier) -> Option<DAffine2> {
@@ -1064,4 +1066,35 @@ mod test_transform_layer {
10641066
let angle_change = ((angle_change % 360.0) + 360.0) % 360.0;
10651067
assert!((angle_change - 90.0).abs() < 0.1, "Expected rotation of 90 degrees, got: {}", angle_change);
10661068
}
1069+
1070+
#[tokio::test]
1071+
async fn test_grs_single_anchor() {
1072+
let mut editor = EditorTestUtils::create();
1073+
editor.new_document().await;
1074+
editor.handle_message(DocumentMessage::CreateEmptyFolder).await;
1075+
let document = editor.active_document();
1076+
let layer = document.metadata().all_layers().next().unwrap();
1077+
1078+
let point_id = PointId::generate();
1079+
let modification_type = VectorModificationType::InsertPoint {
1080+
id: point_id,
1081+
position: DVec2::new(100.0, 100.0),
1082+
};
1083+
editor.handle_message(GraphOperationMessage::Vector { layer, modification_type }).await;
1084+
editor.handle_message(ToolMessage::ActivateTool { tool_type: ToolType::Select }).await;
1085+
1086+
// Testing grab operation - just checking that it doesn't crash.
1087+
editor.handle_message(TransformLayerMessage::BeginGrab).await;
1088+
editor.move_mouse(150.0, 150.0, ModifierKeys::empty(), MouseKeys::NONE).await;
1089+
editor
1090+
.handle_message(TransformLayerMessage::PointerMove {
1091+
slow_key: Key::Shift,
1092+
increments_key: Key::Control,
1093+
})
1094+
.await;
1095+
editor.handle_message(TransformLayerMessage::ApplyTransformOperation { final_transform: true }).await;
1096+
1097+
let final_transform = get_layer_transform(&mut editor, layer).await;
1098+
assert!(final_transform.is_some(), "Transform node should exist after grab operation");
1099+
}
10671100
}
Loading

frontend/src/utility-functions/images.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import ThumbnailChangingSeasons from "@graphite-frontend/assets/images/demo-artwork/thumbnail-changing-seasons.png";
55
import ThumbnailIsometricFountain from "@graphite-frontend/assets/images/demo-artwork/thumbnail-isometric-fountain.png";
66
import ThumbnailPaintedDreams from "@graphite-frontend/assets/images/demo-artwork/thumbnail-painted-dreams.png";
7+
import ThumbnailParametricDunescape from "@graphite-frontend/assets/images/demo-artwork/thumbnail-parametric-dunescape.png";
78
import ThumbnailProceduralStringLights from "@graphite-frontend/assets/images/demo-artwork/thumbnail-procedural-string-lights.png";
89
import ThumbnailRedDress from "@graphite-frontend/assets/images/demo-artwork/thumbnail-red-dress.png";
910
import ThumbnailValleyOfSpires from "@graphite-frontend/assets/images/demo-artwork/thumbnail-valley-of-spires.png";
@@ -12,6 +13,7 @@ const DEMO_ARTWORK = {
1213
ThumbnailChangingSeasons,
1314
ThumbnailIsometricFountain,
1415
ThumbnailPaintedDreams,
16+
ThumbnailParametricDunescape,
1517
ThumbnailProceduralStringLights,
1618
ThumbnailRedDress,
1719
ThumbnailValleyOfSpires,

0 commit comments

Comments
 (0)