mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-21 05:08:12 +08:00
Merge branch 'master' into refit_on_delete
This commit is contained in:
@@ -102,7 +102,7 @@ pub const MANIPULATOR_GROUP_MARKER_SIZE: f64 = 6.;
|
||||
pub const SELECTION_THRESHOLD: f64 = 10.;
|
||||
pub const HIDE_HANDLE_DISTANCE: f64 = 3.;
|
||||
pub const HANDLE_ROTATE_SNAP_ANGLE: f64 = 15.;
|
||||
pub const SEGMENT_INSERTION_DISTANCE: f64 = 8.;
|
||||
pub const SEGMENT_INSERTION_DISTANCE: f64 = 5.;
|
||||
pub const SEGMENT_OVERLAY_SIZE: f64 = 10.;
|
||||
pub const HANDLE_LENGTH_FACTOR: f64 = 0.5;
|
||||
|
||||
@@ -133,6 +133,7 @@ pub const SCALE_EFFECT: f64 = 0.5;
|
||||
|
||||
// COLORS
|
||||
pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff";
|
||||
pub const COLOR_OVERLAY_BLUE_50: &str = "rgba(0, 168, 255, 0.5)";
|
||||
pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848";
|
||||
pub const COLOR_OVERLAY_GREEN: &str = "#63ce63";
|
||||
pub const COLOR_OVERLAY_RED: &str = "#ef5454";
|
||||
|
||||
@@ -214,7 +214,7 @@ pub fn input_mappings() -> Mapping {
|
||||
entry!(KeyDown(Delete); modifiers=[Shift], action_dispatch=PathToolMessage::DeleteAndRefit),
|
||||
entry!(KeyDown(Backspace); modifiers=[Shift], action_dispatch=PathToolMessage::DeleteAndRefit),
|
||||
entry!(KeyDownNoRepeat(Tab); action_dispatch=PathToolMessage::SwapSelectedHandles),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { extend_selection: Shift, lasso_select: Control, handle_drag_from_anchor: Alt, drag_restore_handle: Control }),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { extend_selection: Shift, lasso_select: Control, handle_drag_from_anchor: Alt, drag_restore_handle: Control, molding_in_segment_edit: KeyA }),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=PathToolMessage::RightClick),
|
||||
entry!(KeyDown(Escape); action_dispatch=PathToolMessage::Escape),
|
||||
entry!(KeyDown(KeyG); action_dispatch=PathToolMessage::GRS { key: KeyG }),
|
||||
|
||||
@@ -6,12 +6,12 @@ use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::VectorModificationType;
|
||||
use graphene_std::vector::brush_stroke::BrushStroke;
|
||||
use graphene_std::vector::style::{Fill, Stroke};
|
||||
|
||||
#[impl_message(Message, DocumentMessage, GraphOperation)]
|
||||
|
||||
@@ -9,10 +9,10 @@ use graph_craft::concrete;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::brush_stroke::BrushStroke;
|
||||
use graphene_std::vector::style::{Fill, Stroke};
|
||||
use graphene_std::vector::{PointId, VectorModificationType};
|
||||
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||
@@ -190,6 +190,7 @@ impl<'a> ModifyInputsContext<'a> {
|
||||
Some(NodeInput::value(TaggedValue::F64(typesetting.character_spacing), false)),
|
||||
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_width), false)),
|
||||
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_height), false)),
|
||||
Some(NodeInput::value(TaggedValue::F64(typesetting.tilt), false)),
|
||||
]);
|
||||
|
||||
let text_id = NodeId::new();
|
||||
|
||||
@@ -16,8 +16,8 @@ use graph_craft::ProtoNodeIdentifier;
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::*;
|
||||
use graph_craft::document::*;
|
||||
use graphene_std::ops::XY;
|
||||
use graphene_std::raster::brush_cache::BrushCache;
|
||||
use graphene_std::brush::brush_cache::BrushCache;
|
||||
use graphene_std::extract_xy::XY;
|
||||
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
|
||||
use graphene_std::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
@@ -960,13 +960,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
nodes: [
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::ExtractXyNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::extract_xy::ExtractXyNode")),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
DocumentNode {
|
||||
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::ExtractXyNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::extract_xy::ExtractXyNode")),
|
||||
manual_composition: Some(generic!(T)),
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1016,7 +1016,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("TODO"),
|
||||
description: Cow::Borrowed(
|
||||
"Decomposes the X and Y components of a 2D coordinate.\n\nThe inverse of this node is \"Coordinate Value\", which can have either or both its X and Y exposed as graph inputs.",
|
||||
),
|
||||
properties: None,
|
||||
},
|
||||
// TODO: Remove this and just use the proto node definition directly
|
||||
@@ -1030,7 +1032,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
nodes: vec![DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::network(concrete!(Vec<graphene_std::vector::brush_stroke::BrushStroke>), 1),
|
||||
NodeInput::network(concrete!(Vec<brush::brush_stroke::BrushStroke>), 1),
|
||||
NodeInput::network(concrete!(BrushCache), 2),
|
||||
],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
@@ -1556,6 +1558,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().character_spacing), false),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false),
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
@@ -1577,6 +1580,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
"Line Height",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some("x".to_string()),
|
||||
min: Some(0.),
|
||||
step: Some(0.1),
|
||||
..Default::default()
|
||||
@@ -1586,6 +1590,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
"Character Spacing",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some(" px".to_string()),
|
||||
min: Some(0.),
|
||||
step: Some(0.1),
|
||||
..Default::default()
|
||||
@@ -1595,6 +1600,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
"Max Width",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some(" px".to_string()),
|
||||
min: Some(1.),
|
||||
blank_assist: false,
|
||||
..Default::default()
|
||||
@@ -1604,11 +1610,22 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
"Max Height",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some(" px".to_string()),
|
||||
min: Some(1.),
|
||||
blank_assist: false,
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
"Tilt",
|
||||
"Faux italic",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
min: Some(-85.),
|
||||
max: Some(85.),
|
||||
unit: Some("°".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
],
|
||||
output_names: vec!["Vector".to_string()],
|
||||
..Default::default()
|
||||
@@ -1841,7 +1858,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
|
||||
NodeInput::network(concrete!(vector::misc::PointSpacingType), 1),
|
||||
NodeInput::network(concrete!(f64), 2),
|
||||
NodeInput::network(concrete!(f64), 3),
|
||||
NodeInput::network(concrete!(u32), 3),
|
||||
NodeInput::network(concrete!(f64), 4),
|
||||
NodeInput::network(concrete!(f64), 5),
|
||||
NodeInput::network(concrete!(bool), 6),
|
||||
@@ -1880,7 +1897,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
||||
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
|
||||
NodeInput::value(TaggedValue::PointSpacingType(Default::default()), false),
|
||||
NodeInput::value(TaggedValue::F64(100.), false),
|
||||
NodeInput::value(TaggedValue::F64(100.), false),
|
||||
NodeInput::value(TaggedValue::U32(100), false),
|
||||
NodeInput::value(TaggedValue::F64(0.), false),
|
||||
NodeInput::value(TaggedValue::F64(0.), false),
|
||||
NodeInput::value(TaggedValue::Bool(false), false),
|
||||
|
||||
@@ -12,7 +12,7 @@ use graph_craft::Type;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
|
||||
use graphene_std::animation::RealTimeMode;
|
||||
use graphene_std::ops::XY;
|
||||
use graphene_std::extract_xy::XY;
|
||||
use graphene_std::path_bool::BooleanOperation;
|
||||
use graphene_std::raster::curve::Curve;
|
||||
use graphene_std::raster::{
|
||||
@@ -1427,6 +1427,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
||||
} else {
|
||||
NumberInput::default()
|
||||
.value(Some(uniform_val))
|
||||
.unit(" px")
|
||||
.on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, CornerRadiusInput::<f64>::INDEX))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder()
|
||||
@@ -1840,7 +1841,7 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
||||
}
|
||||
|
||||
pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::ops::math::*;
|
||||
use graphene_std::math_nodes::math::*;
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
|
||||
@@ -124,8 +124,19 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
|
||||
overlay_context.outline_vector(&vector_data, transform);
|
||||
}
|
||||
|
||||
// Get the selected segments and then add a bold line overlay on them
|
||||
for (segment_id, bezier, _, _) in vector_data.segment_bezier_iter() {
|
||||
let Some(selected_shape_state) = shape_editor.selected_shape_state.get_mut(&layer) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if selected_shape_state.is_segment_selected(segment_id) {
|
||||
overlay_context.outline_select_bezier(bezier, transform);
|
||||
}
|
||||
}
|
||||
|
||||
let selected = shape_editor.selected_shape_state.get(&layer);
|
||||
let is_selected = |point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
|
||||
let is_selected = |point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));
|
||||
|
||||
if display_handles {
|
||||
let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector_data.adjacent_segment(point_id)).collect();
|
||||
@@ -187,7 +198,7 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
|
||||
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
|
||||
let transform = document.metadata().transform_to_viewport(layer);
|
||||
let selected = shape_editor.selected_shape_state.get(&layer);
|
||||
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_selected(point));
|
||||
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));
|
||||
|
||||
for point in vector_data.extendable_points(preferences.vector_meshes) {
|
||||
let Some(position) = vector_data.point_domain.position_from_id(point) else { continue };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::utility_functions::overlay_canvas_context;
|
||||
use crate::consts::{
|
||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER,
|
||||
COMPASS_ROSE_RING_INNER_DIAMETER, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER,
|
||||
COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
||||
};
|
||||
use crate::messages::prelude::Message;
|
||||
use bezier_rs::{Bezier, Subpath};
|
||||
@@ -581,6 +581,35 @@ impl OverlayContext {
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
/// Used by the path tool segment mode in order to show the selected segments.
|
||||
pub fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.bezier_command(bezier, transform, true);
|
||||
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);
|
||||
self.render_context.set_line_width(4.);
|
||||
self.render_context.stroke();
|
||||
|
||||
self.render_context.set_line_width(1.);
|
||||
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
self.render_context.begin_path();
|
||||
self.bezier_command(bezier, transform, true);
|
||||
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);
|
||||
self.render_context.set_line_width(4.);
|
||||
self.render_context.stroke();
|
||||
|
||||
self.render_context.set_line_width(1.);
|
||||
|
||||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
fn bezier_command(&self, bezier: Bezier, transform: DAffine2, move_to: bool) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
|
||||
@@ -1161,8 +1161,7 @@ impl NodeNetworkInterface {
|
||||
.and_then(|node_metadata| node_metadata.persistent_metadata.input_properties.get(index))
|
||||
}
|
||||
|
||||
pub fn insert_input_properties_row(&mut self, node_id: &NodeId, index: usize, network_path: &[NodeId]) {
|
||||
let row = ("", "TODO").into();
|
||||
pub fn insert_input_properties_row(&mut self, node_id: &NodeId, index: usize, network_path: &[NodeId], row: PropertiesRow) {
|
||||
let _ = self
|
||||
.node_metadata_mut(node_id, network_path)
|
||||
.map(|node_metadata| node_metadata.persistent_metadata.input_properties.insert(index - 1, row));
|
||||
|
||||
@@ -9,9 +9,7 @@ use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::vector::VectorModificationType;
|
||||
use graphene_std::vector::{HandleExt, ManipulatorPointId};
|
||||
use graphene_std::vector::{HandleId, PointId};
|
||||
use graphene_std::vector::{HandleExt, HandleId, ManipulatorPointId, PointId, VectorModificationType};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
use std::f64::consts::PI;
|
||||
|
||||
@@ -88,6 +86,18 @@ impl OriginalTransforms {
|
||||
let Some(selected_points) = shape_editor.selected_points_in_layer(layer) else {
|
||||
continue;
|
||||
};
|
||||
let Some(selected_segments) = shape_editor.selected_segments_in_layer(layer) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let mut selected_points = selected_points.clone();
|
||||
|
||||
for (segment_id, _, start, end) in vector_data.segment_bezier_iter() {
|
||||
if selected_segments.contains(&segment_id) {
|
||||
selected_points.insert(ManipulatorPointId::Anchor(start));
|
||||
selected_points.insert(ManipulatorPointId::Anchor(end));
|
||||
}
|
||||
}
|
||||
|
||||
// Anchors also move their handles
|
||||
let anchor_ids = selected_points.iter().filter_map(|point| point.as_anchor());
|
||||
@@ -604,7 +614,7 @@ impl<'a> Selected<'a> {
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
}
|
||||
|
||||
if transform_operation.is_some_and(|transform_operation| matches!(transform_operation, TransformOperation::Scaling(_))) && initial_points.anchors.len() > 1 {
|
||||
if transform_operation.is_some_and(|transform_operation| matches!(transform_operation, TransformOperation::Scaling(_))) && (initial_points.anchors.len() == 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// TODO: Eventually remove this document upgrade code
|
||||
// This file contains lots of hacky code for upgrading old documents to the new format
|
||||
|
||||
use super::document::utility_types::network_interface::{NumberInputSettings, PropertiesRow, WidgetOverride};
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector};
|
||||
@@ -24,38 +25,113 @@ const REPLACEMENTS: &[(&str, &str)] = &[
|
||||
("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"),
|
||||
("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"),
|
||||
("graphene_core::ToGraphicGroupNode", "graphene_core::graphic_element::ToGroupNode"),
|
||||
// math_nodes
|
||||
("graphene_core::ops::MathNode", "graphene_math_nodes::MathNode"),
|
||||
("graphene_core::ops::AddNode", "graphene_math_nodes::AddNode"),
|
||||
("graphene_core::ops::SubtractNode", "graphene_math_nodes::SubtractNode"),
|
||||
("graphene_core::ops::MultiplyNode", "graphene_math_nodes::MultiplyNode"),
|
||||
("graphene_core::ops::DivideNode", "graphene_math_nodes::DivideNode"),
|
||||
("graphene_core::ops::ModuloNode", "graphene_math_nodes::ModuloNode"),
|
||||
("graphene_core::ops::ExponentNode", "graphene_math_nodes::ExponentNode"),
|
||||
("graphene_core::ops::RootNode", "graphene_math_nodes::RootNode"),
|
||||
("graphene_core::ops::LogarithmNode", "graphene_math_nodes::LogarithmNode"),
|
||||
("graphene_core::ops::SineNode", "graphene_math_nodes::SineNode"),
|
||||
("graphene_core::ops::CosineNode", "graphene_math_nodes::CosineNode"),
|
||||
("graphene_core::ops::TangentNode", "graphene_math_nodes::TangentNode"),
|
||||
("graphene_core::ops::SineInverseNode", "graphene_math_nodes::SineInverseNode"),
|
||||
("graphene_core::ops::CosineInverseNode", "graphene_math_nodes::CosineInverseNode"),
|
||||
("graphene_core::ops::TangentInverseNode", "graphene_math_nodes::TangentInverseNode"),
|
||||
("graphene_core::ops::RandomNode", "graphene_math_nodes::RandomNode"),
|
||||
("graphene_core::ops::ToU32Node", "graphene_math_nodes::ToU32Node"),
|
||||
("graphene_core::ops::ToU64Node", "graphene_math_nodes::ToU64Node"),
|
||||
("graphene_core::ops::ToF64Node", "graphene_math_nodes::ToF64Node"),
|
||||
("graphene_core::ops::RoundNode", "graphene_math_nodes::RoundNode"),
|
||||
("graphene_core::ops::FloorNode", "graphene_math_nodes::FloorNode"),
|
||||
("graphene_core::ops::CeilingNode", "graphene_math_nodes::CeilingNode"),
|
||||
("graphene_core::ops::MinNode", "graphene_math_nodes::MinNode"),
|
||||
("graphene_core::ops::MaxNode", "graphene_math_nodes::MaxNode"),
|
||||
("graphene_core::ops::ClampNode", "graphene_math_nodes::ClampNode"),
|
||||
("graphene_core::ops::EqualsNode", "graphene_math_nodes::EqualsNode"),
|
||||
("graphene_core::ops::NotEqualsNode", "graphene_math_nodes::NotEqualsNode"),
|
||||
("graphene_core::ops::LessThanNode", "graphene_math_nodes::LessThanNode"),
|
||||
("graphene_core::ops::GreaterThanNode", "graphene_math_nodes::GreaterThanNode"),
|
||||
("graphene_core::ops::LogicalOrNode", "graphene_math_nodes::LogicalOrNode"),
|
||||
("graphene_core::ops::LogicalAndNode", "graphene_math_nodes::LogicalAndNode"),
|
||||
("graphene_core::ops::LogicalNotNode", "graphene_math_nodes::LogicalNotNode"),
|
||||
("graphene_core::ops::BoolValueNode", "graphene_math_nodes::BoolValueNode"),
|
||||
("graphene_core::ops::NumberValueNode", "graphene_math_nodes::NumberValueNode"),
|
||||
("graphene_core::ops::PercentageValueNode", "graphene_math_nodes::PercentageValueNode"),
|
||||
("graphene_core::ops::CoordinateValueNode", "graphene_math_nodes::CoordinateValueNode"),
|
||||
("graphene_core::ops::ColorValueNode", "graphene_math_nodes::ColorValueNode"),
|
||||
("graphene_core::ops::GradientValueNode", "graphene_math_nodes::GradientValueNode"),
|
||||
("graphene_core::ops::StringValueNode", "graphene_math_nodes::StringValueNode"),
|
||||
("graphene_core::ops::DotProductNode", "graphene_math_nodes::DotProductNode"),
|
||||
// debug
|
||||
("graphene_core::ops::SizeOfNode", "graphene_core::debug::SizeOfNode"),
|
||||
("graphene_core::ops::SomeNode", "graphene_core::debug::SomeNode"),
|
||||
("graphene_core::ops::UnwrapNode", "graphene_core::debug::UnwrapNode"),
|
||||
("graphene_core::ops::CloneNode", "graphene_core::debug::CloneNode"),
|
||||
// ???
|
||||
("graphene_core::ops::ExtractXyNode", "graphene_core::extract_xy::ExtractXyNode"),
|
||||
("graphene_core::logic::LogicAndNode", "graphene_core::ops::LogicAndNode"),
|
||||
("graphene_core::logic::LogicNotNode", "graphene_core::ops::LogicNotNode"),
|
||||
("graphene_core::logic::LogicOrNode", "graphene_core::ops::LogicOrNode"),
|
||||
("graphene_core::ops::ConstructVector2", "graphene_core::ops::CoordinateValueNode"),
|
||||
("graphene_core::ops::Vector2ValueNode", "graphene_core::ops::CoordinateValueNode"),
|
||||
("graphene_core::raster::BlackAndWhiteNode", "graphene_core::raster::adjustments::BlackAndWhiteNode"),
|
||||
("graphene_core::raster::BlendNode", "graphene_core::raster::adjustments::BlendNode"),
|
||||
("graphene_core::raster::BlendModeNode", "graphene_core::blending_nodes::BlendModeNode"),
|
||||
("graphene_core::raster::OpacityNode", "graphene_core::blending_nodes::OpacityNode"),
|
||||
("graphene_core::raster::BlendingNode", "graphene_core::blending_nodes::BlendingNode"),
|
||||
("graphene_core::raster::ChannelMixerNode", "graphene_core::raster::adjustments::ChannelMixerNode"),
|
||||
("graphene_core::raster::adjustments::ColorOverlayNode", "graphene_core::raster::adjustments::ColorOverlayNode"),
|
||||
("graphene_core::raster::ExposureNode", "graphene_core::raster::adjustments::ExposureNode"),
|
||||
("graphene_core::raster::ExtractChannelNode", "graphene_core::raster::adjustments::ExtractChannelNode"),
|
||||
("graphene_core::raster::GradientMapNode", "graphene_core::raster::adjustments::GradientMapNode"),
|
||||
("graphene_core::raster::HueSaturationNode", "graphene_core::raster::adjustments::HueSaturationNode"),
|
||||
("graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::AutoTangentsNode"),
|
||||
("graphene_core::vector::RemoveHandlesNode", "graphene_core::vector::AutoTangentsNode"),
|
||||
("graphene_core::raster::InvertNode", "graphene_core::raster::adjustments::InvertNode"),
|
||||
("graphene_core::raster::InvertRGBNode", "graphene_core::raster::adjustments::InvertNode"),
|
||||
("graphene_core::raster::LevelsNode", "graphene_core::raster::adjustments::LevelsNode"),
|
||||
("graphene_core::raster::LuminanceNode", "graphene_core::raster::adjustments::LuminanceNode"),
|
||||
("graphene_core::raster::ExtractOpaqueNode", "graphene_core::raster::adjustments::MakeOpaqueNode"),
|
||||
("graphene_core::raster::PosterizeNode", "graphene_core::raster::adjustments::PosterizeNode"),
|
||||
("graphene_core::raster::ThresholdNode", "graphene_core::raster::adjustments::ThresholdNode"),
|
||||
("graphene_core::raster::VibranceNode", "graphene_core::raster::adjustments::VibranceNode"),
|
||||
// raster::adjustments
|
||||
("graphene_core::raster::adjustments::LuminanceNode", "graphene_raster_nodes::adjustments::LuminanceNode"),
|
||||
("graphene_core::raster::LuminanceNode", "graphene_raster_nodes::adjustments::LuminanceNode"),
|
||||
("graphene_core::raster::adjustments::ExtractChannelNode", "graphene_raster_nodes::adjustments::ExtractChannelNode"),
|
||||
("graphene_core::raster::ExtractChannelNode", "graphene_raster_nodes::adjustments::ExtractChannelNode"),
|
||||
("graphene_core::raster::adjustments::MakeOpaqueNode", "graphene_raster_nodes::adjustments::MakeOpaqueNode"),
|
||||
("graphene_core::raster::ExtractOpaqueNode", "graphene_raster_nodes::adjustments::MakeOpaqueNode"),
|
||||
(
|
||||
"graphene_core::raster::adjustments::BrightnessContrastNode",
|
||||
"graphene_raster_nodes::adjustments::BrightnessContrastNode",
|
||||
),
|
||||
("graphene_core::raster::adjustments::LevelsNode", "graphene_raster_nodes::adjustments::LevelsNode"),
|
||||
("graphene_core::raster::LevelsNode", "graphene_raster_nodes::adjustments::LevelsNode"),
|
||||
("graphene_core::raster::adjustments::BlackAndWhiteNode", "graphene_raster_nodes::adjustments::BlackAndWhiteNode"),
|
||||
("graphene_core::raster::BlackAndWhiteNode", "graphene_raster_nodes::adjustments::BlackAndWhiteNode"),
|
||||
("graphene_core::raster::adjustments::HueSaturationNode", "graphene_raster_nodes::adjustments::HueSaturationNode"),
|
||||
("graphene_core::raster::HueSaturationNode", "graphene_raster_nodes::adjustments::HueSaturationNode"),
|
||||
("graphene_core::raster::adjustments::InvertNode", "graphene_raster_nodes::adjustments::InvertNode"),
|
||||
("graphene_core::raster::InvertNode", "graphene_raster_nodes::adjustments::InvertNode"),
|
||||
("graphene_core::raster::InvertRGBNode", "graphene_raster_nodes::adjustments::InvertNode"),
|
||||
("graphene_core::raster::adjustments::ThresholdNode", "graphene_raster_nodes::adjustments::ThresholdNode"),
|
||||
("graphene_core::raster::ThresholdNode", "graphene_raster_nodes::adjustments::ThresholdNode"),
|
||||
("graphene_core::raster::adjustments::BlendNode", "graphene_raster_nodes::adjustments::BlendNode"),
|
||||
("graphene_core::raster::BlendNode", "graphene_raster_nodes::adjustments::BlendNode"),
|
||||
("graphene_core::raster::BlendColorPairNode", "graphene_raster_nodes::adjustments::BlendColorPairNode"),
|
||||
("graphene_core::raster::adjustments::BlendColorsNode", "graphene_raster_nodes::adjustments::BlendColorsNode"),
|
||||
("graphene_core::raster::BlendColorsNode", "graphene_raster_nodes::adjustments::BlendColorsNode"),
|
||||
("graphene_core::raster::adjustments::GradientMapNode", "graphene_raster_nodes::adjustments::GradientMapNode"),
|
||||
("graphene_core::raster::GradientMapNode", "graphene_raster_nodes::adjustments::GradientMapNode"),
|
||||
("graphene_core::raster::adjustments::VibranceNode", "graphene_raster_nodes::adjustments::VibranceNode"),
|
||||
("graphene_core::raster::VibranceNode", "graphene_raster_nodes::adjustments::VibranceNode"),
|
||||
("graphene_core::raster::adjustments::ChannelMixerNode", "graphene_raster_nodes::adjustments::ChannelMixerNode"),
|
||||
("graphene_core::raster::ChannelMixerNode", "graphene_raster_nodes::adjustments::ChannelMixerNode"),
|
||||
("graphene_core::raster::adjustments::SelectiveColorNode", "graphene_raster_nodes::adjustments::SelectiveColorNode"),
|
||||
("graphene_core::raster::adjustments::PosterizeNode", "graphene_raster_nodes::adjustments::PosterizeNode"),
|
||||
("graphene_core::raster::PosterizeNode", "graphene_raster_nodes::adjustments::PosterizeNode"),
|
||||
("graphene_core::raster::adjustments::ExposureNode", "graphene_raster_nodes::adjustments::ExposureNode"),
|
||||
("graphene_core::raster::ExposureNode", "graphene_raster_nodes::adjustments::ExposureNode"),
|
||||
("graphene_core::raster::adjustments::GenerateCurvesNode", "graphene_raster_nodes::generate_curves::GenerateCurvesNode"),
|
||||
("graphene_core::raster::adjustments::ColorOverlayNode", "graphene_raster_nodes::generate_curves::ColorOverlayNode"),
|
||||
// text
|
||||
("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"),
|
||||
// transform
|
||||
("graphene_core::transform::SetTransformNode", "graphene_core::transform_nodes::ReplaceTransformNode"),
|
||||
("graphene_core::transform::ReplaceTransformNode", "graphene_core::transform_nodes::ReplaceTransformNode"),
|
||||
("graphene_core::transform::TransformNode", "graphene_core::transform_nodes::TransformNode"),
|
||||
("graphene_core::transform::BoundlessFootprintNode", "graphene_core::transform_nodes::BoundlessFootprintNode"),
|
||||
("graphene_core::transform::FreezeRealTimeNode", "graphene_core::transform_nodes::FreezeRealTimeNode"),
|
||||
// ???
|
||||
("graphene_core::vector::SplinesFromPointsNode", "graphene_core::vector::SplineNode"),
|
||||
("graphene_core::vector::generator_nodes::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"),
|
||||
("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"),
|
||||
@@ -71,6 +147,10 @@ const REPLACEMENTS: &[(&str, &str)] = &[
|
||||
("graphene_std::raster::MaskImageNode", "graphene_std::raster::MaskNode"),
|
||||
("graphene_core::vector::FlattenVectorElementsNode", "graphene_core::vector::FlattenPathNode"),
|
||||
("graphene_std::vector::BooleanOperationNode", "graphene_path_bool::BooleanOperationNode"),
|
||||
// brush
|
||||
("graphene_std::brush::BrushStampGeneratorNode", "graphene_brush::brush::BrushStampGeneratorNode"),
|
||||
("graphene_std::brush::BlitNode", "graphene_brush::brush::BlitNode"),
|
||||
("graphene_std::brush::BrushNode", "graphene_brush::brush::BrushNode"),
|
||||
];
|
||||
|
||||
pub fn document_migration_string_preprocessing(document_serialized_content: String) -> String {
|
||||
@@ -263,8 +343,8 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document.network_interface.insert_input_properties_row(node_id, 8, network_path);
|
||||
document.network_interface.insert_input_properties_row(node_id, 9, network_path);
|
||||
document.network_interface.insert_input_properties_row(node_id, 8, network_path, ("", "TODO").into());
|
||||
document.network_interface.insert_input_properties_row(node_id, 9, network_path, ("", "TODO").into());
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false);
|
||||
@@ -357,7 +437,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
||||
}
|
||||
|
||||
// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
|
||||
if reference == "Text" && inputs_count != 8 {
|
||||
if reference == "Text" && inputs_count != 9 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
@@ -388,12 +468,44 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 6),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false),
|
||||
if inputs_count >= 7 {
|
||||
old_inputs[6].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 7),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false),
|
||||
if inputs_count >= 8 {
|
||||
old_inputs[7].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.insert_input_properties_row(
|
||||
node_id,
|
||||
9,
|
||||
network_path,
|
||||
PropertiesRow::with_override(
|
||||
"Tilt",
|
||||
"Faux italic",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
min: Some(-85.),
|
||||
max: Some(85.),
|
||||
unit: Some("°".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 8),
|
||||
if inputs_count >= 9 {
|
||||
old_inputs[8].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
}
|
||||
@@ -666,17 +778,62 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false);
|
||||
let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false);
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path);
|
||||
|
||||
document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string());
|
||||
}
|
||||
|
||||
// Make the "Quantity" parameter a u32 instead of f64
|
||||
if reference == "Sample Polyline" {
|
||||
let node_definition = resolve_document_node_type("Sample Polyline").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
|
||||
// Get the inputs, obtain the quantity value, and put the inputs back
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let quantity_value = old_inputs.get(3).cloned();
|
||||
let _ = document.network_interface.replace_inputs(node_id, old_inputs, network_path);
|
||||
|
||||
if let Some(NodeInput::Value { tagged_value, exposed }) = quantity_value {
|
||||
if let TaggedValue::F64(value) = *tagged_value {
|
||||
let new_quantity_value = NodeInput::value(TaggedValue::U32(value as u32), exposed);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows")
|
||||
if reference == "Grid" && inputs_count == 6 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let index_3_value = old_inputs.get(3).cloned();
|
||||
|
||||
if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value {
|
||||
if matches!(*tagged_value, TaggedValue::DVec2(_)) {
|
||||
// Move index 3 to the end
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
|
||||
} else {
|
||||
// Swap it back if we're not changing anything
|
||||
let _ = document.network_interface.replace_inputs(node_id, old_inputs, network_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure layers are positioned as stacks if they are upstream siblings of another layer
|
||||
|
||||
@@ -924,11 +924,11 @@ impl PortfolioMessageHandler {
|
||||
let result = self.executor.poll_node_graph_evaluation(active_document, responses);
|
||||
if result.is_err() {
|
||||
let error = r#"
|
||||
<rect x="50%" y="50%" width="480" height="100" transform="translate(-240 -50)" rx="4" fill="var(--color-error-red)" />
|
||||
<rect x="50%" y="50%" width="460" height="100" transform="translate(-230 -50)" rx="4" fill="var(--color-warning-yellow)" />
|
||||
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-size="18" fill="var(--color-2-mildblack)">
|
||||
<tspan x="50%" dy="-24" font-weight="bold">The document cannot be rendered in its current state.</tspan>
|
||||
<tspan x="50%" dy="24">Check for error details in the node graph, which can be</tspan>
|
||||
<tspan x="50%" dy="24">opened with the viewport's top right <tspan font-style="italic">Node Graph</tspan> button.</tspan>
|
||||
<tspan x="50%" dy="-24" font-weight="bold">The document cannot render in its current state.</tspan>
|
||||
<tspan x="50%" dy="24">Undo to go back, if available, or check for error details</tspan>
|
||||
<tspan x="50%" dy="24">by clicking the <tspan font-style="italic">Node Graph</tspan> button up at the top right.</tspan>
|
||||
/text>"#
|
||||
// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.
|
||||
.to_string();
|
||||
|
||||
@@ -361,6 +361,7 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
|
||||
let Some(&TaggedValue::F64(character_spacing)) = inputs[5].as_value() else { return None };
|
||||
let Some(&TaggedValue::OptionalF64(max_width)) = inputs[6].as_value() else { return None };
|
||||
let Some(&TaggedValue::OptionalF64(max_height)) = inputs[7].as_value() else { return None };
|
||||
let Some(&TaggedValue::F64(tilt)) = inputs[8].as_value() else { return None };
|
||||
|
||||
let typesetting = TypesettingConfig {
|
||||
font_size,
|
||||
@@ -368,6 +369,7 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
|
||||
max_width,
|
||||
character_spacing,
|
||||
max_height,
|
||||
tilt,
|
||||
};
|
||||
Some((text, font, typesetting))
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
use super::graph_modification_utils::merge_layers;
|
||||
use super::snapping::{SnapCache, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};
|
||||
use super::utility_functions::{adjust_handle_colinearity, calculate_segment_angle, restore_g1_continuity, restore_previous_handle_position};
|
||||
use super::utility_functions::{adjust_handle_colinearity, calculate_bezier_bbox, calculate_segment_angle, restore_g1_continuity, restore_previous_handle_position};
|
||||
use crate::consts::HANDLE_LENGTH_FACTOR;
|
||||
use crate::messages::portfolio::document::overlays::utility_functions::selected_segments;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
use crate::messages::portfolio::document::utility_types::misc::{PathSnapSource, SnapSource};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration;
|
||||
use crate::messages::tool::common_functionality::utility_functions::{find_refit_handle_lengths, is_visible_point};
|
||||
use crate::messages::tool::common_functionality::utility_functions::{find_refit_handle_lengths, is_intersecting, is_visible_point};
|
||||
use crate::messages::tool::tool_messages::path_tool::{PathOverlayMode, PointSelectState};
|
||||
use bezier_rs::{Bezier, BezierHandles, Subpath, TValue};
|
||||
use glam::{DAffine2, DVec2};
|
||||
@@ -45,6 +46,7 @@ pub enum ManipulatorAngle {
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct SelectedLayerState {
|
||||
selected_points: HashSet<ManipulatorPointId>,
|
||||
selected_segments: HashSet<SegmentId>,
|
||||
/// Keeps track of the current state; helps avoid unnecessary computation when called by [`ShapeState`].
|
||||
ignore_handles: bool,
|
||||
ignore_anchors: bool,
|
||||
@@ -54,11 +56,27 @@ pub struct SelectedLayerState {
|
||||
}
|
||||
|
||||
impl SelectedLayerState {
|
||||
pub fn selected(&self) -> impl Iterator<Item = ManipulatorPointId> + '_ {
|
||||
pub fn selected_points(&self) -> impl Iterator<Item = ManipulatorPointId> + '_ {
|
||||
self.selected_points.iter().copied()
|
||||
}
|
||||
|
||||
pub fn is_selected(&self, point: ManipulatorPointId) -> bool {
|
||||
pub fn selected_segments(&self) -> impl Iterator<Item = SegmentId> + '_ {
|
||||
self.selected_segments.iter().copied()
|
||||
}
|
||||
|
||||
pub fn selected_points_count(&self) -> usize {
|
||||
self.selected_points.len()
|
||||
}
|
||||
|
||||
pub fn selected_segments_count(&self) -> usize {
|
||||
self.selected_segments.len()
|
||||
}
|
||||
|
||||
pub fn is_segment_selected(&self, segment: SegmentId) -> bool {
|
||||
self.selected_segments.contains(&segment)
|
||||
}
|
||||
|
||||
pub fn is_point_selected(&self, point: ManipulatorPointId) -> bool {
|
||||
self.selected_points.contains(&point)
|
||||
}
|
||||
|
||||
@@ -66,10 +84,26 @@ impl SelectedLayerState {
|
||||
self.selected_points.insert(point);
|
||||
}
|
||||
|
||||
pub fn select_segment(&mut self, segment: SegmentId) {
|
||||
self.selected_segments.insert(segment);
|
||||
}
|
||||
|
||||
pub fn deselect_point(&mut self, point: ManipulatorPointId) {
|
||||
self.selected_points.remove(&point);
|
||||
}
|
||||
|
||||
pub fn deselect_segment(&mut self, segment: SegmentId) {
|
||||
self.selected_segments.remove(&segment);
|
||||
}
|
||||
|
||||
pub fn clear_points(&mut self) {
|
||||
self.selected_points.clear();
|
||||
}
|
||||
|
||||
pub fn clear_segments(&mut self) {
|
||||
self.selected_segments.clear();
|
||||
}
|
||||
|
||||
pub fn ignore_handles(&mut self, status: bool) {
|
||||
if self.ignore_handles != status {
|
||||
return;
|
||||
@@ -101,14 +135,6 @@ impl SelectedLayerState {
|
||||
self.ignored_anchor_points.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear_points(&mut self) {
|
||||
self.selected_points.clear();
|
||||
}
|
||||
|
||||
pub fn selected_points_count(&self) -> usize {
|
||||
self.selected_points.len()
|
||||
}
|
||||
}
|
||||
|
||||
pub type SelectedShapeState = HashMap<LayerNodeIdentifier, SelectedLayerState>;
|
||||
@@ -128,6 +154,12 @@ pub struct SelectedPointsInfo {
|
||||
pub vector_data: VectorData,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SelectedSegmentsInfo {
|
||||
pub segments: Vec<SegmentId>,
|
||||
pub vector_data: VectorData,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct ManipulatorPointInfo {
|
||||
pub layer: LayerNodeIdentifier,
|
||||
@@ -136,6 +168,7 @@ pub struct ManipulatorPointInfo {
|
||||
|
||||
pub type OpposingHandleLengths = HashMap<LayerNodeIdentifier, HashMap<HandleId, f64>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClosestSegment {
|
||||
layer: LayerNodeIdentifier,
|
||||
segment: SegmentId,
|
||||
@@ -159,6 +192,10 @@ impl ClosestSegment {
|
||||
self.points
|
||||
}
|
||||
|
||||
pub fn bezier(&self) -> Bezier {
|
||||
self.bezier
|
||||
}
|
||||
|
||||
pub fn closest_point_document(&self) -> DVec2 {
|
||||
self.bezier.evaluate(TValue::Parametric(self.t))
|
||||
}
|
||||
@@ -473,7 +510,7 @@ impl ShapeState {
|
||||
|
||||
if let Some(id) = selected.as_anchor() {
|
||||
for neighbor in vector_data.connected_points(id) {
|
||||
if state.is_selected(ManipulatorPointId::Anchor(neighbor)) {
|
||||
if state.is_point_selected(ManipulatorPointId::Anchor(neighbor)) {
|
||||
continue;
|
||||
}
|
||||
let Some(position) = vector_data.point_domain.position_from_id(neighbor) else { continue };
|
||||
@@ -512,38 +549,30 @@ impl ShapeState {
|
||||
let point_position = manipulator_point_id.get_position(&vector_data)?;
|
||||
|
||||
let selected_shape_state = self.selected_shape_state.get(&layer)?;
|
||||
let already_selected = selected_shape_state.is_selected(manipulator_point_id);
|
||||
|
||||
// Should we select or deselect the point?
|
||||
let new_selected = if already_selected { !extend_selection } else { true };
|
||||
let already_selected = selected_shape_state.is_point_selected(manipulator_point_id);
|
||||
|
||||
// Offset to snap the selected point to the cursor
|
||||
let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position);
|
||||
|
||||
// This is selecting the manipulator only for now, next to generalize to points
|
||||
if new_selected {
|
||||
let retain_existing_selection = extend_selection || already_selected;
|
||||
if !retain_existing_selection {
|
||||
self.deselect_all_points();
|
||||
}
|
||||
|
||||
// Add to the selected points
|
||||
let selected_shape_state = self.selected_shape_state.get_mut(&layer)?;
|
||||
selected_shape_state.select_point(manipulator_point_id);
|
||||
|
||||
let points = self
|
||||
.selected_shape_state
|
||||
.iter()
|
||||
.flat_map(|(layer, state)| state.selected_points.iter().map(|&point_id| ManipulatorPointInfo { layer: *layer, point_id }))
|
||||
.collect();
|
||||
|
||||
return Some(Some(SelectedPointsInfo { points, offset, vector_data }));
|
||||
} else {
|
||||
let selected_shape_state = self.selected_shape_state.get_mut(&layer)?;
|
||||
selected_shape_state.deselect_point(manipulator_point_id);
|
||||
|
||||
return Some(None);
|
||||
let retain_existing_selection = extend_selection || already_selected;
|
||||
if !retain_existing_selection {
|
||||
self.deselect_all_points();
|
||||
self.deselect_all_segments();
|
||||
}
|
||||
|
||||
// Add to the selected points (deselect is managed in DraggingState, DragStop)
|
||||
let selected_shape_state = self.selected_shape_state.get_mut(&layer)?;
|
||||
selected_shape_state.select_point(manipulator_point_id);
|
||||
|
||||
let points = self
|
||||
.selected_shape_state
|
||||
.iter()
|
||||
.flat_map(|(layer, state)| state.selected_points.iter().map(|&point_id| ManipulatorPointInfo { layer: *layer, point_id }))
|
||||
.collect();
|
||||
|
||||
return Some(Some(SelectedPointsInfo { points, offset, vector_data }));
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -555,11 +584,16 @@ impl ShapeState {
|
||||
select_threshold: f64,
|
||||
path_overlay_mode: PathOverlayMode,
|
||||
frontier_handles_info: Option<HashMap<SegmentId, Vec<PointId>>>,
|
||||
point_editing_mode: bool,
|
||||
) -> Option<(bool, Option<SelectedPointsInfo>)> {
|
||||
if self.selected_shape_state.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !point_editing_mode {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some((layer, manipulator_point_id)) = self.find_nearest_point_indices(network_interface, mouse_position, select_threshold) {
|
||||
let vector_data = network_interface.compute_modified_vector(layer)?;
|
||||
let point_position = manipulator_point_id.get_position(&vector_data)?;
|
||||
@@ -572,7 +606,7 @@ impl ShapeState {
|
||||
}
|
||||
|
||||
let selected_shape_state = self.selected_shape_state.get(&layer)?;
|
||||
let already_selected = selected_shape_state.is_selected(manipulator_point_id);
|
||||
let already_selected = selected_shape_state.is_point_selected(manipulator_point_id);
|
||||
|
||||
// Offset to snap the selected point to the cursor
|
||||
let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position);
|
||||
@@ -630,7 +664,7 @@ impl ShapeState {
|
||||
// Select all connected points
|
||||
while let Some(point) = selected_stack.pop() {
|
||||
let anchor_point = ManipulatorPointId::Anchor(point);
|
||||
if !state.is_selected(anchor_point) {
|
||||
if !state.is_point_selected(anchor_point) {
|
||||
state.select_point(anchor_point);
|
||||
selected_stack.extend(vector_data.connected_points(point));
|
||||
}
|
||||
@@ -671,6 +705,13 @@ impl ShapeState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Deselects all segments across every selected layer
|
||||
pub fn deselect_all_segments(&mut self) {
|
||||
for state in self.selected_shape_state.values_mut() {
|
||||
state.selected_segments.clear()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_selected_anchors_status(&mut self, status: bool) {
|
||||
for state in self.selected_shape_state.values_mut() {
|
||||
self.ignore_anchors = !status;
|
||||
@@ -736,10 +777,18 @@ impl ShapeState {
|
||||
self.selected_shape_state.values().flat_map(|state| &state.selected_points)
|
||||
}
|
||||
|
||||
pub fn selected_segments(&self) -> impl Iterator<Item = &'_ SegmentId> {
|
||||
self.selected_shape_state.values().flat_map(|state| &state.selected_segments)
|
||||
}
|
||||
|
||||
pub fn selected_points_in_layer(&self, layer: LayerNodeIdentifier) -> Option<&HashSet<ManipulatorPointId>> {
|
||||
self.selected_shape_state.get(&layer).map(|state| &state.selected_points)
|
||||
}
|
||||
|
||||
pub fn selected_segments_in_layer(&self, layer: LayerNodeIdentifier) -> Option<&HashSet<SegmentId>> {
|
||||
self.selected_shape_state.get(&layer).map(|state| &state.selected_segments)
|
||||
}
|
||||
|
||||
pub fn move_primary(&self, segment: SegmentId, delta: DVec2, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer,
|
||||
@@ -766,7 +815,7 @@ impl ShapeState {
|
||||
let Some((start, _end, bezier)) = vector_data.segment_points_from_id(segment) else { continue };
|
||||
|
||||
if let BezierHandles::Quadratic { handle } = bezier.handles {
|
||||
if selected.is_some_and(|selected| selected.is_selected(ManipulatorPointId::Anchor(start))) {
|
||||
if selected.is_some_and(|selected| selected.is_point_selected(ManipulatorPointId::Anchor(start))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -850,6 +899,9 @@ impl ShapeState {
|
||||
let non_zero_handles = handles.iter().filter(|handle| handle.length(vector_data) > 1e-6).count();
|
||||
let handle_segments = handles.iter().map(|handles| handles.segment).collect::<Vec<_>>();
|
||||
|
||||
// Check if the anchor is connected to linear segments and has no handles
|
||||
let linear_segments = vector_data.connected_linear_segments(point_id) != 0;
|
||||
|
||||
// Grab the next and previous manipulator groups by simply looking at the next / previous index
|
||||
let points = handles.iter().map(|handle| vector_data.other_point(handle.segment, point_id));
|
||||
let anchor_positions = points
|
||||
@@ -891,7 +943,7 @@ impl ShapeState {
|
||||
handle_direction *= -1.;
|
||||
}
|
||||
|
||||
if non_zero_handles != 0 {
|
||||
if non_zero_handles != 0 && !linear_segments {
|
||||
let [a, b] = handles.as_slice() else { return };
|
||||
let (non_zero_handle, zero_handle) = if a.length(vector_data) > 1e-6 { (a, b) } else { (b, a) };
|
||||
let Some(direction) = non_zero_handle
|
||||
@@ -1012,9 +1064,9 @@ impl ShapeState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Move the selected points by dragging the mouse.
|
||||
/// Move the selected points and segments by dragging the mouse.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn move_selected_points(
|
||||
pub fn move_selected_points_and_segments(
|
||||
&self,
|
||||
handle_lengths: Option<OpposingHandleLengths>,
|
||||
document: &DocumentMessageHandler,
|
||||
@@ -1040,7 +1092,17 @@ impl ShapeState {
|
||||
};
|
||||
let delta = delta_transform.inverse().transform_vector2(delta);
|
||||
|
||||
for &point in state.selected_points.iter() {
|
||||
// Make a new collection of anchor points which needs to be moved
|
||||
let mut affected_points = state.selected_points.clone();
|
||||
|
||||
for (segment_id, _, start, end) in vector_data.segment_bezier_iter() {
|
||||
if state.is_segment_selected(segment_id) {
|
||||
affected_points.insert(ManipulatorPointId::Anchor(start));
|
||||
affected_points.insert(ManipulatorPointId::Anchor(end));
|
||||
}
|
||||
}
|
||||
|
||||
for &point in affected_points.iter() {
|
||||
if self.is_point_ignored(&point) {
|
||||
continue;
|
||||
}
|
||||
@@ -1055,7 +1117,7 @@ impl ShapeState {
|
||||
};
|
||||
|
||||
let Some(anchor_id) = point.get_anchor(&vector_data) else { continue };
|
||||
if state.is_selected(ManipulatorPointId::Anchor(anchor_id)) {
|
||||
if state.is_point_selected(ManipulatorPointId::Anchor(anchor_id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1074,7 +1136,7 @@ impl ShapeState {
|
||||
continue;
|
||||
}
|
||||
|
||||
if state.is_selected(other.to_manipulator_point()) {
|
||||
if state.is_point_selected(other.to_manipulator_point()) {
|
||||
// If two colinear handles are being dragged at the same time but not the anchor, it is necessary to break the colinear state.
|
||||
let handles = [handle, other];
|
||||
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };
|
||||
@@ -1125,12 +1187,12 @@ impl ShapeState {
|
||||
// ii) The anchor is not selected.
|
||||
|
||||
let anchor = handles[0].to_manipulator_point().get_anchor(&vector_data)?;
|
||||
let anchor_selected = state.is_selected(ManipulatorPointId::Anchor(anchor));
|
||||
let anchor_selected = state.is_point_selected(ManipulatorPointId::Anchor(anchor));
|
||||
if anchor_selected {
|
||||
return None;
|
||||
}
|
||||
|
||||
let handles_selected = handles.map(|handle| state.is_selected(handle.to_manipulator_point()));
|
||||
let handles_selected = handles.map(|handle| state.is_point_selected(handle.to_manipulator_point()));
|
||||
|
||||
let other = match handles_selected {
|
||||
[true, false] => handles[1],
|
||||
@@ -1212,11 +1274,15 @@ impl ShapeState {
|
||||
continue;
|
||||
};
|
||||
|
||||
let selected_segments = &state.selected_segments;
|
||||
|
||||
for point in std::mem::take(&mut state.selected_points) {
|
||||
match point {
|
||||
ManipulatorPointId::Anchor(anchor) => {
|
||||
if let Some(handles) = Self::dissolve_anchor(anchor, responses, layer, &vector_data) {
|
||||
missing_anchors.insert(anchor, handles);
|
||||
if !vector_data.all_connected(anchor).any(|a| selected_segments.contains(&a.segment)) {
|
||||
missing_anchors.insert(anchor, handles);
|
||||
}
|
||||
}
|
||||
deleted_anchors.insert(anchor);
|
||||
}
|
||||
@@ -1286,6 +1352,8 @@ impl ShapeState {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Avoid reconnecting to points which have adjacent segments selected
|
||||
|
||||
// Grab the handles from the opposite side of the segment(s) being deleted and make it relative to the anchor
|
||||
let [handle_start, handle_end] = [start, end].map(|(handle, _, _)| {
|
||||
let handle = handle.opposite();
|
||||
@@ -1357,6 +1425,20 @@ impl ShapeState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_selected_segments(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
for (&layer, state) in &self.selected_shape_state {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
for (segment, _, start, end) in vector_data.segment_bezier_iter() {
|
||||
if state.selected_segments.contains(&segment) {
|
||||
self.dissolve_segment(responses, layer, &vector_data, segment, [start, end]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn break_path_at_selected_point(&self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
for (&layer, state) in &self.selected_shape_state {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
|
||||
@@ -1746,10 +1828,13 @@ impl ShapeState {
|
||||
.filter(|&handle| anchor.abs_diff_eq(handle, 1e-5))
|
||||
.count();
|
||||
|
||||
// Check if the anchor is connected to linear segments.
|
||||
let one_or_more_segment_linear = vector_data.connected_linear_segments(id) != 0;
|
||||
|
||||
// Check by comparing the handle positions to the anchor if this manipulator group is a point
|
||||
for point in self.selected_points() {
|
||||
let Some(point_id) = point.as_anchor() else { continue };
|
||||
if positions != 0 {
|
||||
if positions != 0 || one_or_more_segment_linear {
|
||||
self.convert_manipulator_handles_to_colinear(&vector_data, point_id, responses, layer);
|
||||
} else {
|
||||
for handle in vector_data.all_connected(point_id) {
|
||||
@@ -1801,6 +1886,7 @@ impl ShapeState {
|
||||
false
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn select_all_in_shape(
|
||||
&mut self,
|
||||
network_interface: &NodeNetworkInterface,
|
||||
@@ -1808,13 +1894,17 @@ impl ShapeState {
|
||||
selection_change: SelectionChange,
|
||||
path_overlay_mode: PathOverlayMode,
|
||||
frontier_handles_info: Option<HashMap<SegmentId, Vec<PointId>>>,
|
||||
select_segments: bool,
|
||||
// Here, "selection mode" represents touched or enclosed, not to be confused with editing modes
|
||||
selection_mode: SelectionMode,
|
||||
) {
|
||||
let selected_points = self.selected_points().cloned().collect::<HashSet<_>>();
|
||||
let selected_segments = selected_segments(network_interface, self);
|
||||
|
||||
for (&layer, state) in &mut self.selected_shape_state {
|
||||
if selection_change == SelectionChange::Clear {
|
||||
state.clear_points()
|
||||
state.clear_points();
|
||||
state.clear_segments();
|
||||
}
|
||||
|
||||
let vector_data = network_interface.compute_modified_vector(layer);
|
||||
@@ -1840,7 +1930,46 @@ impl ShapeState {
|
||||
None
|
||||
};
|
||||
|
||||
// Selection segments
|
||||
for (id, bezier, _, _) in vector_data.segment_bezier_iter() {
|
||||
if select_segments {
|
||||
// Select segments if they lie inside the bounding box or lasso polygon
|
||||
let segment_bbox = calculate_bezier_bbox(bezier);
|
||||
let bottom_left = transform.transform_point2(segment_bbox[0]);
|
||||
let top_right = transform.transform_point2(segment_bbox[1]);
|
||||
|
||||
let select = match selection_shape {
|
||||
SelectionShape::Box(quad) => {
|
||||
let enclosed = quad[0].min(quad[1]).cmple(bottom_left).all() && quad[0].max(quad[1]).cmpge(top_right).all();
|
||||
match selection_mode {
|
||||
SelectionMode::Enclosed => enclosed,
|
||||
_ => {
|
||||
// Check for intersection with the segment
|
||||
enclosed || is_intersecting(bezier, quad, transform)
|
||||
}
|
||||
}
|
||||
}
|
||||
SelectionShape::Lasso(_) => {
|
||||
let polygon = polygon_subpath.as_ref().expect("If `selection_shape` is a polygon then subpath is constructed beforehand.");
|
||||
|
||||
// Sample 10 points on the bezier and check if all or some lie inside the polygon
|
||||
let points = bezier.compute_lookup_table(Some(10), None);
|
||||
match selection_mode {
|
||||
SelectionMode::Enclosed => points.map(|p| transform.transform_point2(p)).all(|p| polygon.contains_point(p)),
|
||||
_ => points.map(|p| transform.transform_point2(p)).any(|p| polygon.contains_point(p)),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if select {
|
||||
match selection_change {
|
||||
SelectionChange::Shrink => state.deselect_segment(id),
|
||||
_ => state.select_segment(id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Selecting handles
|
||||
for (position, id) in [(bezier.handle_start(), ManipulatorPointId::PrimaryHandle(id)), (bezier.handle_end(), ManipulatorPointId::EndHandle(id))] {
|
||||
let Some(position) = position else { continue };
|
||||
let transformed_position = transform.transform_point2(position);
|
||||
@@ -1873,6 +2002,7 @@ impl ShapeState {
|
||||
}
|
||||
}
|
||||
|
||||
// Checking for selection of anchor points
|
||||
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
|
||||
let transformed_position = transform.transform_point2(position);
|
||||
|
||||
|
||||
@@ -8,11 +8,12 @@ use crate::messages::tool::common_functionality::graph_modification_utils::get_t
|
||||
use crate::messages::tool::common_functionality::transformation_cage::SelectedEdges;
|
||||
use crate::messages::tool::tool_messages::path_tool::PathOverlayMode;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use bezier_rs::Bezier;
|
||||
use bezier_rs::{Bezier, BezierHandles};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::{FontCache, load_face};
|
||||
use graphene_std::text::{FontCache, load_font};
|
||||
use graphene_std::vector::{HandleExt, HandleId, ManipulatorPointId, PointId, SegmentId, VectorData, VectorModificationType};
|
||||
use kurbo::{CubicBez, Line, ParamCurveExtrema, PathSeg, Point, QuadBez};
|
||||
|
||||
/// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable.
|
||||
pub fn should_extend(
|
||||
@@ -69,8 +70,8 @@ pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageH
|
||||
return Quad::from_box([DVec2::ZERO, DVec2::ZERO]);
|
||||
};
|
||||
|
||||
let buzz_face = font_cache.get(font).map(|data| load_face(data));
|
||||
let far = graphene_std::text::bounding_box(text, buzz_face.as_ref(), typesetting, false);
|
||||
let font_data = font_cache.get(font).map(|data| load_font(data));
|
||||
let far = graphene_std::text::bounding_box(text, font_data, typesetting, false);
|
||||
|
||||
Quad::from_box([DVec2::ZERO, far])
|
||||
}
|
||||
@@ -204,6 +205,71 @@ pub fn is_visible_point(
|
||||
}
|
||||
}
|
||||
|
||||
/// Function to find the bounding box of bezier (uses method from kurbo)
|
||||
pub fn calculate_bezier_bbox(bezier: Bezier) -> [DVec2; 2] {
|
||||
let start = Point::new(bezier.start.x, bezier.start.y);
|
||||
let end = Point::new(bezier.end.x, bezier.end.y);
|
||||
let bbox = match bezier.handles {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
let p1 = Point::new(handle_start.x, handle_start.y);
|
||||
let p2 = Point::new(handle_end.x, handle_end.y);
|
||||
CubicBez::new(start, p1, p2, end).bounding_box()
|
||||
}
|
||||
BezierHandles::Quadratic { handle } => {
|
||||
let p1 = Point::new(handle.x, handle.y);
|
||||
QuadBez::new(start, p1, end).bounding_box()
|
||||
}
|
||||
BezierHandles::Linear => Line::new(start, end).bounding_box(),
|
||||
};
|
||||
[DVec2::new(bbox.x0, bbox.y0), DVec2::new(bbox.x1, bbox.y1)]
|
||||
}
|
||||
|
||||
pub fn is_intersecting(bezier: Bezier, quad: [DVec2; 2], transform: DAffine2) -> bool {
|
||||
let to_layerspace = transform.inverse();
|
||||
let quad = [to_layerspace.transform_point2(quad[0]), to_layerspace.transform_point2(quad[1])];
|
||||
let start = Point::new(bezier.start.x, bezier.start.y);
|
||||
let end = Point::new(bezier.end.x, bezier.end.y);
|
||||
let segment = match bezier.handles {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
let p1 = Point::new(handle_start.x, handle_start.y);
|
||||
let p2 = Point::new(handle_end.x, handle_end.y);
|
||||
PathSeg::Cubic(CubicBez::new(start, p1, p2, end))
|
||||
}
|
||||
BezierHandles::Quadratic { handle } => {
|
||||
let p1 = Point::new(handle.x, handle.y);
|
||||
PathSeg::Quad(QuadBez::new(start, p1, end))
|
||||
}
|
||||
BezierHandles::Linear => PathSeg::Line(Line::new(start, end)),
|
||||
};
|
||||
|
||||
// Create a list of all the sides
|
||||
let sides = [
|
||||
Line::new((quad[0].x, quad[0].y), (quad[1].x, quad[0].y)),
|
||||
Line::new((quad[0].x, quad[0].y), (quad[0].x, quad[1].y)),
|
||||
Line::new((quad[1].x, quad[1].y), (quad[1].x, quad[0].y)),
|
||||
Line::new((quad[1].x, quad[1].y), (quad[0].x, quad[1].y)),
|
||||
];
|
||||
|
||||
let mut is_intersecting = false;
|
||||
for line in sides {
|
||||
let intersections = segment.intersect_line(line);
|
||||
let mut intersects = false;
|
||||
for intersection in intersections {
|
||||
if intersection.line_t <= 1. && intersection.line_t >= 0. && intersection.segment_t <= 1. && intersection.segment_t >= 0. {
|
||||
// There is a valid intersection point
|
||||
intersects = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if intersects {
|
||||
is_intersecting = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
is_intersecting
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn resize_bounds(
|
||||
document: &DocumentMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
@@ -221,7 +287,7 @@ pub fn resize_bounds(
|
||||
let snap = Some(SizeSnapData {
|
||||
manager: snap_manager,
|
||||
points: snap_candidates,
|
||||
snap_data: SnapData::ignore(document, input, &dragging_layers),
|
||||
snap_data: SnapData::ignore(document, input, dragging_layers),
|
||||
});
|
||||
let (position, size) = movement.new_size(input.mouse.position, bounds.original_bound_transform, center, constrain, snap);
|
||||
let (delta, mut pivot) = movement.bounds_to_scale_transform(position, size);
|
||||
@@ -238,11 +304,12 @@ pub fn resize_bounds(
|
||||
}
|
||||
});
|
||||
|
||||
let mut selected = Selected::new(&mut bounds.original_transforms, &mut pivot, &dragging_layers, responses, &document.network_interface, None, &tool, None);
|
||||
let mut selected = Selected::new(&mut bounds.original_transforms, &mut pivot, dragging_layers, responses, &document.network_interface, None, &tool, None);
|
||||
selected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse(), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn rotate_bounds(
|
||||
document: &DocumentMessageHandler,
|
||||
responses: &mut VecDeque<Message>,
|
||||
@@ -280,7 +347,7 @@ pub fn rotate_bounds(
|
||||
let mut selected = Selected::new(
|
||||
&mut bounds.original_transforms,
|
||||
&mut bounds.center_of_transformation,
|
||||
&dragging_layers,
|
||||
dragging_layers,
|
||||
responses,
|
||||
&document.network_interface,
|
||||
None,
|
||||
@@ -313,7 +380,7 @@ pub fn skew_bounds(
|
||||
}
|
||||
});
|
||||
|
||||
let mut selected = Selected::new(&mut bounds.original_transforms, &mut pivot, &layers, responses, &document.network_interface, None, &tool, None);
|
||||
let mut selected = Selected::new(&mut bounds.original_transforms, &mut pivot, layers, responses, &document.network_interface, None, &tool, None);
|
||||
selected.apply_transformation(bounds.original_bound_transform * transformation * bounds.original_bound_transform.inverse(), None);
|
||||
}
|
||||
}
|
||||
@@ -365,7 +432,7 @@ pub fn transforming_transform_cage(
|
||||
let mut selected = Selected::new(
|
||||
&mut bounds.original_transforms,
|
||||
&mut bounds.center_of_transformation,
|
||||
&layers_dragging,
|
||||
layers_dragging,
|
||||
responses,
|
||||
&document.network_interface,
|
||||
None,
|
||||
@@ -423,7 +490,7 @@ pub fn transforming_transform_cage(
|
||||
}
|
||||
|
||||
// No resize, rotate, or skew
|
||||
return (false, false, false);
|
||||
(false, false, false)
|
||||
}
|
||||
|
||||
/// Calculates similarity metric between new bezier curve and two old beziers by using sampled points.
|
||||
|
||||
@@ -8,8 +8,8 @@ use crate::messages::tool::common_functionality::color_selector::{ToolColorOptio
|
||||
use graph_craft::document::NodeId;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::brush::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
|
||||
|
||||
const BRUSH_MAX_SIZE: f64 = 5000.;
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ pub struct PathTool {
|
||||
#[derive(Default)]
|
||||
pub struct PathToolOptions {
|
||||
path_overlay_mode: PathOverlayMode,
|
||||
path_editing_mode: PathEditingMode,
|
||||
}
|
||||
|
||||
#[impl_message(Message, ToolMessage, Path)]
|
||||
@@ -70,6 +71,7 @@ pub enum PathToolMessage {
|
||||
lasso_select: Key,
|
||||
handle_drag_from_anchor: Key,
|
||||
drag_restore_handle: Key,
|
||||
molding_in_segment_edit: Key,
|
||||
},
|
||||
NudgeSelectedPoints {
|
||||
delta_x: f64,
|
||||
@@ -117,9 +119,26 @@ pub enum PathOverlayMode {
|
||||
FrontierHandles = 2,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)]
|
||||
pub struct PathEditingMode {
|
||||
point_editing_mode: bool,
|
||||
segment_editing_mode: bool,
|
||||
}
|
||||
|
||||
impl Default for PathEditingMode {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
point_editing_mode: true,
|
||||
segment_editing_mode: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum PathOptionsUpdate {
|
||||
OverlayModeType(PathOverlayMode),
|
||||
PointEditingMode { enabled: bool },
|
||||
SegmentEditingMode { enabled: bool },
|
||||
}
|
||||
|
||||
impl ToolMetadata for PathTool {
|
||||
@@ -204,6 +223,19 @@ impl LayoutHolder for PathTool {
|
||||
.for_checkbox(&mut checkbox_id)
|
||||
.widget_holder();
|
||||
|
||||
let point_editing_mode = CheckboxInput::new(self.options.path_editing_mode.point_editing_mode)
|
||||
// TODO(Keavon): Replace with a real icon
|
||||
.icon("Dot")
|
||||
.tooltip("Point Editing Mode")
|
||||
.on_update(|input| PathToolMessage::UpdateOptions(PathOptionsUpdate::PointEditingMode { enabled: input.checked }).into())
|
||||
.widget_holder();
|
||||
let segment_editing_mode = CheckboxInput::new(self.options.path_editing_mode.segment_editing_mode)
|
||||
// TODO(Keavon): Replace with a real icon
|
||||
.icon("Remove")
|
||||
.tooltip("Segment Editing Mode")
|
||||
.on_update(|input| PathToolMessage::UpdateOptions(PathOptionsUpdate::SegmentEditingMode { enabled: input.checked }).into())
|
||||
.widget_holder();
|
||||
|
||||
let path_overlay_mode_widget = RadioInput::new(vec![
|
||||
RadioEntryData::new("all")
|
||||
.icon("HandleVisibilityAll")
|
||||
@@ -228,8 +260,12 @@ impl LayoutHolder for PathTool {
|
||||
y_location,
|
||||
unrelated_seperator.clone(),
|
||||
colinear_handle_checkbox,
|
||||
related_seperator,
|
||||
related_seperator.clone(),
|
||||
colinear_handles_label,
|
||||
unrelated_seperator.clone(),
|
||||
point_editing_mode,
|
||||
related_seperator.clone(),
|
||||
segment_editing_mode,
|
||||
unrelated_seperator,
|
||||
path_overlay_mode_widget,
|
||||
],
|
||||
@@ -247,6 +283,14 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
||||
self.options.path_overlay_mode = overlay_mode_type;
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
PathOptionsUpdate::PointEditingMode { enabled } => {
|
||||
self.options.path_editing_mode.point_editing_mode = enabled;
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
PathOptionsUpdate::SegmentEditingMode { enabled } => {
|
||||
self.options.path_editing_mode.segment_editing_mode = enabled;
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
},
|
||||
ToolMessage::Path(PathToolMessage::ClosePath) => {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
@@ -409,6 +453,7 @@ struct PathToolData {
|
||||
angle: f64,
|
||||
opposite_handle_position: Option<DVec2>,
|
||||
last_clicked_point_was_selected: bool,
|
||||
last_clicked_segment_was_selected: bool,
|
||||
snapping_axis: Option<Axis>,
|
||||
alt_clicked_on_anchor: bool,
|
||||
alt_dragging_from_anchor: bool,
|
||||
@@ -420,6 +465,7 @@ struct PathToolData {
|
||||
frontier_handles_info: Option<HashMap<SegmentId, Vec<PointId>>>,
|
||||
adjacent_anchor_offset: Option<DVec2>,
|
||||
sliding_point_info: Option<SlidingPointInfo>,
|
||||
started_drawing_from_inside: bool,
|
||||
}
|
||||
|
||||
impl PathToolData {
|
||||
@@ -480,6 +526,7 @@ impl PathToolData {
|
||||
self.selection_status = selection_status;
|
||||
}
|
||||
|
||||
// TODO: This function is for basic point select mode. We definitely need to make a new one for the segment select mode.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn mouse_down(
|
||||
&mut self,
|
||||
@@ -491,7 +538,10 @@ impl PathToolData {
|
||||
lasso_select: bool,
|
||||
handle_drag_from_anchor: bool,
|
||||
drag_zero_handle: bool,
|
||||
molding_in_segment_edit: bool,
|
||||
path_overlay_mode: PathOverlayMode,
|
||||
segment_editing_mode: bool,
|
||||
point_editing_mode: bool,
|
||||
) -> PathToolFsmState {
|
||||
self.double_click_handled = false;
|
||||
self.opposing_handle_lengths = None;
|
||||
@@ -514,6 +564,7 @@ impl PathToolData {
|
||||
SELECTION_THRESHOLD,
|
||||
path_overlay_mode,
|
||||
self.frontier_handles_info.clone(),
|
||||
point_editing_mode,
|
||||
) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
@@ -597,25 +648,51 @@ impl PathToolData {
|
||||
}
|
||||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
}
|
||||
// We didn't find a point nearby, so we will see if there is a segment to insert a point on
|
||||
else if let Some(closed_segment) = &mut self.segment {
|
||||
// We didn't find a point nearby, so we will see if there is a segment to select or insert a point on
|
||||
else if let Some(segment) = shape_editor.upper_closest_segment(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD) {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
|
||||
// Calculating and storing handle positions
|
||||
let handle1 = ManipulatorPointId::PrimaryHandle(closed_segment.segment());
|
||||
let handle2 = ManipulatorPointId::EndHandle(closed_segment.segment());
|
||||
if segment_editing_mode && !molding_in_segment_edit {
|
||||
let layer = segment.layer();
|
||||
let segment_id = segment.segment();
|
||||
let already_selected = shape_editor.selected_shape_state.get(&layer).is_some_and(|state| state.is_segment_selected(segment_id));
|
||||
self.last_clicked_segment_was_selected = already_selected;
|
||||
|
||||
if let Some(vector_data) = document.network_interface.compute_modified_vector(closed_segment.layer()) {
|
||||
if let (Some(pos1), Some(pos2)) = (handle1.get_position(&vector_data), handle2.get_position(&vector_data)) {
|
||||
self.molding_info = Some((pos1, pos2))
|
||||
if !(already_selected && extend_selection) {
|
||||
let retain_existing_selection = extend_selection || already_selected;
|
||||
if !retain_existing_selection {
|
||||
shape_editor.deselect_all_segments();
|
||||
shape_editor.deselect_all_points();
|
||||
}
|
||||
|
||||
// Add to selected segments
|
||||
if let Some(selected_shape_state) = shape_editor.selected_shape_state.get_mut(&layer) {
|
||||
selected_shape_state.select_segment(segment_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PathToolFsmState::MoldingSegment
|
||||
self.drag_start_pos = input.mouse.position;
|
||||
|
||||
let viewport_to_document = document.metadata().document_to_viewport.inverse();
|
||||
self.previous_mouse_position = viewport_to_document.transform_point2(input.mouse.position);
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
} else {
|
||||
let handle1 = ManipulatorPointId::PrimaryHandle(segment.segment());
|
||||
let handle2 = ManipulatorPointId::EndHandle(segment.segment());
|
||||
if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) {
|
||||
if let (Some(pos1), Some(pos2)) = (handle1.get_position(&vector_data), handle2.get_position(&vector_data)) {
|
||||
self.molding_info = Some((pos1, pos2))
|
||||
}
|
||||
}
|
||||
PathToolFsmState::MoldingSegment
|
||||
}
|
||||
}
|
||||
// We didn't find a segment, so consider selecting the nearest shape instead
|
||||
// We didn't find a segment, so consider selecting the nearest shape instead and start drawing
|
||||
else if let Some(layer) = document.click(input) {
|
||||
shape_editor.deselect_all_points();
|
||||
shape_editor.deselect_all_segments();
|
||||
if extend_selection {
|
||||
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });
|
||||
} else {
|
||||
@@ -624,9 +701,10 @@ impl PathToolData {
|
||||
self.drag_start_pos = input.mouse.position;
|
||||
self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
self.started_drawing_from_inside = true;
|
||||
|
||||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
let selection_shape = if lasso_select { SelectionShapeType::Lasso } else { SelectionShapeType::Box };
|
||||
PathToolFsmState::Drawing { selection_shape }
|
||||
}
|
||||
// Start drawing
|
||||
else {
|
||||
@@ -648,7 +726,7 @@ impl PathToolData {
|
||||
let transform = document.metadata().transform_to_document(layer);
|
||||
|
||||
let mut layer_manipulators = HashSet::with_hasher(NoHashBuilder);
|
||||
for point in state.selected() {
|
||||
for point in state.selected_points() {
|
||||
let Some(anchor) = point.get_anchor(&vector_data) else { continue };
|
||||
layer_manipulators.insert(anchor);
|
||||
let Some([handle1, handle2]) = point.get_handle_pair(&vector_data) else { continue };
|
||||
@@ -752,7 +830,7 @@ impl PathToolData {
|
||||
}
|
||||
|
||||
// Only count selected handles
|
||||
let selected_handle = selection.selected().next()?.as_handle()?;
|
||||
let selected_handle = selection.selected_points().next()?.as_handle()?;
|
||||
let handle_id = selected_handle.to_manipulator_point();
|
||||
|
||||
let layer_to_document = document.metadata().transform_to_document(*layer);
|
||||
@@ -879,7 +957,7 @@ impl PathToolData {
|
||||
let drag_start = self.drag_start_pos;
|
||||
let opposite_delta = drag_start - current_mouse;
|
||||
|
||||
shape_editor.move_selected_points(None, document, opposite_delta, false, true, false, None, false, responses);
|
||||
shape_editor.move_selected_points_and_segments(None, document, opposite_delta, false, true, false, None, false, responses);
|
||||
|
||||
// Calculate the projected delta and shift the points along that delta
|
||||
let delta = current_mouse - drag_start;
|
||||
@@ -891,7 +969,7 @@ impl PathToolData {
|
||||
_ => DVec2::new(delta.x, 0.),
|
||||
};
|
||||
|
||||
shape_editor.move_selected_points(None, document, projected_delta, false, true, false, None, false, responses);
|
||||
shape_editor.move_selected_points_and_segments(None, document, projected_delta, false, true, false, None, false, responses);
|
||||
}
|
||||
|
||||
fn stop_snap_along_axis(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, input: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||
@@ -907,12 +985,12 @@ impl PathToolData {
|
||||
_ => DVec2::new(opposite_delta.x, 0.),
|
||||
};
|
||||
|
||||
shape_editor.move_selected_points(None, document, opposite_projected_delta, false, true, false, None, false, responses);
|
||||
shape_editor.move_selected_points_and_segments(None, document, opposite_projected_delta, false, true, false, None, false, responses);
|
||||
|
||||
// Calculate what actually would have been the original delta for the point, and apply that
|
||||
let delta = current_mouse - drag_start;
|
||||
|
||||
shape_editor.move_selected_points(None, document, delta, false, true, false, None, false, responses);
|
||||
shape_editor.move_selected_points_and_segments(None, document, delta, false, true, false, None, false, responses);
|
||||
|
||||
self.snapping_axis = None;
|
||||
}
|
||||
@@ -934,6 +1012,28 @@ impl PathToolData {
|
||||
tangent_vector.try_normalize()
|
||||
}
|
||||
|
||||
fn update_closest_segment(&mut self, shape_editor: &mut ShapeState, position: DVec2, document: &DocumentMessageHandler, path_overlay_mode: PathOverlayMode) {
|
||||
// Check if there is no point nearby
|
||||
if shape_editor
|
||||
.find_nearest_visible_point_indices(&document.network_interface, position, SELECTION_THRESHOLD, path_overlay_mode, self.frontier_handles_info.clone())
|
||||
.is_some()
|
||||
{
|
||||
self.segment = None;
|
||||
}
|
||||
// If already hovering on a segment, then recalculate its closest point
|
||||
else if let Some(closest_segment) = &mut self.segment {
|
||||
closest_segment.update_closest_point(document.metadata(), position);
|
||||
|
||||
if closest_segment.too_far(position, SEGMENT_INSERTION_DISTANCE) {
|
||||
self.segment = None;
|
||||
}
|
||||
}
|
||||
// If not, check that if there is some closest segment or not
|
||||
else if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, position, SEGMENT_INSERTION_DISTANCE) {
|
||||
self.segment = Some(closest_segment);
|
||||
}
|
||||
}
|
||||
|
||||
fn start_sliding_point(&mut self, shape_editor: &mut ShapeState, document: &DocumentMessageHandler) -> bool {
|
||||
let single_anchor_selected = shape_editor.selected_points().count() == 1 && shape_editor.selected_points().any(|point| matches!(point, ManipulatorPointId::Anchor(_)));
|
||||
|
||||
@@ -1196,7 +1296,7 @@ impl PathToolData {
|
||||
self.temporary_colinear_handles = false;
|
||||
skip_opposite = true;
|
||||
}
|
||||
shape_editor.move_selected_points(handle_lengths, document, snapped_delta, equidistant, true, was_alt_dragging, opposite, skip_opposite, responses);
|
||||
shape_editor.move_selected_points_and_segments(handle_lengths, document, snapped_delta, equidistant, true, was_alt_dragging, opposite, skip_opposite, responses);
|
||||
self.previous_mouse_position += document_to_viewport.inverse().transform_vector2(snapped_delta);
|
||||
} else {
|
||||
let Some(axis) = self.snapping_axis else { return };
|
||||
@@ -1205,7 +1305,7 @@ impl PathToolData {
|
||||
Axis::Y => DVec2::new(0., unsnapped_delta.y),
|
||||
_ => DVec2::new(unsnapped_delta.x, 0.),
|
||||
};
|
||||
shape_editor.move_selected_points(handle_lengths, document, projected_delta, equidistant, true, false, opposite, false, responses);
|
||||
shape_editor.move_selected_points_and_segments(handle_lengths, document, projected_delta, equidistant, true, false, opposite, false, responses);
|
||||
self.previous_mouse_position += document_to_viewport.inverse().transform_vector2(unsnapped_delta);
|
||||
}
|
||||
|
||||
@@ -1229,7 +1329,7 @@ impl Fsm for PathToolFsmState {
|
||||
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
|
||||
let ToolActionHandlerData { document, input, shape_editor, .. } = tool_action_data;
|
||||
|
||||
update_dynamic_hints(self, responses, shape_editor, document, tool_data);
|
||||
update_dynamic_hints(self, responses, shape_editor, document, tool_data, tool_options);
|
||||
|
||||
let ToolMessage::Path(event) = event else { return self };
|
||||
match (self, event) {
|
||||
@@ -1313,48 +1413,43 @@ impl Fsm for PathToolFsmState {
|
||||
|
||||
match self {
|
||||
Self::Ready => {
|
||||
// Check if there is no point nearby
|
||||
if shape_editor
|
||||
.find_nearest_visible_point_indices(
|
||||
&document.network_interface,
|
||||
input.mouse.position,
|
||||
SELECTION_THRESHOLD,
|
||||
tool_options.path_overlay_mode,
|
||||
tool_data.frontier_handles_info.clone(),
|
||||
)
|
||||
.is_some()
|
||||
{
|
||||
tool_data.segment = None;
|
||||
}
|
||||
// If already hovering on a segment, then recalculate its closest point
|
||||
else if let Some(closest_segment) = &mut tool_data.segment {
|
||||
closest_segment.update_closest_point(document.metadata(), input.mouse.position);
|
||||
|
||||
if closest_segment.too_far(input.mouse.position, SEGMENT_INSERTION_DISTANCE) {
|
||||
tool_data.segment = None;
|
||||
}
|
||||
}
|
||||
// If not, check that if there is some closest segment or not
|
||||
else if let Some(closest_segment) = shape_editor.upper_closest_segment(&document.network_interface, input.mouse.position, SEGMENT_INSERTION_DISTANCE) {
|
||||
tool_data.segment = Some(closest_segment);
|
||||
}
|
||||
tool_data.update_closest_segment(shape_editor, input.mouse.position, document, tool_options.path_overlay_mode);
|
||||
|
||||
if let Some(closest_segment) = &tool_data.segment {
|
||||
let perp = closest_segment.calculate_perp(document);
|
||||
let point = closest_segment.closest_point(document.metadata());
|
||||
if tool_options.path_editing_mode.segment_editing_mode {
|
||||
let transform = document.metadata().transform_to_viewport(closest_segment.layer());
|
||||
|
||||
// Draw an X on the segment
|
||||
if tool_data.delete_segment_pressed {
|
||||
let angle = 45_f64.to_radians();
|
||||
let tilted_line = DVec2::from_angle(angle).rotate(perp);
|
||||
let tilted_perp = tilted_line.perp();
|
||||
overlay_context.outline_overlay_bezier(closest_segment.bezier(), transform);
|
||||
|
||||
overlay_context.line(point - tilted_line * SEGMENT_OVERLAY_SIZE, point + tilted_line * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None);
|
||||
overlay_context.line(point - tilted_perp * SEGMENT_OVERLAY_SIZE, point + tilted_perp * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None);
|
||||
}
|
||||
// Draw a line on the segment
|
||||
else {
|
||||
overlay_context.line(point - perp * SEGMENT_OVERLAY_SIZE, point + perp * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None);
|
||||
// Draw the anchors again
|
||||
let display_anchors = overlay_context.visibility_settings.anchors();
|
||||
if display_anchors {
|
||||
let start_pos = transform.transform_point2(closest_segment.bezier().start);
|
||||
let end_pos = transform.transform_point2(closest_segment.bezier().end);
|
||||
let start_id = closest_segment.points()[0];
|
||||
let end_id = closest_segment.points()[1];
|
||||
if let Some(shape_state) = shape_editor.selected_shape_state.get_mut(&closest_segment.layer()) {
|
||||
overlay_context.manipulator_anchor(start_pos, shape_state.is_point_selected(ManipulatorPointId::Anchor(start_id)), None);
|
||||
overlay_context.manipulator_anchor(end_pos, shape_state.is_point_selected(ManipulatorPointId::Anchor(end_id)), None);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let perp = closest_segment.calculate_perp(document);
|
||||
let point = closest_segment.closest_point(document.metadata());
|
||||
|
||||
// Draw an X on the segment
|
||||
if tool_data.delete_segment_pressed {
|
||||
let angle = 45_f64.to_radians();
|
||||
let tilted_line = DVec2::from_angle(angle).rotate(perp);
|
||||
let tilted_perp = tilted_line.perp();
|
||||
|
||||
overlay_context.line(point - tilted_line * SEGMENT_OVERLAY_SIZE, point + tilted_line * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None);
|
||||
overlay_context.line(point - tilted_perp * SEGMENT_OVERLAY_SIZE, point + tilted_perp * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None);
|
||||
}
|
||||
// Draw a line on the segment
|
||||
else {
|
||||
overlay_context.line(point - perp * SEGMENT_OVERLAY_SIZE, point + perp * SEGMENT_OVERLAY_SIZE, Some(COLOR_OVERLAY_BLUE), None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1374,11 +1469,13 @@ impl Fsm for PathToolFsmState {
|
||||
let quad = tool_data.selection_quad(document.metadata());
|
||||
let polygon = &tool_data.lasso_polygon;
|
||||
|
||||
match (selection_shape, selection_mode) {
|
||||
(SelectionShapeType::Box, SelectionMode::Enclosed) => overlay_context.dashed_quad(quad, None, fill_color, Some(4.), Some(4.), Some(0.5)),
|
||||
(SelectionShapeType::Lasso, SelectionMode::Enclosed) => overlay_context.dashed_polygon(polygon, None, fill_color, Some(4.), Some(4.), Some(0.5)),
|
||||
(SelectionShapeType::Box, _) => overlay_context.quad(quad, None, fill_color),
|
||||
(SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, None, fill_color),
|
||||
match (selection_shape, selection_mode, tool_data.started_drawing_from_inside) {
|
||||
// Don't draw box if it is from inside a shape and selection just began
|
||||
(SelectionShapeType::Box, SelectionMode::Enclosed, false) => overlay_context.dashed_quad(quad, None, fill_color, Some(4.), Some(4.), Some(0.5)),
|
||||
(SelectionShapeType::Lasso, SelectionMode::Enclosed, _) => overlay_context.dashed_polygon(polygon, None, fill_color, Some(4.), Some(4.), Some(0.5)),
|
||||
(SelectionShapeType::Box, _, false) => overlay_context.quad(quad, None, fill_color),
|
||||
(SelectionShapeType::Lasso, _, _) => overlay_context.polygon(polygon, None, fill_color),
|
||||
(SelectionShapeType::Box, _, _) => {}
|
||||
}
|
||||
}
|
||||
Self::Dragging(_) => {
|
||||
@@ -1424,12 +1521,14 @@ impl Fsm for PathToolFsmState {
|
||||
lasso_select,
|
||||
handle_drag_from_anchor,
|
||||
drag_restore_handle,
|
||||
molding_in_segment_edit,
|
||||
},
|
||||
) => {
|
||||
let extend_selection = input.keyboard.get(extend_selection as usize);
|
||||
let lasso_select = input.keyboard.get(lasso_select as usize);
|
||||
let handle_drag_from_anchor = input.keyboard.get(handle_drag_from_anchor as usize);
|
||||
let drag_zero_handle = input.keyboard.get(drag_restore_handle as usize);
|
||||
let molding_in_segment_edit = input.keyboard.get(molding_in_segment_edit as usize);
|
||||
|
||||
tool_data.selection_mode = None;
|
||||
tool_data.lasso_polygon.clear();
|
||||
@@ -1443,7 +1542,10 @@ impl Fsm for PathToolFsmState {
|
||||
lasso_select,
|
||||
handle_drag_from_anchor,
|
||||
drag_zero_handle,
|
||||
molding_in_segment_edit,
|
||||
tool_options.path_overlay_mode,
|
||||
tool_options.path_editing_mode.segment_editing_mode,
|
||||
tool_options.path_editing_mode.point_editing_mode,
|
||||
)
|
||||
}
|
||||
(
|
||||
@@ -1459,6 +1561,7 @@ impl Fsm for PathToolFsmState {
|
||||
},
|
||||
) => {
|
||||
tool_data.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
|
||||
tool_data.started_drawing_from_inside = false;
|
||||
|
||||
if selection_shape == SelectionShapeType::Lasso {
|
||||
extend_lasso(&mut tool_data.lasso_polygon, input.mouse.position);
|
||||
@@ -1520,12 +1623,6 @@ impl Fsm for PathToolFsmState {
|
||||
tool_data.handle_drag_toggle = true;
|
||||
}
|
||||
|
||||
if tool_data.selection_status.is_none() {
|
||||
if let Some(layer) = document.click(input) {
|
||||
shape_editor.select_all_anchors_in_layer(document, layer);
|
||||
}
|
||||
}
|
||||
|
||||
let anchor_and_handle_toggled = input.keyboard.get(move_anchor_with_handles as usize);
|
||||
let initial_press = anchor_and_handle_toggled && !tool_data.select_anchor_toggled;
|
||||
let released_from_toggle = tool_data.select_anchor_toggled && !anchor_and_handle_toggled;
|
||||
@@ -1711,6 +1808,11 @@ impl Fsm for PathToolFsmState {
|
||||
if tool_data.drag_start_pos == previous_mouse {
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||
} else {
|
||||
let selection_mode = match tool_action_data.preferences.get_selection_mode() {
|
||||
SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(document.metadata()),
|
||||
selection_mode => selection_mode,
|
||||
};
|
||||
|
||||
match selection_shape {
|
||||
SelectionShapeType::Box => {
|
||||
let bbox = [tool_data.drag_start_pos, previous_mouse];
|
||||
@@ -1720,6 +1822,8 @@ impl Fsm for PathToolFsmState {
|
||||
selection_change,
|
||||
tool_options.path_overlay_mode,
|
||||
tool_data.frontier_handles_info.clone(),
|
||||
tool_options.path_editing_mode.segment_editing_mode,
|
||||
selection_mode,
|
||||
);
|
||||
}
|
||||
SelectionShapeType::Lasso => shape_editor.select_all_in_shape(
|
||||
@@ -1728,6 +1832,8 @@ impl Fsm for PathToolFsmState {
|
||||
selection_change,
|
||||
tool_options.path_overlay_mode,
|
||||
tool_data.frontier_handles_info.clone(),
|
||||
tool_options.path_editing_mode.segment_editing_mode,
|
||||
selection_mode,
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -1739,6 +1845,7 @@ impl Fsm for PathToolFsmState {
|
||||
(PathToolFsmState::Dragging { .. }, PathToolMessage::Escape | PathToolMessage::RightClick) => {
|
||||
if tool_data.handle_drag_toggle && tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD {
|
||||
shape_editor.deselect_all_points();
|
||||
shape_editor.deselect_all_segments();
|
||||
shape_editor.select_points_by_manipulator_id(&tool_data.saved_points_before_handle_drag);
|
||||
|
||||
tool_data.saved_points_before_handle_drag.clear();
|
||||
@@ -1787,8 +1894,17 @@ impl Fsm for PathToolFsmState {
|
||||
|
||||
let document_to_viewport = document.metadata().document_to_viewport;
|
||||
let previous_mouse = document_to_viewport.transform_point2(tool_data.previous_mouse_position);
|
||||
if tool_data.drag_start_pos == previous_mouse {
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||
|
||||
let selection_mode = match tool_action_data.preferences.get_selection_mode() {
|
||||
SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(document.metadata()),
|
||||
selection_mode => selection_mode,
|
||||
};
|
||||
|
||||
if tool_data.drag_start_pos.distance(previous_mouse) < 1e-8 {
|
||||
// If click happens inside of a shape then don't set selected nodes to empty
|
||||
if document.click(input).is_none() {
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||
}
|
||||
} else {
|
||||
match selection_shape {
|
||||
SelectionShapeType::Box => {
|
||||
@@ -1799,6 +1915,8 @@ impl Fsm for PathToolFsmState {
|
||||
select_kind,
|
||||
tool_options.path_overlay_mode,
|
||||
tool_data.frontier_handles_info.clone(),
|
||||
tool_options.path_editing_mode.segment_editing_mode,
|
||||
selection_mode,
|
||||
);
|
||||
}
|
||||
SelectionShapeType::Lasso => shape_editor.select_all_in_shape(
|
||||
@@ -1807,6 +1925,8 @@ impl Fsm for PathToolFsmState {
|
||||
select_kind,
|
||||
tool_options.path_overlay_mode,
|
||||
tool_data.frontier_handles_info.clone(),
|
||||
tool_options.path_editing_mode.segment_editing_mode,
|
||||
selection_mode,
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -1827,32 +1947,31 @@ impl Fsm for PathToolFsmState {
|
||||
tool_data.frontier_handles_info.clone(),
|
||||
);
|
||||
|
||||
let nearest_segment = tool_data.segment.clone();
|
||||
|
||||
if let Some(segment) = &mut tool_data.segment {
|
||||
if !drag_occurred && !tool_data.molding_segment {
|
||||
let segment_mode = tool_options.path_editing_mode.segment_editing_mode;
|
||||
if !drag_occurred && !tool_data.molding_segment && !segment_mode {
|
||||
if tool_data.delete_segment_pressed {
|
||||
if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) {
|
||||
shape_editor.dissolve_segment(responses, segment.layer(), &vector_data, segment.segment(), segment.points());
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
}
|
||||
} else {
|
||||
segment.adjusted_insert_and_select(shape_editor, responses, extend_selection);
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
}
|
||||
} else {
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
}
|
||||
|
||||
tool_data.segment = None;
|
||||
tool_data.molding_info = None;
|
||||
tool_data.molding_segment = false;
|
||||
tool_data.temporary_adjacent_handles_while_molding = None;
|
||||
|
||||
return PathToolFsmState::Ready;
|
||||
}
|
||||
|
||||
let segment_mode = tool_options.path_editing_mode.segment_editing_mode;
|
||||
|
||||
if let Some((layer, nearest_point)) = nearest_point {
|
||||
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == point);
|
||||
if !drag_occurred && extend_selection {
|
||||
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == point);
|
||||
if clicked_selected && tool_data.last_clicked_point_was_selected {
|
||||
shape_editor.selected_shape_state.entry(layer).or_default().deselect_point(nearest_point);
|
||||
} else {
|
||||
@@ -1860,6 +1979,49 @@ impl Fsm for PathToolFsmState {
|
||||
}
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
if !drag_occurred && !extend_selection && clicked_selected {
|
||||
if tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty() {
|
||||
tool_data.saved_points_before_anchor_convert_smooth_sharp = shape_editor.selected_points().copied().collect::<HashSet<_>>();
|
||||
}
|
||||
|
||||
shape_editor.deselect_all_points();
|
||||
shape_editor.deselect_all_segments();
|
||||
|
||||
shape_editor.selected_shape_state.entry(layer).or_default().select_point(nearest_point);
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
}
|
||||
// Segment editing mode
|
||||
else if let Some(nearest_segment) = nearest_segment {
|
||||
if segment_mode {
|
||||
let clicked_selected = shape_editor.selected_segments().any(|&segment| segment == nearest_segment.segment());
|
||||
if !drag_occurred && extend_selection {
|
||||
if clicked_selected && tool_data.last_clicked_segment_was_selected {
|
||||
shape_editor
|
||||
.selected_shape_state
|
||||
.entry(nearest_segment.layer())
|
||||
.or_default()
|
||||
.deselect_segment(nearest_segment.segment());
|
||||
} else {
|
||||
shape_editor.selected_shape_state.entry(nearest_segment.layer()).or_default().select_segment(nearest_segment.segment());
|
||||
}
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
if !drag_occurred && !extend_selection && clicked_selected {
|
||||
shape_editor.deselect_all_segments();
|
||||
shape_editor.deselect_all_points();
|
||||
shape_editor.selected_shape_state.entry(nearest_segment.layer()).or_default().select_segment(nearest_segment.segment());
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Deselect all points if the user clicks the filled region of the shape
|
||||
else if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD {
|
||||
shape_editor.deselect_all_points();
|
||||
shape_editor.deselect_all_segments();
|
||||
}
|
||||
|
||||
if tool_data.temporary_colinear_handles {
|
||||
@@ -1885,25 +2047,6 @@ impl Fsm for PathToolFsmState {
|
||||
tool_data.select_anchor_toggled = false;
|
||||
}
|
||||
|
||||
if let Some((layer, nearest_point)) = nearest_point {
|
||||
if !drag_occurred && !extend_selection {
|
||||
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == point);
|
||||
if clicked_selected {
|
||||
if tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty() {
|
||||
tool_data.saved_points_before_anchor_convert_smooth_sharp = shape_editor.selected_points().copied().collect::<HashSet<_>>();
|
||||
}
|
||||
|
||||
shape_editor.deselect_all_points();
|
||||
shape_editor.selected_shape_state.entry(layer).or_default().select_point(nearest_point);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Deselect all points if the user clicks the filled region of the shape
|
||||
else if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD {
|
||||
shape_editor.deselect_all_points();
|
||||
}
|
||||
|
||||
tool_data.snapping_axis = None;
|
||||
tool_data.sliding_point_info = None;
|
||||
|
||||
@@ -1919,8 +2062,8 @@ impl Fsm for PathToolFsmState {
|
||||
(_, PathToolMessage::Delete) => {
|
||||
// Delete the selected points and clean up overlays
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
// shape_editor.delete_selected_points(document, responses);
|
||||
shape_editor.delete_selected_points(document, responses, false);
|
||||
shape_editor.delete_selected_segments(document, responses);
|
||||
responses.add(PathToolMessage::SelectionChanged);
|
||||
|
||||
PathToolFsmState::Ready
|
||||
@@ -1969,6 +2112,7 @@ impl Fsm for PathToolFsmState {
|
||||
if let Some(layer) = document.click(input) {
|
||||
// Select all points in the layer
|
||||
shape_editor.select_connected_anchors(document, layer, input.mouse.position);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
|
||||
PathToolFsmState::Ready
|
||||
@@ -1978,7 +2122,7 @@ impl Fsm for PathToolFsmState {
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::NudgeSelectedPoints { delta_x, delta_y }) => {
|
||||
shape_editor.move_selected_points(
|
||||
shape_editor.move_selected_points_and_segments(
|
||||
tool_data.opposing_handle_lengths.take(),
|
||||
document,
|
||||
(delta_x, delta_y).into(),
|
||||
@@ -2058,10 +2202,6 @@ enum SelectionStatus {
|
||||
}
|
||||
|
||||
impl SelectionStatus {
|
||||
fn is_none(&self) -> bool {
|
||||
self == &SelectionStatus::None
|
||||
}
|
||||
|
||||
fn as_one(&self) -> Option<&SingleSelectedPoint> {
|
||||
match self {
|
||||
SelectionStatus::One(one) => Some(one),
|
||||
@@ -2191,9 +2331,7 @@ fn calculate_lock_angle(
|
||||
}
|
||||
|
||||
fn check_handle_over_adjacent_anchor(handle_id: ManipulatorPointId, vector_data: &VectorData) -> Option<PointId> {
|
||||
let Some((anchor, handle_position)) = handle_id.get_anchor(&vector_data).zip(handle_id.get_position(vector_data)) else {
|
||||
return None;
|
||||
};
|
||||
let (anchor, handle_position) = handle_id.get_anchor(vector_data).zip(handle_id.get_position(vector_data))?;
|
||||
|
||||
let check_if_close = |point_id: &PointId| {
|
||||
let Some(anchor_position) = vector_data.point_domain.position_from_id(*point_id) else {
|
||||
@@ -2202,7 +2340,7 @@ fn check_handle_over_adjacent_anchor(handle_id: ManipulatorPointId, vector_data:
|
||||
(anchor_position - handle_position).length() < 10.
|
||||
};
|
||||
|
||||
vector_data.connected_points(anchor).find(|point| check_if_close(point))
|
||||
vector_data.connected_points(anchor).find(check_if_close)
|
||||
}
|
||||
fn calculate_adjacent_anchor_tangent(
|
||||
currently_dragged_handle: ManipulatorPointId,
|
||||
@@ -2258,7 +2396,7 @@ fn calculate_adjacent_anchor_tangent(
|
||||
};
|
||||
|
||||
let angle = shared_segment_handle
|
||||
.get_position(&vector_data)
|
||||
.get_position(vector_data)
|
||||
.zip(adjacent_anchor_position)
|
||||
.map(|(handle, anchor)| -(handle - anchor).angle_to(DVec2::X));
|
||||
|
||||
@@ -2269,7 +2407,14 @@ fn calculate_adjacent_anchor_tangent(
|
||||
}
|
||||
}
|
||||
|
||||
fn update_dynamic_hints(state: PathToolFsmState, responses: &mut VecDeque<Message>, shape_editor: &mut ShapeState, document: &DocumentMessageHandler, tool_data: &PathToolData) {
|
||||
fn update_dynamic_hints(
|
||||
state: PathToolFsmState,
|
||||
responses: &mut VecDeque<Message>,
|
||||
shape_editor: &mut ShapeState,
|
||||
document: &DocumentMessageHandler,
|
||||
tool_data: &PathToolData,
|
||||
tool_options: &PathToolOptions,
|
||||
) {
|
||||
// Condinting based on currently selected segment if it has any one g1 continuous handle
|
||||
|
||||
let hint_data = match state {
|
||||
@@ -2304,12 +2449,27 @@ fn update_dynamic_hints(state: PathToolFsmState, responses: &mut VecDeque<Messag
|
||||
drag_selected_hints.push(HintInfo::multi_keys([[Key::Control], [Key::Shift]], "Slide").prepend_plus());
|
||||
}
|
||||
|
||||
let mut hint_data = vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Select Point"), HintInfo::keys([Key::Shift], "Extend").prepend_plus()]),
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Select Area"), HintInfo::keys([Key::Control], "Lasso").prepend_plus()]),
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Insert Point on Segment")]),
|
||||
HintGroup(vec![HintInfo::keys_and_mouse([Key::Alt], MouseMotion::Lmb, "Delete Segment")]),
|
||||
];
|
||||
let mut hint_data = match (tool_data.segment.is_some(), tool_options.path_editing_mode.segment_editing_mode) {
|
||||
(true, true) => {
|
||||
vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Select Segment"), HintInfo::keys([Key::Shift], "Extend").prepend_plus()]),
|
||||
HintGroup(vec![HintInfo::keys_and_mouse([Key::KeyA], MouseMotion::Lmb, "Mold Segment")]),
|
||||
]
|
||||
}
|
||||
(true, false) => {
|
||||
vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Insert Point on Segment")]),
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Mold Segment")]),
|
||||
HintGroup(vec![HintInfo::keys_and_mouse([Key::Alt], MouseMotion::Lmb, "Delete Segment")]),
|
||||
]
|
||||
}
|
||||
(false, _) => {
|
||||
vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Lmb, "Select Point"), HintInfo::keys([Key::Shift], "Extend").prepend_plus()]),
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::LmbDrag, "Select Area"), HintInfo::keys([Key::Control], "Lasso").prepend_plus()]),
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
if at_least_one_anchor_selected {
|
||||
// TODO: Dynamically show either "Smooth" or "Sharp" based on the current state
|
||||
|
||||
@@ -1402,6 +1402,7 @@ impl Fsm for SelectToolFsmState {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
tool_data.axis_align = false;
|
||||
tool_data.lasso_polygon.clear();
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
let selection = tool_data.nested_selection_behavior;
|
||||
@@ -1422,6 +1423,7 @@ impl Fsm for SelectToolFsmState {
|
||||
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
tool_data.lasso_polygon.clear();
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
let selection = tool_data.nested_selection_behavior;
|
||||
|
||||
@@ -143,7 +143,7 @@ impl LayoutHolder for ShapeTool {
|
||||
true,
|
||||
|_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColorType(color_type.clone())).into()),
|
||||
|color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(color.value.as_solid())).into(),
|
||||
|color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(),
|
||||
));
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
@@ -154,7 +154,7 @@ impl LayoutHolder for ShapeTool {
|
||||
true,
|
||||
|_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColorType(color_type.clone())).into()),
|
||||
|color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(color.value.as_solid())).into(),
|
||||
|color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(),
|
||||
));
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(create_weight_widget(self.options.line_weight));
|
||||
|
||||
@@ -18,7 +18,7 @@ use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_face};
|
||||
use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_font};
|
||||
use graphene_std::vector::style::Fill;
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -35,6 +35,7 @@ pub struct TextOptions {
|
||||
font_name: String,
|
||||
font_style: String,
|
||||
fill: ToolColorOptions,
|
||||
tilt: f64,
|
||||
}
|
||||
|
||||
impl Default for TextOptions {
|
||||
@@ -42,10 +43,11 @@ impl Default for TextOptions {
|
||||
Self {
|
||||
font_size: 24.,
|
||||
line_height_ratio: 1.2,
|
||||
character_spacing: 1.,
|
||||
character_spacing: 0.,
|
||||
font_name: graphene_std::consts::DEFAULT_FONT_FAMILY.into(),
|
||||
font_style: graphene_std::consts::DEFAULT_FONT_STYLE.into(),
|
||||
fill: ToolColorOptions::new_primary(),
|
||||
tilt: 0.,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -468,8 +470,8 @@ impl Fsm for TextToolFsmState {
|
||||
transform: document.metadata().transform_to_viewport(tool_data.layer).to_cols_array(),
|
||||
});
|
||||
if let Some(editing_text) = tool_data.editing_text.as_mut() {
|
||||
let buzz_face = font_cache.get(&editing_text.font).map(|data| load_face(data));
|
||||
let far = graphene_std::text::bounding_box(&tool_data.new_text, buzz_face.as_ref(), editing_text.typesetting, false);
|
||||
let font_data = font_cache.get(&editing_text.font).map(|data| load_font(data));
|
||||
let far = graphene_std::text::bounding_box(&tool_data.new_text, font_data, editing_text.typesetting, false);
|
||||
if far.x != 0. && far.y != 0. {
|
||||
let quad = Quad::from_box([DVec2::ZERO, far]);
|
||||
let transformed_quad = document.metadata().transform_to_viewport(tool_data.layer) * quad;
|
||||
@@ -517,8 +519,8 @@ impl Fsm for TextToolFsmState {
|
||||
// Draw red overlay if text is clipped
|
||||
let transformed_quad = layer_transform * bounds;
|
||||
if let Some((text, font, typesetting)) = graph_modification_utils::get_text(layer.unwrap(), &document.network_interface) {
|
||||
let buzz_face = font_cache.get(font).map(|data| load_face(data));
|
||||
if lines_clipping(text.as_str(), buzz_face, typesetting) {
|
||||
let font_data = font_cache.get(font).map(|data| load_font(data));
|
||||
if lines_clipping(text.as_str(), font_data, typesetting) {
|
||||
overlay_context.line(transformed_quad.0[2], transformed_quad.0[3], Some(COLOR_OVERLAY_RED), Some(3.));
|
||||
}
|
||||
}
|
||||
@@ -784,6 +786,7 @@ impl Fsm for TextToolFsmState {
|
||||
max_width: constraint_size.map(|size| size.x),
|
||||
character_spacing: tool_options.character_spacing,
|
||||
max_height: constraint_size.map(|size| size.y),
|
||||
tilt: tool_options.tilt,
|
||||
},
|
||||
font: Font::new(tool_options.font_name.clone(), tool_options.font_style.clone()),
|
||||
color: tool_options.fill.active_color(),
|
||||
|
||||
@@ -180,14 +180,28 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
*selected.pivot = selected.mean_average_of_pivots();
|
||||
self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot);
|
||||
self.grab_target = document.metadata().document_to_viewport.inverse().transform_point2(selected.mean_average_of_pivots());
|
||||
} else if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
|
||||
}
|
||||
// Here vector data from all layers is not considered which can be a problem in pivot calculation
|
||||
else if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
|
||||
*selected.original_transforms = OriginalTransforms::default();
|
||||
|
||||
let viewspace = document.metadata().transform_to_viewport(selected_layers[0]);
|
||||
let selected_points = shape_editor.selected_points().collect::<Vec<_>>();
|
||||
|
||||
let selected_segments = shape_editor.selected_segments().collect::<HashSet<_>>();
|
||||
|
||||
let mut affected_points = shape_editor.selected_points().copied().collect::<Vec<_>>();
|
||||
|
||||
for (segment_id, _, start, end) in vector_data.segment_bezier_iter() {
|
||||
if selected_segments.contains(&segment_id) {
|
||||
affected_points.push(ManipulatorPointId::Anchor(start));
|
||||
affected_points.push(ManipulatorPointId::Anchor(end));
|
||||
}
|
||||
}
|
||||
|
||||
let affected_point_refs = affected_points.iter().collect();
|
||||
|
||||
let get_location = |point: &&ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position));
|
||||
if let Some((new_pivot, grab_target)) = calculate_pivot(&selected_points, &vector_data, viewspace, |point: &ManipulatorPointId| get_location(&point)) {
|
||||
if let Some((new_pivot, grab_target)) = calculate_pivot(&affected_point_refs, &vector_data, viewspace, |point: &ManipulatorPointId| get_location(&point)) {
|
||||
*selected.pivot = new_pivot;
|
||||
|
||||
self.local_pivot = document_to_viewport.inverse().transform_point2(*selected.pivot);
|
||||
@@ -390,7 +404,8 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
||||
}
|
||||
TransformLayerMessage::BeginGRS { transform_type } => {
|
||||
let selected_points: Vec<&ManipulatorPointId> = shape_editor.selected_points().collect();
|
||||
if (using_path_tool && selected_points.is_empty())
|
||||
let selected_segments = shape_editor.selected_segments().collect::<Vec<_>>();
|
||||
if (using_path_tool && selected_points.is_empty() && selected_segments.is_empty())
|
||||
|| (!using_path_tool && !using_select_tool && !using_pen_tool && !using_shape_tool)
|
||||
|| selected_layers.is_empty()
|
||||
|| transform_type.equivalent_to(self.transform_operation)
|
||||
|
||||
@@ -227,9 +227,7 @@ impl NodeRuntime {
|
||||
}
|
||||
|
||||
async fn update_network(&mut self, mut graph: NodeNetwork) -> Result<ResolvedDocumentNodeTypesDelta, String> {
|
||||
if cfg!(not(test)) {
|
||||
preprocessor::expand_network(&mut graph, &self.substitutions);
|
||||
}
|
||||
preprocessor::expand_network(&mut graph, &self.substitutions);
|
||||
|
||||
let scoped_network = wrap_network_in_scope(graph, self.editor_api.clone());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user