Replace the legacy Bezier struct with kurbo::PathSeg throughout (#4455)

This commit is contained in:
Keavon Chambers
2026-09-15 20:03:17 +02:00
committed by Dennis Kobert
parent 30333d6ec4
commit 07829c6e0c
14 changed files with 127 additions and 324 deletions

View File

@@ -13,9 +13,10 @@ use graph_craft::document::NodeId;
use graphene_std::Color;
use graphene_std::raster::Image;
use graphene_std::subpath::BezierHandles;
use graphene_std::vector::misc::HandleId;
use graphene_std::vector::misc::{HandleId, point_to_dvec2, segment_to_handles};
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
use graphite_proc_macros::{ExtractField, message_handler_data};
use kurbo::ParamCurve;
use std::sync::Arc;
const CLIPBOARD_PREFIX: &str = "graphite: ";
@@ -414,7 +415,7 @@ impl MessageHandler<ClipboardMessage, ClipboardMessageContext<'_>> for Clipboard
// Create new segment ids and add the segments into the existing Vector path
let mut segments_map = HashMap::new();
for (segment_id, bezier, start, end) in new_vector.segment_bezier_iter() {
for (segment_id, segment, start, end) in new_vector.segment_iter() {
let (Some(&start_point), Some(&end_point)) = (points_map.get(&start), points_map.get(&end)) else {
warn!("Skipping pasted vector segment with an unknown endpoint");
continue;
@@ -423,10 +424,12 @@ impl MessageHandler<ClipboardMessage, ClipboardMessageContext<'_>> for Clipboard
let new_segment_id = SegmentId::generate();
segments_map.insert(segment_id, new_segment_id);
let handles = match bezier.handles {
let segment_start = point_to_dvec2(segment.start());
let segment_end = point_to_dvec2(segment.end());
let handles = match segment_to_handles(&segment) {
BezierHandles::Linear => [None, None],
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
BezierHandles::Quadratic { handle } => [Some(handle - segment_start), None],
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - segment_start), Some(handle_end - segment_end)],
};
let points = [start_point, end_point];

View File

@@ -6,9 +6,10 @@ pub use crate::messages::portfolio::document::utility_types::text_metrics::text_
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
use crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler;
use glam::{DAffine2, DVec2};
use graphene_std::subpath::{Bezier, BezierHandles};
use graphene_std::vector::misc::ManipulatorPointId;
use graphene_std::subpath::BezierHandles;
use graphene_std::vector::misc::{ManipulatorPointId, point_to_dvec2, segment_to_handles};
use graphene_std::vector::{PointId, SegmentId, Vector};
use kurbo::{Affine, ParamCurve, PathSeg};
use std::collections::HashMap;
#[cfg(target_family = "wasm")]
use wasm_bindgen::JsCast;
@@ -59,7 +60,7 @@ pub fn selected_segments_for_layer(vector: &Vector, state: &SelectedLayerState)
.collect::<Vec<_>>();
// Adding segments which are are connected to selected anchors
for (segment_id, _bezier, start, end) in vector.segment_bezier_iter() {
for (segment_id, _, start, end) in vector.segment_iter() {
if selected_anchors.contains(&start) || selected_anchors.contains(&end) {
selected_segments.push(segment_id);
}
@@ -67,23 +68,25 @@ pub fn selected_segments_for_layer(vector: &Vector, state: &SelectedLayerState)
selected_segments
}
fn overlay_bezier_handles(bezier: Bezier, segment_id: SegmentId, transform: DAffine2, is_selected: impl Fn(ManipulatorPointId) -> bool, overlay_context: &mut OverlayContext) {
let bezier = bezier.apply_transformation(|point| transform.transform_point2(point));
fn overlay_bezier_handles(segment: PathSeg, segment_id: SegmentId, transform: DAffine2, is_selected: impl Fn(ManipulatorPointId) -> bool, overlay_context: &mut OverlayContext) {
let segment = Affine::new(transform.to_cols_array()) * segment;
let segment_start = point_to_dvec2(segment.start());
let segment_end = point_to_dvec2(segment.end());
let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
match bezier.handles {
BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => {
overlay_context.line(handle, bezier.start, None, None);
overlay_context.line(handle, bezier.end, None, None);
match segment_to_handles(&segment) {
BezierHandles::Quadratic { handle } if not_under_anchor(handle, segment_start) && not_under_anchor(handle, segment_end) => {
overlay_context.line(handle, segment_start, None, None);
overlay_context.line(handle, segment_end, None, None);
overlay_context.manipulator_handle(handle, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None);
}
BezierHandles::Cubic { handle_start, handle_end } => {
if not_under_anchor(handle_start, bezier.start) {
overlay_context.line(handle_start, bezier.start, None, None);
if not_under_anchor(handle_start, segment_start) {
overlay_context.line(handle_start, segment_start, None, None);
overlay_context.manipulator_handle(handle_start, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None);
}
if not_under_anchor(handle_end, bezier.end) {
overlay_context.line(handle_end, bezier.end, None, None);
if not_under_anchor(handle_end, segment_end) {
overlay_context.line(handle_end, segment_end, None, None);
overlay_context.manipulator_handle(handle_end, is_selected(ManipulatorPointId::EndHandle(segment_id)), None);
}
}
@@ -92,7 +95,7 @@ fn overlay_bezier_handles(bezier: Bezier, segment_id: SegmentId, transform: DAff
}
fn overlay_bezier_handle_specific_point(
bezier: Bezier,
segment: PathSeg,
segment_id: SegmentId,
(start, end): (PointId, PointId),
point_to_render: PointId,
@@ -100,22 +103,24 @@ fn overlay_bezier_handle_specific_point(
is_selected: impl Fn(ManipulatorPointId) -> bool,
overlay_context: &mut OverlayContext,
) {
let bezier = bezier.apply_transformation(|point| transform.transform_point2(point));
let segment = Affine::new(transform.to_cols_array()) * segment;
let segment_start = point_to_dvec2(segment.start());
let segment_end = point_to_dvec2(segment.end());
let not_under_anchor = |position: DVec2, anchor: DVec2| position.distance_squared(anchor) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
match bezier.handles {
BezierHandles::Quadratic { handle } if not_under_anchor(handle, bezier.start) && not_under_anchor(handle, bezier.end) => {
let end = if start == point_to_render { bezier.start } else { bezier.end };
overlay_context.line(handle, end, None, None);
match segment_to_handles(&segment) {
BezierHandles::Quadratic { handle } if not_under_anchor(handle, segment_start) && not_under_anchor(handle, segment_end) => {
let anchor = if start == point_to_render { segment_start } else { segment_end };
overlay_context.line(handle, anchor, None, None);
overlay_context.manipulator_handle(handle, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None);
}
BezierHandles::Cubic { handle_start, handle_end } => {
if not_under_anchor(handle_start, bezier.start) && (point_to_render == start) {
overlay_context.line(handle_start, bezier.start, None, None);
if not_under_anchor(handle_start, segment_start) && (point_to_render == start) {
overlay_context.line(handle_start, segment_start, None, None);
overlay_context.manipulator_handle(handle_start, is_selected(ManipulatorPointId::PrimaryHandle(segment_id)), None);
}
if not_under_anchor(handle_end, bezier.end) && (point_to_render == end) {
overlay_context.line(handle_end, bezier.end, None, None);
if not_under_anchor(handle_end, segment_end) && (point_to_render == end) {
overlay_context.line(handle_end, segment_end, None, None);
overlay_context.manipulator_handle(handle_end, is_selected(ManipulatorPointId::EndHandle(segment_id)), None);
}
}
@@ -150,23 +155,23 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
match draw_handles {
DrawHandles::All => {
vector.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| {
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
vector.segment_iter().for_each(|(segment_id, segment, _start, _end)| {
overlay_bezier_handles(segment, segment_id, transform, is_selected, overlay_context);
});
}
DrawHandles::SelectedAnchors(ref selected_segments) => {
let Some(focused_segments) = selected_segments.get(&layer) else { continue };
vector
.segment_bezier_iter()
.segment_iter()
.filter(|(segment_id, ..)| focused_segments.contains(segment_id))
.for_each(|(segment_id, bezier, _start, _end)| {
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
.for_each(|(segment_id, segment, _start, _end)| {
overlay_bezier_handles(segment, segment_id, transform, is_selected, overlay_context);
});
for (segment_id, bezier, start, end) in vector.segment_bezier_iter() {
for (segment_id, segment, start, end) in vector.segment_iter() {
if let Some((corresponding_anchor, _)) = opposite_handles_data.iter().find(|(_, adj_segment_id)| adj_segment_id == &segment_id) {
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), *corresponding_anchor, transform, is_selected, overlay_context);
overlay_bezier_handle_specific_point(segment, segment_id, (start, end), *corresponding_anchor, transform, is_selected, overlay_context);
}
}
}
@@ -174,14 +179,14 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
let Some(segment_endpoints) = segment_endpoints_by_layer.get(&layer) else { continue };
vector
.segment_bezier_iter()
.segment_iter()
.filter(|(segment_id, ..)| segment_endpoints.contains_key(segment_id))
.for_each(|(segment_id, bezier, start, end)| {
.for_each(|(segment_id, segment, start, end)| {
if segment_endpoints.get(&segment_id).unwrap().len() == 1 {
let point_to_render = segment_endpoints.get(&segment_id).unwrap()[0];
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), point_to_render, transform, is_selected, overlay_context);
overlay_bezier_handle_specific_point(segment, segment_id, (start, end), point_to_render, transform, is_selected, overlay_context);
} else {
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
overlay_bezier_handles(segment, segment_id, transform, is_selected, overlay_context);
}
});
}

View File

@@ -93,7 +93,7 @@ impl OriginalTransforms {
let mut selected_points = selected_points.clone();
for (segment_id, _, start, end) in vector.segment_bezier_iter() {
for (segment_id, _, start, end) in vector.segment_iter() {
if selected_segments.contains(&segment_id) {
selected_points.insert(ManipulatorPointId::Anchor(start));
selected_points.insert(ManipulatorPointId::Anchor(end));

View File

@@ -168,7 +168,7 @@ pub fn merge_points(document: &DocumentMessageHandler, layer: LayerNodeIdentifie
let transform = document.metadata().transform_to_document(layer);
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };
let segment = vector.segment_bezier_iter().find(|(_, _, start, end)| *end == second_endpont || *start == second_endpont);
let segment = vector.segment_iter().find(|(_, _, start, end)| *end == second_endpont || *start == second_endpont);
let Some((segment, _, mut segment_start_point, mut segment_end_point)) = segment else {
log::error!("Could not get the segment for second_endpoint.");
return;

View File

@@ -15,7 +15,7 @@ use glam::{DAffine2, DVec2};
use graphene_std::subpath::{BezierHandles, Subpath};
use graphene_std::subpath::{PathSegPoints, pathseg_points};
use graphene_std::vector::algorithms::bezpath_algorithms::pathseg_compute_lookup_table;
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2};
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2, segment_to_handles};
use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModificationType};
use kurbo::{Affine, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveNearest, PathSeg, Rect, Shape};
use std::f64::consts::TAU;
@@ -251,7 +251,7 @@ impl ClosestSegment {
// Transform to viewport space
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
// Split the Bezier at the parameter `t`
// Split the segment at the parameter `t`
let first = self.bezier.subsegment(0_f64..self.t);
let second = self.bezier.subsegment(self.t..1.);
@@ -788,7 +788,7 @@ impl ShapeState {
}
if segments {
for (id, _, start, end) in vector.segment_bezier_iter() {
for (id, _, start, end) in vector.segment_iter() {
if connected_points.contains(&start) || connected_points.contains(&end) {
state.select_segment(id);
}
@@ -950,14 +950,14 @@ impl ShapeState {
// Move the other handle for a quadratic bezier
for segment in vector.end_connected(point) {
let Some((start, _end, bezier)) = vector.segment_points_from_id(segment) else { continue };
let Some((start, _end, path_segment)) = vector.segment_points_from_id(segment) else { continue };
if let BezierHandles::Quadratic { handle } = bezier.handles {
if let BezierHandles::Quadratic { handle } = segment_to_handles(&path_segment) {
if selected.is_some_and(|selected| selected.is_point_selected(ManipulatorPointId::Anchor(start))) {
continue;
}
let relative_position = handle - bezier.start + delta;
let relative_position = handle - point_to_dvec2(path_segment.start()) + delta;
let modification_type = VectorModificationType::SetPrimaryHandle { segment, relative_position };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
@@ -1284,7 +1284,7 @@ impl ShapeState {
// Make a new set of anchor points which needs to be moved
let mut affected_points = state.selected_points.clone();
for (segment_id, _, start, end) in vector.segment_bezier_iter() {
for (segment_id, _, start, end) in vector.segment_iter() {
if state.is_segment_selected(segment_id) {
affected_points.insert(ManipulatorPointId::Anchor(start));
affected_points.insert(ManipulatorPointId::Anchor(end));
@@ -1570,7 +1570,7 @@ impl ShapeState {
for (&layer, state) in &self.selected_shape_state {
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
for (segment, _, start, end) in vector.segment_bezier_iter() {
for (segment, _, start, end) in vector.segment_iter() {
if state.selected_segments.contains(&segment) {
if start_transaction && !transaction_started {
responses.add(DocumentMessage::AddTransaction);
@@ -1785,20 +1785,23 @@ impl ShapeState {
let viewspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
// Handles
for (segment_id, bezier, _, _) in vector.segment_bezier_iter() {
let bezier = bezier.apply_transformation(|point| viewspace.transform_point2(point));
for (segment_id, segment, _, _) in vector.segment_iter() {
let segment = Affine::new(viewspace.to_cols_array()) * segment;
let handles = segment_to_handles(&segment);
let segment_start = point_to_dvec2(segment.start());
let segment_end = point_to_dvec2(segment.end());
let valid = |handle: DVec2, control: DVec2| handle.distance_squared(control) > crate::consts::HIDE_HANDLE_DISTANCE.powi(2);
if let Some(primary_handle) = bezier.handle_start()
&& valid(primary_handle, bezier.start)
&& (bezier.handle_end().is_some() || valid(primary_handle, bezier.end))
if let Some(primary_handle) = handles.start()
&& valid(primary_handle, segment_start)
&& (handles.end().is_some() || valid(primary_handle, segment_end))
&& primary_handle.distance_squared(pos) <= closest_distance_squared
{
closest_distance_squared = primary_handle.distance_squared(pos);
manipulator_point = Some(ManipulatorPointId::PrimaryHandle(segment_id));
}
if let Some(end_handle) = bezier.handle_end()
&& valid(end_handle, bezier.end)
if let Some(end_handle) = handles.end()
&& valid(end_handle, segment_end)
&& end_handle.distance_squared(pos) <= closest_distance_squared
{
closest_distance_squared = end_handle.distance_squared(pos);
@@ -2071,9 +2074,9 @@ impl ShapeState {
self.convert_manipulator_handles_to_colinear(&vector, point_id, responses, layer);
} else {
for handle in vector.all_connected(point_id) {
let Some(bezier) = vector.segment_from_id(handle.segment) else { continue };
let Some(path_segment) = vector.segment_from_id(handle.segment) else { continue };
match bezier.handles {
match segment_to_handles(&path_segment) {
BezierHandles::Linear => {}
BezierHandles::Quadratic { .. } => {
let segment = handle.segment;
@@ -2173,7 +2176,7 @@ impl ShapeState {
let Some(vector) = network_interface.compute_modified_vector(layer) else { continue };
if !select_points && select_segments {
vector
.segment_bezier_iter()
.segment_iter()
.filter(|(segment, _, _, _)| segments.contains(segment))
.for_each(|(_, _, start, end)| match selection_change {
SelectionChange::Shrink => {

View File

@@ -30,7 +30,7 @@ use graphene_std::transform::ReferencePoint;
use graphene_std::uuid::NodeId;
use graphene_std::vector::algorithms::util::pathseg_tangent;
use graphene_std::vector::click_target::ClickTargetType;
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2};
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2, segment_to_handles};
use graphene_std::vector::{HandleExt, NoHashBuilder, PointId, SegmentId, Vector, VectorModificationType};
use kurbo::{DEFAULT_ACCURACY, ParamCurve, ParamCurveNearest, PathSeg, Rect};
use std::vec;
@@ -1647,7 +1647,7 @@ impl Fsm for PathToolFsmState {
let selected_state = shape_editor.selected_shape_state.entry(layer).or_default();
for (segment, _, start, end) in vector.segment_bezier_iter() {
for (segment, _, start, end) in vector.segment_iter() {
if selected_state.is_segment_selected(segment) {
selected_state.select_point(ManipulatorPointId::Anchor(start));
selected_state.select_point(ManipulatorPointId::Anchor(end));
@@ -1700,7 +1700,7 @@ impl Fsm for PathToolFsmState {
let selected_state = shape_editor.selected_shape_state.entry(layer).or_default();
for (segment, _, start, end) in vector.segment_bezier_iter() {
for (segment, _, start, end) in vector.segment_iter() {
let first_selected = selected_state.is_point_selected(ManipulatorPointId::Anchor(start));
let second_selected = selected_state.is_point_selected(ManipulatorPointId::Anchor(end));
if first_selected && second_selected {
@@ -1764,7 +1764,7 @@ impl Fsm for PathToolFsmState {
// The points which are part of only one segment will be rendered
let mut selected_segments_by_point: HashMap<PointId, Vec<SegmentId>> = HashMap::new();
for (segment_id, _bezier, start, end) in vector.segment_bezier_iter() {
for (segment_id, _, start, end) in vector.segment_iter() {
if focused_segments.contains(&segment_id) {
selected_segments_by_point.entry(start).or_default().push(segment_id);
selected_segments_by_point.entry(end).or_default().push(segment_id);
@@ -2764,7 +2764,7 @@ impl Fsm for PathToolFsmState {
let mut selected_points_by_segment = HashSet::new();
old_vector
.segment_bezier_iter()
.segment_iter()
.filter(|(segment, _, _, _)| layer_selection_state.is_segment_selected(*segment))
.for_each(|(_, _, start, end)| {
selected_points_by_segment.insert(start);
@@ -2781,7 +2781,7 @@ impl Fsm for PathToolFsmState {
let find_index = |id: PointId| new_vector.point_domain.iter().enumerate().find(|(_, (point_id, _))| *point_id == id).map(|(index, _)| index);
// Add segments which have selected ends
for ((segment_id, bezier, start, end), stroke) in old_vector.segment_bezier_iter().zip(old_vector.segment_domain.stroke().iter()) {
for ((segment_id, segment, start, end), stroke) in old_vector.segment_iter().zip(old_vector.segment_domain.stroke().iter()) {
let both_ends_selected = layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(start)) && layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(end));
let segment_selected = layer_selection_state.is_segment_selected(segment_id);
@@ -2791,7 +2791,7 @@ impl Fsm for PathToolFsmState {
error!("Point does not exist in point domain");
return PathToolFsmState::Ready;
};
new_vector.segment_domain.push(segment_id, start_index, end_index, bezier.handles, *stroke);
new_vector.segment_domain.push(segment_id, start_index, end_index, segment_to_handles(&segment), *stroke);
}
}
@@ -2945,7 +2945,7 @@ impl Fsm for PathToolFsmState {
// Add all the selected points
let mut selected_points_by_segment = HashSet::new();
old_vector
.segment_bezier_iter()
.segment_iter()
.filter(|(segment, _, _, _)| layer_selection_state.is_segment_selected(*segment))
.for_each(|(_, _, start, end)| {
selected_points_by_segment.insert(start);
@@ -3398,7 +3398,7 @@ fn calculate_adjacent_anchor_tangent(currently_dragged_handle: ManipulatorPointI
0 => {
// Find non-shared segments
let non_shared_segment: Vec<_> = vector
.segment_bezier_iter()
.segment_iter()
.filter_map(|(segment_id, _, start, end)| {
let touches_adjacent = start == adjacent_anchor || end == adjacent_anchor;
let shares_with_dragged = start == dragged_handle_anchor || end == dragged_handle_anchor;

View File

@@ -162,7 +162,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
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.segment_bezier_iter() {
for (segment_id, _, start, end) in vector.segment_iter() {
if selected_segments.contains(&segment_id) {
affected_points.push(ManipulatorPointId::Anchor(start));
affected_points.push(ManipulatorPointId::Anchor(end));