Skip to content

Commit 49ca9e6

Browse files
authored
Merge branch 'GraphiteEditor:master' into granular-overlays-settings
2 parents 676cd4c + c156c0a commit 49ca9e6

File tree

12 files changed

+233
-124
lines changed

12 files changed

+233
-124
lines changed

editor/src/messages/portfolio/document/graph_operation/utility_types.rs

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -413,21 +413,13 @@ impl<'a> ModifyInputsContext<'a> {
413413
pub fn transform_set_direct(&mut self, transform: DAffine2, skip_rerender: bool, transform_node_id: Option<NodeId>) {
414414
// If the Transform node didn't exist yet, create it now
415415
let Some(transform_node_id) = transform_node_id.or_else(|| {
416-
// Check if the transform is the identity transform within an epsilon
417-
let is_identity = {
418-
let transform = transform.to_scale_angle_translation();
419-
let identity = DAffine2::IDENTITY.to_scale_angle_translation();
420-
421-
(transform.0.x - identity.0.x).abs() < 1e-6
422-
&& (transform.0.y - identity.0.y).abs() < 1e-6
423-
&& (transform.1 - identity.1).abs() < 1e-6
424-
&& (transform.2.x - identity.2.x).abs() < 1e-6
425-
&& (transform.2.y - identity.2.y).abs() < 1e-6
426-
};
427-
428-
// We don't want to pollute the graph with an unnecessary Transform node, so we avoid creating and setting it by returning None
429-
if is_identity {
430-
return None;
416+
// Check if the transform is the identity transform and if so, don't create a new Transform node
417+
if let Some((scale, angle, translation)) = (transform.matrix2.determinant() != 0.).then(|| transform.to_scale_angle_translation()) {
418+
// Check if the transform is the identity transform within an epsilon
419+
if scale.x.abs() < 1e-6 && scale.y.abs() < 1e-6 && angle.abs() < 1e-6 && translation.x.abs() < 1e-6 && translation.y.abs() < 1e-6 {
420+
// We don't want to pollute the graph with an unnecessary Transform node, so we avoid creating and setting it by returning None
421+
return None;
422+
}
431423
}
432424

433425
// Create the Transform node

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

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -302,30 +302,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
302302
return;
303303
}
304304

305-
let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {
306-
log::error!("Could not get network metadata in NodeGraphMessage::EnterNestedNetwork");
307-
return;
308-
};
309-
310-
let click = ipp.mouse.position;
311-
let node_graph_point = network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(click);
312-
313-
// Check if clicked on empty area (no node, no input/output connector)
314-
let clicked_id = network_interface.node_from_click(click, selection_network_path);
315-
let clicked_input = network_interface.input_connector_from_click(click, selection_network_path);
316-
let clicked_output = network_interface.output_connector_from_click(click, selection_network_path);
317-
318-
if clicked_id.is_none() && clicked_input.is_none() && clicked_output.is_none() && self.context_menu.is_none() {
319-
// Create a context menu with node creation options
320-
self.context_menu = Some(ContextMenuInformation {
321-
context_menu_coordinates: (node_graph_point.x as i32, node_graph_point.y as i32),
322-
context_menu_data: ContextMenuData::CreateNode { compatible_type: None },
323-
});
324-
325-
responses.add(FrontendMessage::UpdateContextMenuInformation {
326-
context_menu_information: self.context_menu.clone(),
327-
});
328-
}
329305
let Some(node_id) = network_interface.node_from_click(ipp.mouse.position, selection_network_path) else {
330306
return;
331307
};

editor/src/messages/portfolio/document/utility_types/document_metadata.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ impl DocumentMetadata {
8080
}
8181

8282
pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
83+
// We're not allowed to convert the root parent to a node id
84+
if layer == LayerNodeIdentifier::ROOT_PARENT {
85+
return self.document_to_viewport;
86+
}
87+
8388
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
8489
let local_transform = self.local_transforms.get(&layer.to_node()).copied().unwrap_or_default();
8590

editor/src/messages/portfolio/document/utility_types/network_interface.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1169,7 +1169,7 @@ impl NodeNetworkInterface {
11691169
if is_layer && *reference == Some("Merge".to_string()) {
11701170
"Untitled Layer".to_string()
11711171
} else {
1172-
reference.clone().unwrap_or("Untitled node".to_string())
1172+
reference.clone().unwrap_or("Untitled Node".to_string())
11731173
}
11741174
} else {
11751175
self.display_name(node_id, network_path)

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,8 @@ impl Fsm for GradientToolFsmState {
527527

528528
#[cfg(test)]
529529
mod test_gradient {
530+
use crate::messages::input_mapper::utility_types::input_mouse::EditorMouseState;
531+
use crate::messages::input_mapper::utility_types::input_mouse::ScrollDelta;
530532
use crate::messages::portfolio::document::{graph_operation::utility_types::TransformIn, utility_types::misc::GroupFolderType};
531533
pub use crate::test_utils::test_prelude::*;
532534
use glam::DAffine2;
@@ -650,4 +652,57 @@ mod test_gradient {
650652
assert!(transform.transform_point2(gradient.start).abs_diff_eq(DVec2::new(2., 3.), 1e-10));
651653
assert!(transform.transform_point2(gradient.end).abs_diff_eq(DVec2::new(24., 4.), 1e-10));
652654
}
655+
656+
#[tokio::test]
657+
async fn double_click_insert_stop() {
658+
let mut editor = EditorTestUtils::create();
659+
editor.new_document().await;
660+
661+
editor.drag_tool(ToolType::Rectangle, -5., -3., 100., 100., ModifierKeys::empty()).await;
662+
663+
editor.select_primary_color(Color::GREEN).await;
664+
editor.select_secondary_color(Color::BLUE).await;
665+
666+
editor.drag_tool(ToolType::Gradient, 0., 0., 100., 0., ModifierKeys::empty()).await;
667+
668+
// Get initial gradient state (should have 2 stops)
669+
let initial_fills = get_fills(&mut editor).await;
670+
assert_eq!(initial_fills.len(), 1);
671+
let (initial_fill, _) = initial_fills.first().unwrap();
672+
let initial_gradient = initial_fill.as_gradient().unwrap();
673+
assert_eq!(initial_gradient.stops.len(), 2);
674+
675+
editor.select_tool(ToolType::Gradient).await;
676+
677+
// Simulate a double click
678+
let click_position = DVec2::new(50., 0.);
679+
let modifier_keys = ModifierKeys::empty();
680+
681+
// Send the DoubleClick event
682+
editor
683+
.handle_message(InputPreprocessorMessage::DoubleClick {
684+
editor_mouse_state: EditorMouseState {
685+
editor_position: click_position,
686+
mouse_keys: MouseKeys::LEFT,
687+
scroll_delta: ScrollDelta::default(),
688+
},
689+
modifier_keys,
690+
})
691+
.await;
692+
693+
// Check that a new stop has been added
694+
let updated_fills = get_fills(&mut editor).await;
695+
assert_eq!(updated_fills.len(), 1);
696+
let (updated_fill, _) = updated_fills.first().unwrap();
697+
let updated_gradient = updated_fill.as_gradient().unwrap();
698+
699+
assert_eq!(updated_gradient.stops.len(), 3);
700+
701+
let positions: Vec<f64> = updated_gradient.stops.iter().map(|(pos, _)| *pos).collect();
702+
assert!(
703+
positions.iter().any(|pos| (pos - 0.5).abs() < 0.1),
704+
"Expected to find a stop near position 0.5, but found: {:?}",
705+
positions
706+
);
707+
}
653708
}

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

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ struct PenToolData {
356356
path_closed: bool,
357357

358358
handle_mode: HandleMode,
359+
prior_segment_layer: Option<LayerNodeIdentifier>,
359360
prior_segment_endpoint: Option<PointId>,
360361
prior_segment: Option<SegmentId>,
361362
handle_type: TargetHandle,
@@ -380,6 +381,13 @@ impl PenToolData {
380381
self.latest_points.push(point);
381382
}
382383

384+
fn cleanup(&mut self, responses: &mut VecDeque<Message>) {
385+
self.handle_end = None;
386+
self.latest_points.clear();
387+
self.point_index = 0;
388+
self.snap_manager.cleanup(responses);
389+
}
390+
383391
/// Check whether target handle is primary, end, or `self.handle_end`
384392
fn check_end_handle_type(&self, vector_data: &VectorData) -> TargetHandle {
385393
match (self.handle_end, self.prior_segment_endpoint, self.prior_segment, self.path_closed) {
@@ -1253,9 +1261,13 @@ impl PenToolData {
12531261
let viewport = document.metadata().document_to_viewport.transform_point2(snapped.snapped_point_document);
12541262

12551263
let tolerance = crate::consts::SNAP_POINT_TOLERANCE;
1264+
self.prior_segment = None;
1265+
self.prior_segment_endpoint = None;
1266+
self.prior_segment_layer = None;
12561267

12571268
if let Some((layer, point, _position)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences) {
12581269
self.prior_segment_endpoint = Some(point);
1270+
self.prior_segment_layer = Some(layer);
12591271
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
12601272
let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
12611273
self.prior_segment = segment;
@@ -1905,16 +1917,62 @@ impl Fsm for PenToolFsmState {
19051917

19061918
state
19071919
}
1908-
(PenToolFsmState::DraggingHandle(..) | PenToolFsmState::PlacingAnchor, PenToolMessage::Confirm) => {
1920+
(PenToolFsmState::DraggingHandle(..), PenToolMessage::Confirm) => {
1921+
// Confirm to end path
1922+
if let Some((vector_data, layer)) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)).zip(layer) {
1923+
let single_point_in_layer = vector_data.point_domain.ids().len() == 1;
1924+
tool_data.finish_placing_handle(SnapData::new(document, input), transform, preferences, responses);
1925+
let latest_points = tool_data.latest_points.len() == 1;
1926+
1927+
if latest_points && single_point_in_layer {
1928+
responses.add(NodeGraphMessage::DeleteNodes {
1929+
node_ids: vec![layer.to_node()],
1930+
delete_children: true,
1931+
});
1932+
responses.add(NodeGraphMessage::RunDocumentGraph);
1933+
} else if (latest_points && tool_data.prior_segment_endpoint.is_none())
1934+
|| (tool_data.prior_segment_endpoint.is_some() && tool_data.prior_segment_layer != Some(layer) && latest_points)
1935+
{
1936+
let vector_modification = VectorModificationType::RemovePoint {
1937+
id: tool_data.latest_point().unwrap().id,
1938+
};
1939+
responses.add(GraphOperationMessage::Vector {
1940+
layer,
1941+
modification_type: vector_modification,
1942+
});
1943+
responses.add(PenToolMessage::Abort);
1944+
} else {
1945+
responses.add(DocumentMessage::EndTransaction);
1946+
}
1947+
}
1948+
1949+
tool_data.cleanup(responses);
1950+
tool_data.cleanup_target_selections(shape_editor, layer, document, responses);
1951+
1952+
responses.add(OverlaysMessage::Draw);
1953+
1954+
PenToolFsmState::Ready
1955+
}
1956+
(PenToolFsmState::PlacingAnchor, PenToolMessage::Confirm) => {
19091957
responses.add(DocumentMessage::EndTransaction);
1910-
tool_data.handle_end = None;
1911-
tool_data.latest_points.clear();
1912-
tool_data.point_index = 0;
1913-
tool_data.snap_manager.cleanup(responses);
1958+
tool_data.cleanup(responses);
19141959
tool_data.cleanup_target_selections(shape_editor, layer, document, responses);
19151960

19161961
PenToolFsmState::Ready
19171962
}
1963+
(PenToolFsmState::DraggingHandle(..), PenToolMessage::Abort) => {
1964+
if tool_data.handle_end.is_none() {
1965+
responses.add(DocumentMessage::AbortTransaction);
1966+
tool_data.cleanup(responses);
1967+
tool_data.cleanup_target_selections(shape_editor, layer, document, responses);
1968+
1969+
PenToolFsmState::Ready
1970+
} else {
1971+
tool_data
1972+
.place_anchor(SnapData::new(document, input), transform, input.mouse.position, preferences, responses)
1973+
.unwrap_or(PenToolFsmState::Ready)
1974+
}
1975+
}
19181976
(_, PenToolMessage::Abort) => {
19191977
let should_delete_layer = if layer.is_some() {
19201978
let vector_data = document.network_interface.compute_modified_vector(layer.unwrap()).unwrap();
@@ -1924,10 +1982,7 @@ impl Fsm for PenToolFsmState {
19241982
};
19251983

19261984
responses.add(DocumentMessage::AbortTransaction);
1927-
tool_data.handle_end = None;
1928-
tool_data.latest_points.clear();
1929-
tool_data.point_index = 0;
1930-
tool_data.snap_manager.cleanup(responses);
1985+
tool_data.cleanup(responses);
19311986
tool_data.cleanup_target_selections(shape_editor, layer, document, responses);
19321987

19331988
if should_delete_layer {
@@ -1989,8 +2044,8 @@ impl Fsm for PenToolFsmState {
19892044
let mut dragging_hint_data = HintData(Vec::new());
19902045
dragging_hint_data.0.push(HintGroup(vec![
19912046
HintInfo::mouse(MouseMotion::Rmb, ""),
1992-
HintInfo::keys([Key::Escape], "").prepend_slash(),
1993-
HintInfo::keys([Key::Enter], "End Path").prepend_slash(),
2047+
HintInfo::keys([Key::Escape], "Cancel Segment").prepend_slash(),
2048+
HintInfo::keys([Key::Enter], "End Path"),
19942049
]));
19952050

19962051
let mut toggle_group = match mode {

node-graph/gcore/src/graphic_element/renderer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
mod quad;
22
mod rect;
33

4-
use crate::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WEIGHT};
54
use crate::raster::image::ImageFrameTable;
65
use crate::raster::{BlendMode, Image};
76
use crate::transform::{Footprint, Transform};
@@ -465,6 +464,7 @@ impl GraphicElementRendered for VectorDataTable {
465464

466465
#[cfg(feature = "vello")]
467466
fn render_to_vello(&self, scene: &mut Scene, parent_transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) {
467+
use crate::consts::{LAYER_OUTLINE_STROKE_COLOR, LAYER_OUTLINE_STROKE_WEIGHT};
468468
use crate::vector::style::{GradientType, LineCap, LineJoin};
469469
use vello::kurbo::{Cap, Join};
470470
use vello::peniko;

node-graph/gcore/src/vector/vector_data.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,22 @@ impl VectorData {
194194

195195
/// Compute the bounding boxes of the subpaths with the specified transform
196196
pub fn bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
197-
self.segment_bezier_iter()
197+
let combine = |[a_min, a_max]: [DVec2; 2], [b_min, b_max]: [DVec2; 2]| [a_min.min(b_min), a_max.max(b_max)];
198+
199+
let anchor_bounds = self
200+
.point_domain
201+
.positions()
202+
.iter()
203+
.map(|&point| transform.transform_point2(point))
204+
.map(|point| [point, point])
205+
.reduce(combine);
206+
207+
let segment_bounds = self
208+
.segment_bezier_iter()
198209
.map(|(_, bezier, _, _)| bezier.apply_transformation(|point| transform.transform_point2(point)).bounding_box())
199-
.reduce(|b1, b2| [b1[0].min(b2[0]), b1[1].max(b2[1])])
210+
.reduce(combine);
211+
212+
anchor_bounds.iter().chain(segment_bounds.iter()).copied().reduce(combine)
200213
}
201214

202215
/// Calculate the corners of the bounding box but with a nonzero size.

0 commit comments

Comments
 (0)