Skip to content

Commit f98e053

Browse files
authored
Merge branch 'master' into spacing-type-radio-button
2 parents d0f091a + 1090770 commit f98e053

20 files changed

Lines changed: 1270 additions & 308 deletions

File tree

demo-artwork/parametric-dunescape.graphite

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

editor/src/messages/input_mapper/input_mappings.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ pub fn input_mappings() -> Mapping {
218218
entry!(KeyDown(KeyG); action_dispatch=PathToolMessage::GRS { key: KeyG }),
219219
entry!(KeyDown(KeyR); action_dispatch=PathToolMessage::GRS { key: KeyR }),
220220
entry!(KeyDown(KeyS); action_dispatch=PathToolMessage::GRS { key: KeyS }),
221-
entry!(PointerMove; refresh_keys=[KeyC, Space, Control, Shift, Alt], action_dispatch=PathToolMessage::PointerMove { toggle_colinear: KeyC, equidistant: Alt, move_anchor_with_handles: Space, snap_angle: Shift, lock_angle: Control, delete_segment: Alt }),
221+
entry!(PointerMove; refresh_keys=[KeyC, Space, Control, Shift, Alt], action_dispatch=PathToolMessage::PointerMove { toggle_colinear: KeyC, equidistant: Alt, move_anchor_with_handles: Space, snap_angle: Shift, lock_angle: Control, delete_segment: Alt, break_colinear_molding: Alt }),
222222
entry!(KeyDown(Delete); action_dispatch=PathToolMessage::Delete),
223223
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAllAnchors),
224224
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::DeselectAllPoints),

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -951,7 +951,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
951951
properties: None,
952952
},
953953
DocumentNodeDefinition {
954-
identifier: "Split Vector2",
954+
identifier: "Split Coordinate",
955955
category: "Math: Vector",
956956
node_template: NodeTemplate {
957957
document_node: DocumentNode {
@@ -982,7 +982,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
982982
..Default::default()
983983
},
984984
persistent_node_metadata: DocumentNodePersistentMetadata {
985-
input_properties: vec![("Vector2", "TODO").into()],
985+
input_properties: vec![("Coordinate", "TODO").into()],
986986
output_names: vec!["X".to_string(), "Y".to_string()],
987987
has_primary_output: false,
988988
network_metadata: Some(NodeNetworkMetadata {
@@ -2913,7 +2913,7 @@ fn static_input_properties() -> InputProperties {
29132913
.input_metadata(&node_id, index, "min", context.selection_network_path)
29142914
.and_then(|value| value.as_f64());
29152915

2916-
Ok(vec![node_properties::vector2_widget(
2916+
Ok(vec![node_properties::coordinate_widget(
29172917
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
29182918
x,
29192919
y,
@@ -3190,7 +3190,7 @@ fn static_input_properties() -> InputProperties {
31903190
Box::new(|node_id, index, context| {
31913191
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
31923192
Ok(vec![LayoutGroup::Row {
3193-
widgets: node_properties::array_of_vector2_widget(
3193+
widgets: node_properties::array_of_coordinates_widget(
31943194
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
31953195
TextInput::default().centered(true),
31963196
),

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

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,15 @@ pub(crate) fn property_from_type(
154154
number_max = Some(range_end);
155155
number_input = number_input.mode_range().min(range_start).max(range_end);
156156
}
157+
if let Some(unit) = unit {
158+
number_input = number_input.unit(unit);
159+
}
160+
if let Some(display_decimal_places) = display_decimal_places {
161+
number_input = number_input.display_decimal_places(display_decimal_places);
162+
}
163+
if let Some(step) = step {
164+
number_input = number_input.step(step);
165+
}
157166

158167
let min = |x: f64| number_min.unwrap_or(x);
159168
let max = |x: f64| number_max.unwrap_or(x);
@@ -167,15 +176,15 @@ pub(crate) fn property_from_type(
167176
// Aliased types (ambiguous values)
168177
Some("Percentage") => number_widget(default_info, number_input.percentage().min(min(0.)).max(max(100.))).into(),
169178
Some("SignedPercentage") => number_widget(default_info, number_input.percentage().min(min(-100.)).max(max(100.))).into(),
170-
Some("Angle") => number_widget(default_info, number_input.mode_range().min(min(-180.)).max(max(180.)).unit("°")).into(),
171-
Some("Multiplier") => number_widget(default_info, number_input.unit("x")).into(),
172-
Some("PixelLength") => number_widget(default_info, number_input.min(min(0.)).unit(" px")).into(),
179+
Some("Angle") => number_widget(default_info, number_input.mode_range().min(min(-180.)).max(max(180.)).unit(unit.unwrap_or("°"))).into(),
180+
Some("Multiplier") => number_widget(default_info, number_input.unit(unit.unwrap_or("x"))).into(),
181+
Some("PixelLength") => number_widget(default_info, number_input.min(min(0.)).unit(unit.unwrap_or(" px"))).into(),
173182
Some("Length") => number_widget(default_info, number_input.min(min(0.))).into(),
174183
Some("Fraction") => number_widget(default_info, number_input.mode_range().min(min(0.)).max(max(1.))).into(),
175184
Some("IntegerCount") => number_widget(default_info, number_input.int().min(min(1.))).into(),
176185
Some("SeedValue") => number_widget(default_info, number_input.int().min(min(0.))).into(),
177-
Some("Resolution") => vector2_widget(default_info, "W", "H", " px", Some(64.)),
178-
Some("PixelSize") => vector2_widget(default_info, "X", "Y", " px", None),
186+
Some("Resolution") => coordinate_widget(default_info, "W", "H", unit.unwrap_or(" px"), Some(64.)),
187+
Some("PixelSize") => coordinate_widget(default_info, "X", "Y", unit.unwrap_or(" px"), None),
179188

180189
// For all other types, use TypeId-based matching
181190
_ => {
@@ -189,14 +198,14 @@ pub(crate) fn property_from_type(
189198
Some(x) if x == TypeId::of::<u64>() => number_widget(default_info, number_input.int().min(min(0.))).into(),
190199
Some(x) if x == TypeId::of::<bool>() => bool_widget(default_info, CheckboxInput::default()).into(),
191200
Some(x) if x == TypeId::of::<String>() => text_widget(default_info).into(),
192-
Some(x) if x == TypeId::of::<DVec2>() => vector2_widget(default_info, "X", "Y", "", None),
193-
Some(x) if x == TypeId::of::<UVec2>() => vector2_widget(default_info, "X", "Y", "", Some(0.)),
194-
Some(x) if x == TypeId::of::<IVec2>() => vector2_widget(default_info, "X", "Y", "", None),
201+
Some(x) if x == TypeId::of::<DVec2>() => coordinate_widget(default_info, "X", "Y", "", None),
202+
Some(x) if x == TypeId::of::<UVec2>() => coordinate_widget(default_info, "X", "Y", "", Some(0.)),
203+
Some(x) if x == TypeId::of::<IVec2>() => coordinate_widget(default_info, "X", "Y", "", None),
195204
// ==========================
196205
// PRIMITIVE COLLECTION TYPES
197206
// ==========================
198207
Some(x) if x == TypeId::of::<Vec<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(),
199-
Some(x) if x == TypeId::of::<Vec<DVec2>>() => array_of_vector2_widget(default_info, TextInput::default()).into(),
208+
Some(x) if x == TypeId::of::<Vec<DVec2>>() => array_of_coordinates_widget(default_info, TextInput::default()).into(),
200209
// ====================
201210
// GRAPHICAL DATA TYPES
202211
// ====================
@@ -512,7 +521,7 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
512521
last.clone()
513522
}
514523

515-
pub fn vector2_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>) -> LayoutGroup {
524+
pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>) -> LayoutGroup {
516525
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
517526

518527
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
@@ -655,7 +664,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
655664
widgets
656665
}
657666

658-
pub fn array_of_vector2_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> {
667+
pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> {
659668
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
660669

661670
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
@@ -1195,7 +1204,7 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
11951204
if let Some(&TaggedValue::GridType(grid_type)) = grid_type_input.as_non_exposed_value() {
11961205
match grid_type {
11971206
GridType::Rectangular => {
1198-
let spacing = vector2_widget(ParameterWidgetsInfo::from_index(document_node, node_id, spacing_index, true, context), "W", "H", " px", Some(0.));
1207+
let spacing = coordinate_widget(ParameterWidgetsInfo::from_index(document_node, node_id, spacing_index, true, context), "W", "H", " px", Some(0.));
11991208
widgets.push(spacing);
12001209
}
12011210
GridType::Isometric => {
@@ -1205,7 +1214,7 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
12051214
NumberInput::default().label("H").min(0.).unit(" px"),
12061215
),
12071216
};
1208-
let angles = vector2_widget(ParameterWidgetsInfo::from_index(document_node, node_id, angles_index, true, context), "", "", "°", None);
1217+
let angles = coordinate_widget(ParameterWidgetsInfo::from_index(document_node, node_id, angles_index, true, context), "", "", "°", None);
12091218
widgets.extend([spacing, angles]);
12101219
}
12111220
}
@@ -1424,6 +1433,9 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
14241433
unit_suffix = field.unit;
14251434
step = field.number_step;
14261435
number_options = (field.number_min, field.number_max, field.number_mode_range);
1436+
display_decimal_places = field.number_display_decimal_places;
1437+
unit_suffix = field.unit;
1438+
step = field.number_step;
14271439
if let Some(ref default) = field.default_type {
14281440
break 'early_return default.clone();
14291441
}

editor/src/messages/portfolio/portfolio_message_handler.rs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -467,15 +467,16 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
467467
}
468468
};
469469

470-
const REPLACEMENTS: [(&str, &str); 37] = [
470+
const REPLACEMENTS: [(&str, &str); 40] = [
471471
("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"),
472472
("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"),
473473
("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"),
474474
("graphene_core::ToGraphicGroupNode", "graphene_core::graphic_element::ToGroupNode"),
475475
("graphene_core::logic::LogicAndNode", "graphene_core::ops::LogicAndNode"),
476476
("graphene_core::logic::LogicNotNode", "graphene_core::ops::LogicNotNode"),
477477
("graphene_core::logic::LogicOrNode", "graphene_core::ops::LogicOrNode"),
478-
("graphene_core::ops::ConstructVector2", "graphene_core::ops::Vector2ValueNode"),
478+
("graphene_core::ops::ConstructVector2", "graphene_core::ops::CoordinateValueNode"),
479+
("graphene_core::ops::Vector2ValueNode", "graphene_core::ops::CoordinateValueNode"),
479480
("graphene_core::raster::BlackAndWhiteNode", "graphene_core::raster::adjustments::BlackAndWhiteNode"),
480481
("graphene_core::raster::BlendNode", "graphene_core::raster::adjustments::BlendNode"),
481482
("graphene_core::raster::ChannelMixerNode", "graphene_core::raster::adjustments::ChannelMixerNode"),
@@ -484,6 +485,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
484485
("graphene_core::raster::ExtractChannelNode", "graphene_core::raster::adjustments::ExtractChannelNode"),
485486
("graphene_core::raster::GradientMapNode", "graphene_core::raster::adjustments::GradientMapNode"),
486487
("graphene_core::raster::HueSaturationNode", "graphene_core::raster::adjustments::HueSaturationNode"),
488+
("graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::AutoTangentsNode"),
489+
("graphene_core::vector::RemoveHandlesNode", "graphene_core::vector::AutoTangentsNode"),
487490
("graphene_core::raster::InvertNode", "graphene_core::raster::adjustments::InvertNode"),
488491
("graphene_core::raster::InvertRGBNode", "graphene_core::raster::adjustments::InvertNode"),
489492
("graphene_core::raster::LevelsNode", "graphene_core::raster::adjustments::LevelsNode"),
@@ -963,6 +966,48 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
963966

964967
document.network_interface.replace_reference_name(node_id, network_path, "Flatten Path".to_string());
965968
}
969+
970+
if reference == "Remove Handles" {
971+
let node_definition = resolve_document_node_type("Auto-Tangents").unwrap();
972+
let new_node_template = node_definition.default_node_template();
973+
let document_node = new_node_template.document_node;
974+
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
975+
document
976+
.network_interface
977+
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
978+
979+
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
980+
981+
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
982+
document
983+
.network_interface
984+
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::F64(0.), false), network_path);
985+
document
986+
.network_interface
987+
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path);
988+
989+
document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string());
990+
}
991+
992+
if reference == "Generate Handles" {
993+
let node_definition = resolve_document_node_type("Auto-Tangents").unwrap();
994+
let new_node_template = node_definition.default_node_template();
995+
let document_node = new_node_template.document_node;
996+
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
997+
document
998+
.network_interface
999+
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
1000+
1001+
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
1002+
1003+
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
1004+
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
1005+
document
1006+
.network_interface
1007+
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(true), false), network_path);
1008+
1009+
document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string());
1010+
}
9661011
}
9671012

9681013
// TODO: Eventually remove this document upgrade code

editor/src/messages/tool/common_functionality/graph_modification_utils.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,10 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
5757
}
5858

5959
// Move the `second_layer` below the `first_layer` for positioning purposes
60-
let first_layer_parent = first_layer.parent(document.metadata()).unwrap();
61-
let first_layer_index = first_layer_parent.children(document.metadata()).position(|child| child == first_layer).unwrap();
60+
let Some(first_layer_parent) = first_layer.parent(document.metadata()) else { return };
61+
let Some(first_layer_index) = first_layer_parent.children(document.metadata()).position(|child| child == first_layer) else {
62+
return;
63+
};
6264
responses.add(NodeGraphMessage::MoveLayerToStack {
6365
layer: second_layer,
6466
parent: first_layer_parent,

editor/src/messages/tool/common_functionality/shape_editor.rs

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::graph_modification_utils::merge_layers;
22
use super::snapping::{SnapCache, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};
3-
use super::utility_functions::calculate_segment_angle;
3+
use super::utility_functions::{adjust_handle_colinearity, calculate_segment_angle, restore_g1_continuity, restore_previous_handle_position};
44
use crate::consts::HANDLE_LENGTH_FACTOR;
55
use crate::messages::portfolio::document::overlays::utility_functions::selected_segments;
66
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
@@ -107,11 +107,7 @@ impl SelectedLayerState {
107107
}
108108

109109
pub fn selected_points_count(&self) -> usize {
110-
let count = self.selected_points.iter().fold(0, |acc, point| {
111-
let is_ignored = (point.as_handle().is_some() && self.ignore_handles) || (point.as_anchor().is_some() && self.ignore_anchors);
112-
acc + if is_ignored { 0 } else { 1 }
113-
});
114-
count
110+
self.selected_points.len()
115111
}
116112
}
117113

@@ -163,6 +159,10 @@ impl ClosestSegment {
163159
self.points
164160
}
165161

162+
pub fn closest_point_document(&self) -> DVec2 {
163+
self.bezier.evaluate(TValue::Parametric(self.t))
164+
}
165+
166166
pub fn closest_point_to_viewport(&self) -> DVec2 {
167167
self.bezier_point_to_viewport
168168
}
@@ -208,7 +208,7 @@ impl ClosestSegment {
208208
(first_handle, second_handle)
209209
}
210210

211-
pub fn adjusted_insert(&self, responses: &mut VecDeque<Message>) -> PointId {
211+
pub fn adjusted_insert(&self, responses: &mut VecDeque<Message>) -> (PointId, [SegmentId; 2]) {
212212
let layer = self.layer;
213213
let [first, second] = self.bezier.split(TValue::Parametric(self.t));
214214

@@ -253,11 +253,11 @@ impl ClosestSegment {
253253
responses.add(GraphOperationMessage::Vector { layer, modification_type });
254254
}
255255

256-
midpoint
256+
(midpoint, segment_ids)
257257
}
258258

259259
pub fn adjusted_insert_and_select(&self, shape_editor: &mut ShapeState, responses: &mut VecDeque<Message>, extend_selection: bool) {
260-
let id = self.adjusted_insert(responses);
260+
let (id, _) = self.adjusted_insert(responses);
261261
shape_editor.select_anchor_point_by_id(self.layer, id, extend_selection)
262262
}
263263

@@ -282,6 +282,71 @@ impl ClosestSegment {
282282
.unwrap_or(DVec2::ZERO);
283283
tangent.perp()
284284
}
285+
286+
/// Molding the bezier curve.
287+
/// Returns adjacent handles' [`HandleId`] if colinearity is broken temporarily.
288+
pub fn mold_handle_positions(
289+
&self,
290+
document: &DocumentMessageHandler,
291+
responses: &mut VecDeque<Message>,
292+
(c1, c2): (DVec2, DVec2),
293+
new_b: DVec2,
294+
break_colinear_molding: bool,
295+
temporary_adjacent_handles_while_molding: Option<[Option<HandleId>; 2]>,
296+
) -> Option<[Option<HandleId>; 2]> {
297+
let transform = document.metadata().transform_to_viewport(self.layer);
298+
299+
let start = self.bezier.start;
300+
let end = self.bezier.end;
301+
302+
// Apply the drag delta to the segment's handles
303+
let b = self.bezier_point_to_viewport;
304+
let delta = transform.inverse().transform_vector2(new_b - b);
305+
let (nc1, nc2) = (c1 + delta, c2 + delta);
306+
307+
let handle1 = HandleId::primary(self.segment);
308+
let handle2 = HandleId::end(self.segment);
309+
let layer = self.layer;
310+
311+
let modification_type = handle1.set_relative_position(nc1 - start);
312+
responses.add(GraphOperationMessage::Vector { layer, modification_type });
313+
314+
let modification_type = handle2.set_relative_position(nc2 - end);
315+
responses.add(GraphOperationMessage::Vector { layer, modification_type });
316+
317+
// If adjacent segments have colinear handles, their direction is changed but their handle lengths is preserved
318+
// TODO: Find something which is more appropriate
319+
let vector_data = document.network_interface.compute_modified_vector(self.layer())?;
320+
321+
if break_colinear_molding {
322+
// Disable G1 continuity
323+
let other_handles = [
324+
restore_previous_handle_position(handle1, c1, start, &vector_data, layer, responses),
325+
restore_previous_handle_position(handle2, c2, end, &vector_data, layer, responses),
326+
];
327+
328+
// Store other HandleId in tool data to regain colinearity later
329+
if temporary_adjacent_handles_while_molding.is_some() {
330+
temporary_adjacent_handles_while_molding
331+
} else {
332+
Some(other_handles)
333+
}
334+
} else {
335+
// Move the colinear handles so that colinearity is maintained
336+
adjust_handle_colinearity(handle1, start, nc1, &vector_data, layer, responses);
337+
adjust_handle_colinearity(handle2, end, nc2, &vector_data, layer, responses);
338+
339+
if let Some(adjacent_handles) = temporary_adjacent_handles_while_molding {
340+
if let Some(other_handle1) = adjacent_handles[0] {
341+
restore_g1_continuity(handle1, other_handle1, nc1, start, &vector_data, layer, responses);
342+
}
343+
if let Some(other_handle2) = adjacent_handles[1] {
344+
restore_g1_continuity(handle2, other_handle2, nc2, end, &vector_data, layer, responses);
345+
}
346+
}
347+
None
348+
}
349+
}
285350
}
286351

287352
// TODO Consider keeping a list of selected manipulators to minimize traversals of the layers

0 commit comments

Comments
 (0)