mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-24 21:18:11 +08:00
Add segment editing mode to the Path tool (#2712)
* Segment select mode upto dragging * Lasso select for segment editing * Formatting * Compatibility with point selection mode * Add delete segment support and drawing from inside of shape * Add GRS support for selected segments * Cleanup and add dynamic hints * Fix double click behaviour and overlays * Format code * Fix merge * Fix Lint * Fix formatting * Fix lasso bug * Code review --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
a4fbea9193
commit
391ed34a30
@@ -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::is_visible_point;
|
||||
use crate::messages::tool::common_functionality::utility_functions::{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;
|
||||
}
|
||||
|
||||
@@ -1012,9 +1061,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 +1089,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 +1114,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 +1133,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 +1184,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],
|
||||
@@ -1208,11 +1267,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);
|
||||
}
|
||||
@@ -1257,6 +1320,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();
|
||||
@@ -1304,6 +1369,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 };
|
||||
@@ -1748,6 +1827,7 @@ impl ShapeState {
|
||||
false
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn select_all_in_shape(
|
||||
&mut self,
|
||||
network_interface: &NodeNetworkInterface,
|
||||
@@ -1755,13 +1835,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);
|
||||
@@ -1787,7 +1871,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);
|
||||
@@ -1820,6 +1943,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::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(
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user