mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-23 08:58:12 +08:00
Migrate vector data and tools to use nodes (#1065)
* Add rendering to vector nodes * Add line, shape, rectange and freehand tool * Fix transforms, strokes and fills * Migrate spline tool * Remove blank lines * Fix test * Fix fill in properties * Select layers when filling * Properties panel transform around pivot * Fix select tool outlines * Select tool modifies node graph pivot * Add the pivot assist to the properties * Improve setting non existant fill UX * Cleanup hash function * Path and pen tools * Bug fixes * Disable boolean ops * Fix default handle smoothing on ellipses * Fix test and warnings --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
committed by
Keavon Chambers
co-authored by
Keavon Chambers
parent
639a24d8ad
commit
959e790cdf
@@ -0,0 +1,25 @@
|
||||
use crate::messages::portfolio::document::node_graph;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use bezier_rs::Subpath;
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use glam::DAffine2;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// Create a new vector layer from a vector of [`bezier_rs::Subpath`].
|
||||
pub fn new_vector_layer(subpaths: Vec<Subpath<ManipulatorGroupId>>, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||
responses.push_back(DocumentMessage::DeselectAllLayers.into());
|
||||
let network = node_graph::new_vector_network(subpaths);
|
||||
|
||||
responses.push_back(
|
||||
Operation::AddNodeGraphFrame {
|
||||
path: layer_path.clone(),
|
||||
insert_index: -1,
|
||||
transform: DAffine2::ZERO.to_cols_array(),
|
||||
network,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
responses.add(DocumentMessage::NodeGraphFrameGenerate { layer_path });
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod graph_modification_utils;
|
||||
pub mod overlay_renderer;
|
||||
pub mod path_outline;
|
||||
pub mod pivot;
|
||||
|
||||
@@ -3,97 +3,106 @@ use crate::consts::VIEWPORT_GRID_ROUNDING_BIAS;
|
||||
use crate::consts::{COLOR_ACCENT, HIDE_HANDLE_DISTANCE, MANIPULATOR_GROUP_MARKER_SIZE, PATH_OUTLINE_WEIGHT};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use bezier_rs::ManipulatorGroup;
|
||||
use document_legacy::document::Document;
|
||||
use document_legacy::layers::style::{self, Fill, Stroke};
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graphene_core::raster::color::Color;
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
use graphene_std::vector::manipulator_group::ManipulatorGroup;
|
||||
use graphene_std::vector::manipulator_point::ManipulatorPoint;
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||
|
||||
use super::shape_editor::SelectedShapeState;
|
||||
|
||||
/// [ManipulatorGroupOverlay]s is the collection of overlays that make up an [ManipulatorGroup] visible in the editor.
|
||||
type ManipulatorGroupOverlays = [Option<Vec<LayerId>>; 5];
|
||||
type ManipulatorId = u64;
|
||||
#[derive(Clone, Debug, Default)]
|
||||
struct ManipulatorGroupOverlays {
|
||||
pub anchor: Option<Vec<LayerId>>,
|
||||
pub in_handle: Option<Vec<LayerId>>,
|
||||
pub in_line: Option<Vec<LayerId>>,
|
||||
pub out_handle: Option<Vec<LayerId>>,
|
||||
pub out_line: Option<Vec<LayerId>>,
|
||||
}
|
||||
impl ManipulatorGroupOverlays {
|
||||
pub fn iter<'a>(&'a self) -> impl Iterator<Item = &'a Option<Vec<LayerId>>> {
|
||||
[&self.anchor, &self.in_handle, &self.in_line, &self.out_handle, &self.out_line].into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
type GraphiteManipulatorGroup = ManipulatorGroup<ManipulatorGroupId>;
|
||||
|
||||
const POINT_STROKE_WEIGHT: f64 = 2.;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct OverlayRenderer {
|
||||
shape_overlay_cache: HashMap<LayerId, Vec<LayerId>>,
|
||||
manipulator_group_overlay_cache: HashMap<(LayerId, ManipulatorId), ManipulatorGroupOverlays>,
|
||||
manipulator_group_overlay_cache: HashMap<(LayerId, ManipulatorGroupId), ManipulatorGroupOverlays>,
|
||||
}
|
||||
|
||||
impl OverlayRenderer {
|
||||
pub fn new() -> Self {
|
||||
OverlayRenderer {
|
||||
manipulator_group_overlay_cache: HashMap::new(),
|
||||
shape_overlay_cache: HashMap::new(),
|
||||
}
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn render_subpath_overlays(&mut self, document: &Document, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||
pub fn render_subpath_overlays(&mut self, selected_shape_state: &SelectedShapeState, document: &Document, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>) {
|
||||
let transform = document.generate_transform_relative_to_viewport(&layer_path).ok().unwrap();
|
||||
if let Ok(layer) = document.layer(&layer_path) {
|
||||
let layer_id = layer_path.last().unwrap();
|
||||
self.layer_overlay_visibility(document, layer_path.clone(), true, responses);
|
||||
|
||||
if let Some(shape) = layer.as_subpath() {
|
||||
if let Some(vector_data) = layer.as_vector_data() {
|
||||
let outline_cache = self.shape_overlay_cache.get(layer_id);
|
||||
trace!("Overlay: Outline cache {:?}", &outline_cache);
|
||||
|
||||
// Create an outline if we do not have a cached one
|
||||
if outline_cache.is_none() {
|
||||
let outline_path = self.create_shape_outline_overlay(shape.clone(), responses);
|
||||
let outline_path = self.create_shape_outline_overlay(graphene_core::vector::Subpath::from_bezier_crate(&vector_data.subpaths), responses);
|
||||
self.shape_overlay_cache.insert(*layer_id, outline_path.clone());
|
||||
Self::place_outline_overlays(outline_path.clone(), &transform, responses);
|
||||
trace!("Overlay: Creating new outline {:?}", &outline_path);
|
||||
} else if let Some(outline_path) = outline_cache {
|
||||
trace!("Overlay: Updating overlays for {:?} owning layer: {:?}", outline_path, layer_id);
|
||||
Self::modify_outline_overlays(outline_path.clone(), shape.clone(), responses);
|
||||
Self::modify_outline_overlays(outline_path.clone(), graphene_core::vector::Subpath::from_bezier_crate(&vector_data.subpaths), responses);
|
||||
Self::place_outline_overlays(outline_path.clone(), &transform, responses);
|
||||
}
|
||||
|
||||
// Create, place, and style the manipulator overlays
|
||||
for (manipulator_group_id, manipulator_group) in shape.manipulator_groups().enumerate() {
|
||||
let manipulator_group_cache = self.manipulator_group_overlay_cache.entry((*layer_id, *manipulator_group_id)).or_default();
|
||||
for manipulator_group in vector_data.manipulator_groups() {
|
||||
let manipulator_group_cache = self.manipulator_group_overlay_cache.entry((*layer_id, manipulator_group.id)).or_default();
|
||||
|
||||
// Only view in and out handles if they are not on top of the anchor
|
||||
let [in_handle, out_handle] = {
|
||||
let Some(anchor) = manipulator_group.points[ManipulatorType::Anchor].as_ref() else {
|
||||
continue;
|
||||
};
|
||||
let anchor = manipulator_group.anchor;
|
||||
|
||||
let anchor_position = transform.transform_point2(anchor.position);
|
||||
let filter_position = |handle: &&ManipulatorPoint| transform.transform_point2(handle.position).distance_squared(anchor_position) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
|
||||
let filter_manipulator_point = |manipulator_type| manipulator_group.points[manipulator_type as usize].as_ref().filter(filter_position);
|
||||
[filter_manipulator_point(ManipulatorType::InHandle), filter_manipulator_point(ManipulatorType::OutHandle)]
|
||||
let anchor_position = transform.transform_point2(anchor);
|
||||
let not_under_anchor = |&position: &DVec2| transform.transform_point2(position).distance_squared(anchor_position) >= HIDE_HANDLE_DISTANCE * HIDE_HANDLE_DISTANCE;
|
||||
let filter_handle = |manipulator: Option<DVec2>| manipulator.filter(not_under_anchor);
|
||||
[filter_handle(manipulator_group.in_handle), filter_handle(manipulator_group.out_handle)]
|
||||
};
|
||||
|
||||
// Create anchor
|
||||
manipulator_group_cache[0] = manipulator_group_cache[0].take().or_else(|| Some(Self::create_anchor_overlay(responses)));
|
||||
manipulator_group_cache.anchor = manipulator_group_cache.anchor.take().or_else(|| Some(Self::create_anchor_overlay(responses)));
|
||||
// Create or delete in handle
|
||||
if in_handle.is_none() {
|
||||
Self::remove_overlay(manipulator_group_cache[1].take(), responses);
|
||||
Self::remove_overlay(manipulator_group_cache[3].take(), responses);
|
||||
Self::remove_overlay(manipulator_group_cache.in_handle.take(), responses);
|
||||
Self::remove_overlay(manipulator_group_cache.in_line.take(), responses);
|
||||
} else {
|
||||
manipulator_group_cache[1] = manipulator_group_cache[1].take().or_else(|| Self::create_handle_overlay_if_exists(in_handle, responses));
|
||||
manipulator_group_cache[3] = manipulator_group_cache[3].take().or_else(|| Self::create_handle_line_overlay_if_exists(in_handle, responses));
|
||||
manipulator_group_cache.in_handle = manipulator_group_cache.in_handle.take().or_else(|| Self::create_handle_overlay_if_exists(in_handle, responses));
|
||||
manipulator_group_cache.in_line = manipulator_group_cache.in_line.take().or_else(|| Self::create_handle_line_overlay_if_exists(in_handle, responses));
|
||||
}
|
||||
// Create or delete out handle
|
||||
if out_handle.is_none() {
|
||||
Self::remove_overlay(manipulator_group_cache[2].take(), responses);
|
||||
Self::remove_overlay(manipulator_group_cache[4].take(), responses);
|
||||
Self::remove_overlay(manipulator_group_cache.out_handle.take(), responses);
|
||||
Self::remove_overlay(manipulator_group_cache.out_line.take(), responses);
|
||||
} else {
|
||||
manipulator_group_cache[2] = manipulator_group_cache[2].take().or_else(|| Self::create_handle_overlay_if_exists(out_handle, responses));
|
||||
manipulator_group_cache[4] = manipulator_group_cache[4].take().or_else(|| Self::create_handle_line_overlay_if_exists(out_handle, responses));
|
||||
manipulator_group_cache.out_handle = manipulator_group_cache.out_handle.take().or_else(|| Self::create_handle_overlay_if_exists(out_handle, responses));
|
||||
manipulator_group_cache.out_line = manipulator_group_cache.out_line.take().or_else(|| Self::create_handle_line_overlay_if_exists(out_handle, responses));
|
||||
}
|
||||
|
||||
// Update placement and style
|
||||
Self::place_manipulator_group_overlays(manipulator_group, manipulator_group_cache, &transform, responses);
|
||||
Self::style_overlays(manipulator_group, manipulator_group_cache, responses);
|
||||
Self::style_overlays(selected_shape_state, &layer_path, manipulator_group, manipulator_group_cache, responses);
|
||||
}
|
||||
// TODO Handle removing shapes from cache so we don't memory leak
|
||||
// Eventually will get replaced with am immediate mode renderer for overlays
|
||||
@@ -112,11 +121,12 @@ impl OverlayRenderer {
|
||||
|
||||
// Remove the ManipulatorGroup overlays
|
||||
if let Ok(layer) = document.layer(&layer_path) {
|
||||
if let Some(shape) = layer.as_subpath() {
|
||||
for (id, _) in shape.manipulator_groups().enumerate() {
|
||||
if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, *id)) {
|
||||
if let Some(vector_data) = layer.as_vector_data() {
|
||||
for manipulator_group in vector_data.manipulator_groups() {
|
||||
let id = manipulator_group.id;
|
||||
if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, id)) {
|
||||
Self::remove_manipulator_group_overlays(manipulator_group_overlays, responses);
|
||||
self.manipulator_group_overlay_cache.remove(&(*layer_id, *id));
|
||||
self.manipulator_group_overlay_cache.remove(&(*layer_id, id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,9 +143,10 @@ impl OverlayRenderer {
|
||||
|
||||
// Hide the manipulator group overlays
|
||||
if let Ok(layer) = document.layer(&layer_path) {
|
||||
if let Some(shape) = layer.as_subpath() {
|
||||
for (id, _) in shape.manipulator_groups().enumerate() {
|
||||
if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, *id)) {
|
||||
if let Some(vector_data) = layer.as_vector_data() {
|
||||
for manipulator_group in vector_data.manipulator_groups() {
|
||||
let id = manipulator_group.id;
|
||||
if let Some(manipulator_group_overlays) = self.manipulator_group_overlay_cache.get(&(*layer_id, id)) {
|
||||
Self::set_manipulator_group_overlay_visibility(manipulator_group_overlays, visibility, responses);
|
||||
}
|
||||
}
|
||||
@@ -144,7 +155,7 @@ impl OverlayRenderer {
|
||||
}
|
||||
|
||||
/// Create the kurbo shape that matches the selected viewport shape.
|
||||
fn create_shape_outline_overlay(&self, subpath: Subpath, responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
||||
fn create_shape_outline_overlay(&self, subpath: graphene_core::vector::Subpath, responses: &mut VecDeque<Message>) -> Vec<LayerId> {
|
||||
let layer_path = vec![generate_uuid()];
|
||||
let operation = Operation::AddShape {
|
||||
path: layer_path.clone(),
|
||||
@@ -185,7 +196,7 @@ impl OverlayRenderer {
|
||||
}
|
||||
|
||||
/// Create a single handle overlay and return its layer id if it exists.
|
||||
fn create_handle_overlay_if_exists(handle: Option<&ManipulatorPoint>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
|
||||
fn create_handle_overlay_if_exists(handle: Option<DVec2>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
|
||||
handle.map(|_| Self::create_handle_overlay(responses))
|
||||
}
|
||||
|
||||
@@ -210,7 +221,7 @@ impl OverlayRenderer {
|
||||
}
|
||||
|
||||
/// Create the shape outline overlay and return its layer ID.
|
||||
fn create_handle_line_overlay_if_exists(handle: Option<&ManipulatorPoint>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
|
||||
fn create_handle_line_overlay_if_exists(handle: Option<DVec2>, responses: &mut VecDeque<Message>) -> Option<Vec<LayerId>> {
|
||||
handle.as_ref().map(|_| Self::create_handle_line_overlay(responses))
|
||||
}
|
||||
|
||||
@@ -219,53 +230,48 @@ impl OverlayRenderer {
|
||||
responses.push_back(transform_message);
|
||||
}
|
||||
|
||||
fn modify_outline_overlays(outline_path: Vec<LayerId>, subpath: Subpath, responses: &mut VecDeque<Message>) {
|
||||
fn modify_outline_overlays(outline_path: Vec<LayerId>, subpath: graphene_core::vector::Subpath, responses: &mut VecDeque<Message>) {
|
||||
let outline_modify_message = Self::overlay_modify_message(outline_path, subpath);
|
||||
responses.push_back(outline_modify_message);
|
||||
}
|
||||
|
||||
/// Updates the position of the overlays based on the [Subpath] points.
|
||||
fn place_manipulator_group_overlays(manipulator_group: &ManipulatorGroup, overlays: &mut ManipulatorGroupOverlays, parent_transform: &DAffine2, responses: &mut VecDeque<Message>) {
|
||||
if let Some(manipulator_point) = &manipulator_group.points[ManipulatorType::Anchor] {
|
||||
// Helper function to keep things DRY (don't-repeat-yourself)
|
||||
let mut place_handle_and_line = |handle: &ManipulatorPoint, line_overlay: &mut Vec<LayerId>, marker_source: &mut Option<Vec<LayerId>>| {
|
||||
let line_vector = parent_transform.transform_point2(manipulator_point.position) - parent_transform.transform_point2(handle.position);
|
||||
let scale = DVec2::splat(line_vector.length());
|
||||
let angle = -line_vector.angle_between(DVec2::X);
|
||||
let translation = (parent_transform.transform_point2(handle.position) + VIEWPORT_GRID_ROUNDING_BIAS).round() + DVec2::splat(0.5);
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
responses.push_back(Self::overlay_transform_message(line_overlay.clone(), transform));
|
||||
fn place_manipulator_group_overlays(manipulator_group: &GraphiteManipulatorGroup, overlays: &mut ManipulatorGroupOverlays, parent_transform: &DAffine2, responses: &mut VecDeque<Message>) {
|
||||
let anchor = manipulator_group.anchor;
|
||||
let mut place_handle_and_line = |handle_position: DVec2, line_overlay: &[LayerId], marker_source: &mut Option<Vec<LayerId>>| {
|
||||
let line_vector = parent_transform.transform_point2(anchor) - parent_transform.transform_point2(handle_position);
|
||||
let scale = DVec2::splat(line_vector.length());
|
||||
let angle = -line_vector.angle_between(DVec2::X);
|
||||
let translation = (parent_transform.transform_point2(handle_position) + VIEWPORT_GRID_ROUNDING_BIAS).round() + DVec2::splat(0.5);
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
responses.push_back(Self::overlay_transform_message(line_overlay.to_vec(), transform));
|
||||
|
||||
let marker_overlay = marker_source.take().unwrap_or_else(|| Self::create_handle_overlay(responses));
|
||||
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
|
||||
let angle = 0.;
|
||||
let translation = (parent_transform.transform_point2(handle.position) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round();
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
responses.push_back(Self::overlay_transform_message(marker_overlay.clone(), transform));
|
||||
*marker_source = Some(marker_overlay);
|
||||
};
|
||||
let marker_overlay = marker_source.take().unwrap_or_else(|| Self::create_handle_overlay(responses));
|
||||
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
|
||||
let angle = 0.;
|
||||
let translation = (parent_transform.transform_point2(handle_position) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round();
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
responses.push_back(Self::overlay_transform_message(marker_overlay.clone(), transform));
|
||||
*marker_source = Some(marker_overlay);
|
||||
};
|
||||
|
||||
// Place the handle overlays
|
||||
let [_, h1, h2] = &manipulator_group.points;
|
||||
let [a, b, c, line1, line2] = overlays;
|
||||
let markers = [a, b, c];
|
||||
if let (Some(handle), Some(line_source)) = (h1.as_ref(), line1.as_mut()) {
|
||||
place_handle_and_line(handle, line_source, markers[handle.manipulator_type as usize]);
|
||||
}
|
||||
if let (Some(handle), Some(line_source)) = (h2.as_ref(), line2.as_mut()) {
|
||||
place_handle_and_line(handle, line_source, markers[handle.manipulator_type as usize]);
|
||||
}
|
||||
// Place the handle overlays
|
||||
if let (Some(handle_position), Some(line_overlay)) = (manipulator_group.in_handle, overlays.in_line.as_mut()) {
|
||||
place_handle_and_line(handle_position, line_overlay, &mut overlays.in_handle);
|
||||
}
|
||||
if let (Some(handle_psoition), Some(line_overlay)) = (manipulator_group.out_handle, overlays.out_line.as_ref()) {
|
||||
place_handle_and_line(handle_psoition, line_overlay, &mut overlays.out_handle);
|
||||
}
|
||||
|
||||
// Place the anchor point overlay
|
||||
if let Some(anchor_overlay) = &overlays[ManipulatorType::Anchor as usize] {
|
||||
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
|
||||
let angle = 0.;
|
||||
let translation = (parent_transform.transform_point2(manipulator_point.position) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round();
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
// Place the anchor point overlay
|
||||
if let Some(anchor_overlay) = &overlays.anchor {
|
||||
let scale = DVec2::splat(MANIPULATOR_GROUP_MARKER_SIZE);
|
||||
let angle = 0.;
|
||||
let translation = (parent_transform.transform_point2(anchor) - (scale / 2.) + VIEWPORT_GRID_ROUNDING_BIAS).round();
|
||||
let transform = DAffine2::from_scale_angle_translation(scale, angle, translation).to_cols_array();
|
||||
|
||||
let message = Self::overlay_transform_message(anchor_overlay.clone(), transform);
|
||||
responses.push_back(message);
|
||||
}
|
||||
let message = Self::overlay_transform_message(anchor_overlay.clone(), transform);
|
||||
responses.push_back(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,24 +316,27 @@ impl OverlayRenderer {
|
||||
}
|
||||
|
||||
/// Create an update message for an overlay.
|
||||
fn overlay_modify_message(layer_path: Vec<LayerId>, subpath: Subpath) -> Message {
|
||||
fn overlay_modify_message(layer_path: Vec<LayerId>, subpath: graphene_core::vector::Subpath) -> Message {
|
||||
DocumentMessage::Overlays(Operation::SetShapePath { path: layer_path, subpath }.into()).into()
|
||||
}
|
||||
|
||||
/// Sets the overlay style for this point.
|
||||
fn style_overlays(manipulator_group: &ManipulatorGroup, overlays: &ManipulatorGroupOverlays, responses: &mut VecDeque<Message>) {
|
||||
fn style_overlays(state: &SelectedShapeState, layer_path: &[LayerId], manipulator_group: &GraphiteManipulatorGroup, overlays: &ManipulatorGroupOverlays, responses: &mut VecDeque<Message>) {
|
||||
// TODO Move the style definitions out of the Subpath, should be looked up from a stylesheet or similar
|
||||
let selected_style = style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, POINT_STROKE_WEIGHT + 1.0)), Fill::solid(COLOR_ACCENT));
|
||||
let deselected_style = style::PathStyle::new(Some(Stroke::new(COLOR_ACCENT, POINT_STROKE_WEIGHT)), Fill::solid(Color::WHITE));
|
||||
|
||||
let selected_shape_state = state.get(layer_path);
|
||||
// Update if the manipulator points are shown as selected
|
||||
// Here the index is important, even though overlays[..] has five elements we only care about the first three
|
||||
for (index, point) in manipulator_group.points.iter().enumerate() {
|
||||
if let Some(point) = point {
|
||||
if let Some(overlay) = &overlays[index] {
|
||||
let style = if point.editor_state.is_selected { selected_style.clone() } else { deselected_style.clone() };
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerStyle { path: overlay.clone(), style }.into()).into());
|
||||
}
|
||||
for (index, overlay) in [&overlays.in_handle, &overlays.out_handle, &overlays.anchor].into_iter().enumerate() {
|
||||
let selected_type = [SelectedType::InHandle, SelectedType::OutHandle, SelectedType::Anchor][index];
|
||||
if let Some(overlay_path) = overlay {
|
||||
let selected = selected_shape_state
|
||||
.filter(|state| state.is_selected(ManipulatorPointId::new(manipulator_group.id, selected_type)))
|
||||
.is_some();
|
||||
|
||||
let style = if selected { selected_style.clone() } else { deselected_style.clone() };
|
||||
responses.push_back(DocumentMessage::Overlays(Operation::SetLayerStyle { path: overlay_path.clone(), style }.into()).into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::messages::prelude::*;
|
||||
|
||||
use document_legacy::intersection::Quad;
|
||||
use document_legacy::layers::layer_info::LayerDataType;
|
||||
use document_legacy::layers::nodegraph_layer::NodeGraphFrameLayer;
|
||||
use document_legacy::layers::style::{self, Fill, RenderData, Stroke};
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graphene_std::vector::subpath::Subpath;
|
||||
@@ -35,6 +36,7 @@ impl PathOutline {
|
||||
let subpath = match &document_layer.data {
|
||||
LayerDataType::Shape(layer_shape) => Some(layer_shape.shape.clone()),
|
||||
LayerDataType::Text(text) => Some(text.to_subpath_nonmut(render_data)),
|
||||
LayerDataType::NodeGraphFrame(NodeGraphFrameLayer { vector_data: Some(vector_data), .. }) => Some(Subpath::from_bezier_crate(&vector_data.subpaths)),
|
||||
_ => document_layer.aabb_for_transform(DAffine2::IDENTITY, render_data).map(|[p1, p2]| Subpath::new_rect(p1, p2)),
|
||||
}?;
|
||||
|
||||
|
||||
@@ -171,8 +171,8 @@ impl Pivot {
|
||||
let pivot = transform.inverse().transform_point2(position);
|
||||
// Only update the pivot when computed position is finite. Infinite can happen when scale is 0.
|
||||
if pivot.is_finite() {
|
||||
let layer_path = layer_path.to_owned();
|
||||
responses.push_back(Operation::SetPivot { layer_path, pivot: pivot.into() }.into());
|
||||
let layer = layer_path.to_owned();
|
||||
responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::messages::tool::common_functionality::snapping::SnapManager;
|
||||
|
||||
use document_legacy::layers::style::RenderData;
|
||||
use document_legacy::LayerId;
|
||||
use document_legacy::Operation;
|
||||
|
||||
use glam::{DAffine2, DVec2, Vec2Swizzles};
|
||||
|
||||
@@ -54,9 +53,10 @@ impl Resize {
|
||||
}
|
||||
|
||||
Some(
|
||||
Operation::SetLayerTransformInViewport {
|
||||
path: path.to_vec(),
|
||||
transform: DAffine2::from_scale_angle_translation(size, 0., start).to_cols_array(),
|
||||
GraphOperationMessage::TransformSet {
|
||||
layer: path.to_vec(),
|
||||
transform: DAffine2::from_scale_angle_translation(size, 0., start),
|
||||
transform_in: TransformIn::Viewport,
|
||||
}
|
||||
.into(),
|
||||
)
|
||||
|
||||
@@ -1,32 +1,34 @@
|
||||
use crate::messages::portfolio::document::node_graph::VectorDataModification;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use bezier_rs::TValue;
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
use graphene_std::vector::manipulator_group::ManipulatorGroup;
|
||||
use graphene_std::vector::manipulator_point::ManipulatorPoint;
|
||||
use graphene_std::vector::subpath::{BezierId, Subpath};
|
||||
use bezier_rs::{Bezier, TValue};
|
||||
use document_legacy::LayerId;
|
||||
use graphene_core::uuid::ManipulatorGroupId;
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType, VectorData};
|
||||
|
||||
use document_legacy::document::Document;
|
||||
use glam::DVec2;
|
||||
|
||||
/// ShapeEditor is the container for all of the layer paths that are represented as [Subpath]s and provides
|
||||
/// functionality required to query and create the [Subpath] / [ManipulatorGroup]s / [ManipulatorPoint]s.
|
||||
///
|
||||
/// Overview:
|
||||
/// ```text
|
||||
/// ShapeEditor
|
||||
/// |
|
||||
/// selected_layers <- Paths to selected layers that may contain Subpaths
|
||||
/// / | \
|
||||
/// Subpath ... Subpath <- Reference from layer paths, one Subpath per layer (for now, will eventually be a CompoundPath)
|
||||
/// / | \
|
||||
/// ManipulatorGroup ... ManipulatorGroup <- Subpath contains many ManipulatorGroups
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ShapeEditor {
|
||||
pub struct SelectedLayerState {
|
||||
selected_points: HashSet<ManipulatorPointId>,
|
||||
}
|
||||
impl SelectedLayerState {
|
||||
pub fn is_selected(&self, point: ManipulatorPointId) -> bool {
|
||||
self.selected_points.contains(&point)
|
||||
}
|
||||
pub fn select_point(&mut self, point: ManipulatorPointId) {
|
||||
self.selected_points.insert(point);
|
||||
}
|
||||
pub fn deselect_point(&mut self, point: ManipulatorPointId) {
|
||||
self.selected_points.remove(&point);
|
||||
}
|
||||
}
|
||||
pub type SelectedShapeState = HashMap<Vec<LayerId>, SelectedLayerState>;
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ShapeState {
|
||||
// The layers we can select and edit manipulators (anchors and handles) from
|
||||
selected_layers: Vec<Vec<LayerId>>,
|
||||
pub selected_shape_state: SelectedShapeState,
|
||||
}
|
||||
|
||||
pub struct SelectedPointsInfo<'a> {
|
||||
@@ -36,333 +38,299 @@ pub struct SelectedPointsInfo<'a> {
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub struct ManipulatorPointInfo<'a> {
|
||||
pub shape_layer_path: &'a [LayerId],
|
||||
pub manipulator_group_id: u64,
|
||||
pub manipulator_type: ManipulatorType,
|
||||
pub point_id: ManipulatorPointId,
|
||||
}
|
||||
|
||||
// TODO Consider keeping a list of selected manipulators to minimize traversals of the layers
|
||||
impl ShapeEditor {
|
||||
impl ShapeState {
|
||||
/// Select the first point within the selection threshold.
|
||||
/// Returns a tuple of the points if found and the offset, or `None` otherwise.
|
||||
pub fn select_point(&self, document: &Document, mouse_position: DVec2, select_threshold: f64, add_to_selection: bool, responses: &mut VecDeque<Message>) -> Option<SelectedPointsInfo> {
|
||||
if self.selected_layers.is_empty() {
|
||||
pub fn select_point(&mut self, document: &Document, mouse_position: DVec2, select_threshold: f64, add_to_selection: bool) -> Option<SelectedPointsInfo> {
|
||||
if self.selected_shape_state.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some((shape_layer_path, manipulator_group_id, manipulator_point_index)) = self.find_nearest_point_indices(document, mouse_position, select_threshold) {
|
||||
trace!("Selecting... manipulator group ID: {}, manipulator point index: {}", manipulator_group_id, manipulator_point_index);
|
||||
if let Some((shape_layer_path, manipulator_point_id)) = self.find_nearest_point_indices(document, mouse_position, select_threshold) {
|
||||
trace!("Selecting... manipulator point: {:?}", manipulator_point_id);
|
||||
|
||||
// If the point we're selecting has already been selected
|
||||
// we can assume this point exists.. since we did just click on it hence the unwrap
|
||||
let is_point_selected = self.shape(document, shape_layer_path).unwrap().manipulator_groups().by_id(manipulator_group_id).unwrap().points[manipulator_point_index]
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.editor_state
|
||||
.is_selected;
|
||||
let vector_data = document.layer(&shape_layer_path).ok()?.as_vector_data()?;
|
||||
let manipulator_group = vector_data.manipulator_groups().find(|group| group.id == manipulator_point_id.group)?;
|
||||
let point_position = manipulator_point_id.manipulator_type.get_position(manipulator_group)?;
|
||||
|
||||
let point_position = self.shape(document, shape_layer_path).unwrap().manipulator_groups().by_id(manipulator_group_id).unwrap().points[manipulator_point_index]
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.position;
|
||||
|
||||
// The currently selected points (which are then modified to reflect the selection)
|
||||
let mut points = self
|
||||
.selected_layers()
|
||||
.iter()
|
||||
.filter_map(|path| document.layer(path).ok().map(|layer| (path, layer)))
|
||||
.filter_map(|(path, shape)| shape.as_subpath().map(|subpath| (path, subpath)))
|
||||
.flat_map(|(path, shape)| {
|
||||
shape
|
||||
.manipulator_groups()
|
||||
.enumerate()
|
||||
.filter(|(_id, manipulator_group)| manipulator_group.is_anchor_selected())
|
||||
.flat_map(|(id, manipulator_group)| manipulator_group.selected_points().map(move |point| (id, point.manipulator_type)))
|
||||
.map(|(anchor, manipulator_point)| ManipulatorPointInfo {
|
||||
shape_layer_path: path.as_slice(),
|
||||
manipulator_group_id: *anchor,
|
||||
manipulator_type: manipulator_point,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let selected_shape_state = self.selected_shape_state.get(&shape_layer_path)?;
|
||||
let already_selected = selected_shape_state.is_selected(manipulator_point_id);
|
||||
|
||||
// Should we select or deselect the point?
|
||||
let should_select = if is_point_selected { !add_to_selection } else { true };
|
||||
let new_selected = if already_selected { !add_to_selection } else { true };
|
||||
|
||||
// This is selecting the manipulator only for now, next to generalize to points
|
||||
if should_select {
|
||||
// If we're replacing the selection, clear all points in other selected shapes
|
||||
let add = add_to_selection || is_point_selected;
|
||||
if !add {
|
||||
points.clear();
|
||||
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
|
||||
if new_selected {
|
||||
let retain_existing_selection = add_to_selection || already_selected;
|
||||
if !retain_existing_selection {
|
||||
self.deselect_all();
|
||||
}
|
||||
|
||||
// Add to the selected points
|
||||
let point_info = ManipulatorPointInfo {
|
||||
shape_layer_path,
|
||||
manipulator_group_id,
|
||||
manipulator_type: ManipulatorType::from_index(manipulator_point_index),
|
||||
};
|
||||
|
||||
points.push(point_info);
|
||||
responses.push_back(
|
||||
Operation::SelectManipulatorPoints {
|
||||
layer_path: shape_layer_path.to_vec(),
|
||||
point_ids: vec![(point_info.manipulator_group_id, point_info.manipulator_type)],
|
||||
add,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
let selected_shape_state = self.selected_shape_state.get_mut(&shape_layer_path)?;
|
||||
selected_shape_state.select_point(manipulator_point_id);
|
||||
|
||||
// Offset to snap the selected point to the cursor
|
||||
let offset = document
|
||||
.generate_transform_relative_to_viewport(shape_layer_path)
|
||||
.generate_transform_relative_to_viewport(&shape_layer_path)
|
||||
.map(|viewspace| mouse_position - viewspace.transform_point2(point_position))
|
||||
.unwrap_or_default();
|
||||
|
||||
let points = self
|
||||
.selected_shape_state
|
||||
.iter()
|
||||
.flat_map(|(shape_layer_path, state)| state.selected_points.iter().map(|&point_id| ManipulatorPointInfo { shape_layer_path, point_id }))
|
||||
.collect();
|
||||
|
||||
return Some(SelectedPointsInfo { points, offset });
|
||||
} else {
|
||||
responses.push_back(
|
||||
Operation::DeselectManipulatorPoints {
|
||||
layer_path: shape_layer_path.to_vec(),
|
||||
point_ids: vec![(manipulator_group_id, ManipulatorType::from_index(manipulator_point_index))],
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
points.retain(|x| {
|
||||
*x != ManipulatorPointInfo {
|
||||
shape_layer_path,
|
||||
manipulator_group_id,
|
||||
manipulator_type: ManipulatorType::from_index(manipulator_point_index),
|
||||
}
|
||||
});
|
||||
let selected_shape_state = self.selected_shape_state.get_mut(&shape_layer_path)?;
|
||||
selected_shape_state.deselect_point(manipulator_point_id);
|
||||
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// Deselect all points if no nearby point
|
||||
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
|
||||
self.deselect_all();
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// A wrapper for `find_nearest_point_indices()` and returns a [ManipulatorPoint].
|
||||
pub fn find_nearest_point<'a>(&'a self, document: &'a Document, mouse_position: DVec2, select_threshold: f64) -> Option<&'a ManipulatorPoint> {
|
||||
let (shape_layer_path, manipulator_group_id, manipulator_point_index) = self.find_nearest_point_indices(document, mouse_position, select_threshold)?;
|
||||
let selected_shape = self.shape(document, shape_layer_path).unwrap();
|
||||
if let Some(manipulator_group) = selected_shape.manipulator_groups().by_id(manipulator_group_id) {
|
||||
return manipulator_group.points[manipulator_point_index].as_ref();
|
||||
}
|
||||
None
|
||||
pub fn deselect_all(&mut self) {
|
||||
self.selected_shape_state.values_mut().for_each(|state| state.selected_points.clear());
|
||||
}
|
||||
|
||||
/// Set the shapes we consider for selection, we will choose draggable manipulators from these shapes.
|
||||
pub fn set_selected_layers(&mut self, target_layers: Vec<Vec<LayerId>>) {
|
||||
self.selected_layers = target_layers;
|
||||
self.selected_shape_state.retain(|layer_path, _| target_layers.contains(layer_path));
|
||||
for layer in target_layers {
|
||||
self.selected_shape_state.entry(layer).or_insert_with(SelectedLayerState::default);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected_layers(&self) -> &Vec<Vec<LayerId>> {
|
||||
&self.selected_layers
|
||||
}
|
||||
|
||||
pub fn selected_layers_ref(&self) -> Vec<&[LayerId]> {
|
||||
self.selected_layers.iter().map(|l| l.as_slice()).collect::<Vec<_>>()
|
||||
pub fn selected_layers(&self) -> impl Iterator<Item = &Vec<LayerId>> {
|
||||
self.selected_shape_state.keys()
|
||||
}
|
||||
|
||||
/// Clear all of the shapes we can modify.
|
||||
pub fn clear_selected_layers(&mut self) {
|
||||
self.selected_layers.clear();
|
||||
self.selected_shape_state.clear();
|
||||
}
|
||||
|
||||
pub fn has_selected_layers(&self) -> bool {
|
||||
!self.selected_layers.is_empty()
|
||||
}
|
||||
|
||||
/// Provide the currently selected manipulators by reference.
|
||||
pub fn selected_manipulator_groups<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a ManipulatorGroup> {
|
||||
self.iter(document).flat_map(|shape| shape.selected_manipulator_groups())
|
||||
!self.selected_shape_state.is_empty()
|
||||
}
|
||||
|
||||
/// A mutable iterator of all the manipulators, regardless of selection.
|
||||
pub fn manipulator_groups<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a ManipulatorGroup> {
|
||||
self.iter(document).flat_map(|shape| shape.manipulator_groups().iter())
|
||||
pub fn manipulator_groups<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a bezier_rs::ManipulatorGroup<ManipulatorGroupId>> {
|
||||
self.iter(document).flat_map(|shape| shape.manipulator_groups())
|
||||
}
|
||||
|
||||
// Sets the selected points to all points for the corresponding intersection
|
||||
pub fn select_all_anchors(&self, responses: &mut VecDeque<Message>, itersections: Vec<u64>) {
|
||||
responses.push_back(Operation::SelectAllAnchors { layer_path: itersections }.into());
|
||||
pub fn select_all_anchors(&mut self, document: &Document, layer_path: &[LayerId]) {
|
||||
let Ok(layer) = document.layer(layer_path) else { return };
|
||||
let Some(vector_data) = layer.as_vector_data() else { return };
|
||||
let Some(state) = self.selected_shape_state.get_mut(layer_path) else { return };
|
||||
for manipulator in vector_data.manipulator_groups() {
|
||||
state.select_point(ManipulatorPointId::new(manipulator.id, SelectedType::Anchor))
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide the currently selected points by reference.
|
||||
pub fn selected_points<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a ManipulatorPoint> {
|
||||
self.selected_manipulator_groups(document).flat_map(|manipulator_group| manipulator_group.selected_points())
|
||||
pub fn selected_points<'a>(&'a self) -> impl Iterator<Item = &'a ManipulatorPointId> {
|
||||
self.selected_shape_state.values().flat_map(|state| &state.selected_points)
|
||||
}
|
||||
|
||||
/// Move the selected points by dragging the mouse.
|
||||
pub fn move_selected_points(&self, delta: DVec2, mirror_distance: bool, responses: &mut VecDeque<Message>) {
|
||||
for layer_path in &self.selected_layers {
|
||||
responses.push_back(
|
||||
DocumentMessage::MoveSelectedManipulatorPoints {
|
||||
layer_path: layer_path.clone(),
|
||||
delta: (delta.x, delta.y),
|
||||
mirror_distance,
|
||||
pub fn move_selected_points(&self, document: &Document, delta: DVec2, mirror_distance: bool, responses: &mut VecDeque<Message>) {
|
||||
for (layer_path, state) in &self.selected_shape_state {
|
||||
let Ok(layer) = document.layer(&layer_path) else { continue };
|
||||
let Some(vector_data) = layer.as_vector_data() else { continue };
|
||||
|
||||
let transform = document.multiply_transforms(&layer_path).unwrap_or_default();
|
||||
let delta = transform.inverse().transform_vector2(delta);
|
||||
|
||||
for &point in state.selected_points.iter() {
|
||||
if point.manipulator_type.is_handle() && state.is_selected(ManipulatorPointId::new(point.group, SelectedType::Anchor)) {
|
||||
continue;
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
let Some(group) = vector_data.manipulator_from_id(point.group) else { continue };
|
||||
|
||||
let mut move_point = |point: ManipulatorPointId| {
|
||||
let Some(previous_position) = point.manipulator_type.get_position(group) else { return };
|
||||
let position = previous_position + delta;
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.clone(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
});
|
||||
};
|
||||
|
||||
move_point(point);
|
||||
|
||||
if point.manipulator_type == SelectedType::Anchor {
|
||||
move_point(ManipulatorPointId::new(point.group, SelectedType::InHandle));
|
||||
move_point(ManipulatorPointId::new(point.group, SelectedType::OutHandle));
|
||||
}
|
||||
|
||||
if mirror_distance && point.manipulator_type != SelectedType::Anchor && vector_data.mirror_angle.contains(&point.group) {
|
||||
let Some(mut origional_handle_position) = point.manipulator_type.get_position(group) else { continue };
|
||||
origional_handle_position += delta;
|
||||
|
||||
let point = ManipulatorPointId::new(point.group, point.manipulator_type.opposite());
|
||||
if state.is_selected(point) {
|
||||
continue;
|
||||
}
|
||||
let position = group.anchor - (origional_handle_position - group.anchor);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.clone(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The opposing handle lengths.
|
||||
pub fn opposing_handle_lengths(&self, document: &Document) -> HashMap<Vec<LayerId>, HashMap<u64, f64>> {
|
||||
self.selected_layers()
|
||||
pub fn opposing_handle_lengths(&self, document: &Document) -> HashMap<Vec<LayerId>, HashMap<ManipulatorGroupId, f64>> {
|
||||
self.selected_shape_state
|
||||
.iter()
|
||||
.filter_map(|path| document.layer(path).ok().map(|layer| (path, layer)))
|
||||
.filter_map(|(path, shape)| shape.as_subpath().map(|subpath| (path, subpath)))
|
||||
.map(|(path, shape)| {
|
||||
let opposing_handle_lengths = shape
|
||||
.manipulator_groups()
|
||||
.enumerate()
|
||||
.filter_map(|(id, manipulator_group)| {
|
||||
// We will keep track of the opposing handle length when:
|
||||
// i) Both handles exist and exactly one is selected.
|
||||
// ii) The anchor is not selected.
|
||||
// iii) We have to mirror the angle between handles.
|
||||
.filter_map(|(path, state)| {
|
||||
let layer = document.layer(path).ok()?;
|
||||
let vector_data = layer.as_vector_data()?;
|
||||
let opposing_handle_lengths = vector_data
|
||||
.subpaths
|
||||
.iter()
|
||||
.flat_map(|subpath| {
|
||||
subpath.manipulator_groups().iter().filter_map(|manipulator_group| {
|
||||
// We will keep track of the opposing handle length when:
|
||||
// i) Both handles exist and exactly one is selected.
|
||||
// ii) The anchor is not selected.
|
||||
// iii) We have to mirror the angle between handles.
|
||||
|
||||
if !manipulator_group.editor_state.mirror_angle_between_handles {
|
||||
return None;
|
||||
}
|
||||
let in_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::InHandle));
|
||||
let out_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::OutHandle));
|
||||
let anchor_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::Anchor));
|
||||
|
||||
let mut selected_handles = manipulator_group.selected_handles();
|
||||
let handle = selected_handles.next()?;
|
||||
|
||||
// Check that handle is the only selected handle.
|
||||
if selected_handles.next().is_none() {
|
||||
let opposing_handle_position = manipulator_group.opposing_handle(handle)?.position;
|
||||
let anchor = manipulator_group.points[ManipulatorType::Anchor].as_ref()?;
|
||||
if !anchor.is_selected() {
|
||||
let opposing_handle_length = opposing_handle_position.distance(anchor.position);
|
||||
Some((*id, opposing_handle_length))
|
||||
} else {
|
||||
None
|
||||
if anchor_selected {
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
let single_selected_handle = match (in_handle_selected, out_handle_selected) {
|
||||
(true, false) => SelectedType::InHandle,
|
||||
(false, true) => SelectedType::OutHandle,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let opposing_handle_position = single_selected_handle.opposite().get_position(manipulator_group)?;
|
||||
|
||||
let opposing_handle_length = opposing_handle_position.distance(manipulator_group.anchor);
|
||||
Some((manipulator_group.id, opposing_handle_length))
|
||||
})
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
(path.clone(), opposing_handle_lengths)
|
||||
Some((path.clone(), opposing_handle_lengths))
|
||||
})
|
||||
.collect::<HashMap<_, _>>()
|
||||
}
|
||||
|
||||
/// Reset the opposing handle lengths.
|
||||
pub fn reset_opposing_handle_lengths(&self, document: &Document, opposing_handle_lengths: &HashMap<Vec<LayerId>, HashMap<u64, f64>>, responses: &mut VecDeque<Message>) {
|
||||
self.selected_layers()
|
||||
.iter()
|
||||
.filter_map(|path| document.layer(path).ok().map(|layer| (path, layer)))
|
||||
.filter_map(|(path, shape)| shape.as_subpath().map(|subpath| (path, subpath)))
|
||||
.filter_map(|(path, shape)| opposing_handle_lengths.get(path).map(|layer_opposing_handle_lengths| (path, shape, layer_opposing_handle_lengths)))
|
||||
.flat_map(|(path, shape, layer_opposing_handle_lengths)| {
|
||||
shape
|
||||
.manipulator_groups()
|
||||
.enumerate()
|
||||
.map(move |(id, manipulator_group)| (path, layer_opposing_handle_lengths, id, manipulator_group))
|
||||
})
|
||||
.for_each(|(path, layer_opposing_handle_lengths, id, manipulator_group)| {
|
||||
if !manipulator_group.editor_state.mirror_angle_between_handles {
|
||||
return;
|
||||
}
|
||||
pub fn reset_opposing_handle_lengths(&self, document: &Document, opposing_handle_lengths: &HashMap<Vec<LayerId>, HashMap<ManipulatorGroupId, f64>>, responses: &mut VecDeque<Message>) {
|
||||
for (path, state) in &self.selected_shape_state {
|
||||
let Ok(layer) = document.layer(path) else { continue };
|
||||
let Some(vector_data) = layer.as_vector_data() else { continue };
|
||||
let Some(opposing_handle_lengths) = opposing_handle_lengths.get(path) else { continue };
|
||||
|
||||
let opposing_handle_length = if let Some(length) = layer_opposing_handle_lengths.get(id) {
|
||||
length
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut selected_handles = manipulator_group.selected_handles();
|
||||
let handle = if let Some(handle) = selected_handles.next() {
|
||||
handle
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Check that handle is the only selected handle.
|
||||
if selected_handles.next().is_none() {
|
||||
let opposing_handle = if let Some(opposing_handle) = manipulator_group.opposing_handle(handle) {
|
||||
opposing_handle
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let anchor = if let Some(anchor) = manipulator_group.points[ManipulatorType::Anchor].as_ref() {
|
||||
anchor
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
if anchor.is_selected() {
|
||||
return;
|
||||
for subpath in &vector_data.subpaths {
|
||||
for manipulator_group in subpath.manipulator_groups() {
|
||||
if !vector_data.mirror_angle.contains(&manipulator_group.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(offset) = (opposing_handle.position - anchor.position).try_normalize() {
|
||||
let new_opposing_handle_position = anchor.position + offset * (*opposing_handle_length);
|
||||
assert!(new_opposing_handle_position.is_finite(), "Opposing handle not finite!");
|
||||
responses.push_back(
|
||||
Operation::MoveManipulatorPoint {
|
||||
layer_path: path.clone(),
|
||||
id: *id,
|
||||
manipulator_type: opposing_handle.manipulator_type,
|
||||
position: new_opposing_handle_position.into(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
let Some(opposing_handle_length) = opposing_handle_lengths.get(&manipulator_group.id) else { continue };
|
||||
|
||||
let in_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::InHandle));
|
||||
let out_handle_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::OutHandle));
|
||||
let anchor_selected = state.is_selected(ManipulatorPointId::new(manipulator_group.id, SelectedType::Anchor));
|
||||
|
||||
if anchor_selected {
|
||||
continue;
|
||||
}
|
||||
|
||||
let single_selected_handle = match (in_handle_selected, out_handle_selected) {
|
||||
(true, false) => SelectedType::InHandle,
|
||||
(false, true) => SelectedType::OutHandle,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let Some(opposing_handle) = single_selected_handle.opposite().get_position(manipulator_group) else { continue };
|
||||
|
||||
let Some(offset) = (opposing_handle - manipulator_group.anchor).try_normalize() else { continue };
|
||||
|
||||
let point = ManipulatorPointId::new(manipulator_group.id, single_selected_handle.opposite());
|
||||
let position = manipulator_group.anchor + offset * (*opposing_handle_length);
|
||||
assert!(position.is_finite(), "Opposing handle not finite!");
|
||||
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dissolve the selected points.
|
||||
pub fn delete_selected_points(&self, responses: &mut VecDeque<Message>) {
|
||||
responses.push_back(DocumentMessage::DeleteSelectedManipulatorPoints.into());
|
||||
}
|
||||
|
||||
/// Toggle if the handles should mirror angle across the anchor position.
|
||||
pub fn toggle_handle_mirroring_on_selected(&self, toggle_angle: bool, responses: &mut VecDeque<Message>) {
|
||||
for layer_path in &self.selected_layers {
|
||||
responses.push_back(
|
||||
DocumentMessage::ToggleSelectedHandleMirroring {
|
||||
layer_path: layer_path.clone(),
|
||||
toggle_angle,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
for (layer, state) in &self.selected_shape_state {
|
||||
for &point in &state.selected_points {
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer.to_vec(),
|
||||
modification: VectorDataModification::RemoveManipulatorPoint { point },
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Deselect all manipulators from the shapes that the manipulation handler has created.
|
||||
pub fn deselect_all_points(&self, responses: &mut VecDeque<Message>) {
|
||||
responses.push_back(DocumentMessage::DeselectAllManipulatorPoints.into());
|
||||
/// Toggle if the handles should mirror angle across the anchor position.
|
||||
pub fn toggle_handle_mirroring_on_selected(&self, responses: &mut VecDeque<Message>) {
|
||||
for (layer, state) in &self.selected_shape_state {
|
||||
for point in &state.selected_points {
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer.to_vec(),
|
||||
modification: VectorDataModification::ToggleManipulatorHandleMirroring { id: point.group },
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterate over the shapes.
|
||||
pub fn iter<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a Subpath> + 'a {
|
||||
self.selected_layers.iter().flat_map(|layer_id| document.layer(layer_id)).filter_map(|shape| shape.as_subpath())
|
||||
pub fn iter<'a>(&'a self, document: &'a Document) -> impl Iterator<Item = &'a VectorData> + 'a {
|
||||
self.selected_shape_state
|
||||
.keys()
|
||||
.flat_map(|layer_id| document.layer(layer_id))
|
||||
.filter_map(|shape| shape.as_vector_data())
|
||||
}
|
||||
|
||||
/// Find a [ManipulatorPoint] that is within the selection threshold and return the layer path, an index to the [ManipulatorGroup], and an enum index for [ManipulatorPoint].
|
||||
/// Return value is an `Option` of the tuple representing `(layer path, ManipulatorGroup ID, ManipulatorType enum index)`.
|
||||
fn find_nearest_point_indices(&self, document: &Document, mouse_position: DVec2, select_threshold: f64) -> Option<(&[LayerId], u64, usize)> {
|
||||
if self.selected_layers.is_empty() {
|
||||
pub fn find_nearest_point_indices(&mut self, document: &Document, mouse_position: DVec2, select_threshold: f64) -> Option<(Vec<LayerId>, ManipulatorPointId)> {
|
||||
if self.selected_shape_state.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let select_threshold_squared = select_threshold * select_threshold;
|
||||
// Find the closest control point among all elements of shapes_to_modify
|
||||
for layer in self.selected_layers.iter() {
|
||||
if let Some((manipulator_id, manipulator_point_index, distance_squared)) = self.closest_point_in_layer(document, layer, mouse_position) {
|
||||
for layer in self.selected_shape_state.keys() {
|
||||
if let Some((manipulator_point_id, distance_squared)) = Self::closest_point_in_layer(document, layer, mouse_position) {
|
||||
// Choose the first point under the threshold
|
||||
if distance_squared < select_threshold_squared {
|
||||
trace!("Selecting... manipulator ID: {}, manipulator point index: {}", manipulator_id, manipulator_point_index);
|
||||
return Some((layer, manipulator_id, manipulator_point_index));
|
||||
trace!("Selecting... manipulator point: {:?}", manipulator_point_id);
|
||||
return Some((layer.clone(), manipulator_point_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -373,51 +341,54 @@ impl ShapeEditor {
|
||||
// TODO Use quadtree or some equivalent spatial acceleration structure to improve this to O(log(n))
|
||||
/// Find the closest manipulator, manipulator point, and distance so we can select path elements.
|
||||
/// Brute force comparison to determine which manipulator (handle or anchor) we want to select taking O(n) time.
|
||||
/// Return value is an `Option` of the tuple representing `(manipulator ID, manipulator point index, distance squared)`.
|
||||
fn closest_point_in_layer(&self, document: &Document, layer_path: &[LayerId], pos: glam::DVec2) -> Option<(u64, usize, f64)> {
|
||||
let mut closest_distance_squared: f64 = f64::MAX; // Not ideal
|
||||
let mut result: Option<(u64, usize, f64)> = None;
|
||||
/// Return value is an `Option` of the tuple representing `(ManipulatorPointId, distance squared)`.
|
||||
fn closest_point_in_layer(document: &Document, layer_path: &[LayerId], pos: glam::DVec2) -> Option<(ManipulatorPointId, f64)> {
|
||||
let mut closest_distance_squared: f64 = f64::MAX;
|
||||
let mut result = None;
|
||||
|
||||
if let Some(shape) = document.layer(layer_path).ok()?.as_subpath() {
|
||||
let viewspace = document.generate_transform_relative_to_viewport(layer_path).ok()?;
|
||||
for (manipulator_id, manipulator) in shape.manipulator_groups().enumerate() {
|
||||
let manipulator_point_index = manipulator.closest_point(&viewspace, pos, crate::consts::HIDE_HANDLE_DISTANCE);
|
||||
if let Some(point) = &manipulator.points[manipulator_point_index] {
|
||||
if point.editor_state.can_be_selected {
|
||||
let distance_squared = viewspace.transform_point2(point.position).distance_squared(pos);
|
||||
if distance_squared < closest_distance_squared {
|
||||
closest_distance_squared = distance_squared;
|
||||
result = Some((*manipulator_id, manipulator_point_index, distance_squared));
|
||||
}
|
||||
}
|
||||
let vector_data = document.layer(layer_path).ok()?.as_vector_data()?;
|
||||
let viewspace = document.generate_transform_relative_to_viewport(layer_path).ok()?;
|
||||
for subpath in &vector_data.subpaths {
|
||||
for manipulator in subpath.manipulator_groups() {
|
||||
let (selected, distance_squared) = SelectedType::closest_widget(manipulator, viewspace, pos, crate::consts::HIDE_HANDLE_DISTANCE);
|
||||
|
||||
if distance_squared < closest_distance_squared {
|
||||
closest_distance_squared = distance_squared;
|
||||
result = Some((ManipulatorPointId::new(manipulator.id, selected), distance_squared));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Find the `t` value along the path segment we have clicked upon, together with that segment ID.
|
||||
///
|
||||
/// Returns a tuple of [`BezierId`] and `t` as an f64.
|
||||
fn closest_segment(&self, document: &Document, layer_path: &[LayerId], position: glam::DVec2, tolerance: f64) -> Option<(BezierId, f64)> {
|
||||
/// Returns a tuple of subpath_index, manipulator_start and `t` as an f64.
|
||||
fn closest_segment(&self, document: &Document, layer_path: &[LayerId], position: glam::DVec2, tolerance: f64) -> Option<(ManipulatorGroupId, ManipulatorGroupId, Bezier, f64)> {
|
||||
let transform = document.generate_transform_relative_to_viewport(layer_path).ok()?;
|
||||
let layer_pos = transform.inverse().transform_point2(position);
|
||||
let projection_options = bezier_rs::ProjectionOptions { lut_size: 5, ..Default::default() };
|
||||
|
||||
let mut result: Option<(BezierId, f64)> = None;
|
||||
let mut result = None;
|
||||
let mut closest_distance_squared: f64 = tolerance * tolerance;
|
||||
|
||||
for bezier_id in document.layer(layer_path).ok()?.as_subpath()?.bezier_iter() {
|
||||
let bezier = bezier_id.internal;
|
||||
let t = bezier.project(layer_pos, projection_options);
|
||||
let layerspace = bezier.evaluate(TValue::Parametric(t));
|
||||
let vector_data = document.layer(layer_path).ok()?.as_vector_data()?;
|
||||
|
||||
let screenspace = transform.transform_point2(layerspace);
|
||||
let distance_squared = screenspace.distance_squared(position);
|
||||
for subpath in &vector_data.subpaths {
|
||||
for (manipulator_index, bezier) in subpath.iter().enumerate() {
|
||||
let t = bezier.project(layer_pos, projection_options);
|
||||
let layerspace = bezier.evaluate(TValue::Parametric(t));
|
||||
|
||||
if distance_squared < closest_distance_squared {
|
||||
closest_distance_squared = distance_squared;
|
||||
result = Some((bezier_id, t));
|
||||
let screenspace = transform.transform_point2(layerspace);
|
||||
let distance_squared = screenspace.distance_squared(position);
|
||||
|
||||
if distance_squared < closest_distance_squared {
|
||||
closest_distance_squared = distance_squared;
|
||||
let start = subpath.manipulator_groups()[manipulator_index];
|
||||
let end = subpath.manipulator_groups()[(manipulator_index + 1) % subpath.len()];
|
||||
result = Some((start.id, end.id, bezier, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,34 +397,36 @@ impl ShapeEditor {
|
||||
|
||||
/// Handles the splitting of a curve to insert new points (which can be activated by double clicking on a curve with the Path tool).
|
||||
pub fn split(&self, document: &Document, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) {
|
||||
for layer_path in &self.selected_layers {
|
||||
if let Some((bezier_id, t)) = self.closest_segment(document, layer_path, position, tolerance) {
|
||||
let [first, second] = bezier_id.internal.split(TValue::Parametric(t));
|
||||
for layer_path in self.selected_layers() {
|
||||
if let Some((start, end, bezier, t)) = self.closest_segment(document, layer_path, position, tolerance) {
|
||||
let [first, second] = bezier.split(TValue::Parametric(t));
|
||||
|
||||
// Adjust the first manipulator group's out handle
|
||||
let out_handle = Operation::SetManipulatorPoints {
|
||||
layer_path: layer_path.clone(),
|
||||
id: bezier_id.start,
|
||||
manipulator_type: ManipulatorType::OutHandle,
|
||||
position: first.handle_start().map(|p| p.into()),
|
||||
let point = ManipulatorPointId::new(start, SelectedType::OutHandle);
|
||||
let position = first.handle_start().unwrap_or(first.start());
|
||||
let out_handle = GraphOperationMessage::Vector {
|
||||
layer: layer_path.clone(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
};
|
||||
responses.add(out_handle);
|
||||
|
||||
// Insert a new manipulator group between the existing ones
|
||||
let insert = Operation::InsertManipulatorGroup {
|
||||
layer_path: layer_path.clone(),
|
||||
manipulator_group: ManipulatorGroup::new_with_handles(first.end(), first.handle_end(), second.handle_start()),
|
||||
after_id: bezier_id.end,
|
||||
let manipulator_group = bezier_rs::ManipulatorGroup::new(first.end(), first.handle_end(), second.handle_start());
|
||||
let insert = GraphOperationMessage::Vector {
|
||||
layer: layer_path.clone(),
|
||||
modification: VectorDataModification::AddManipulatorGroup { manipulator_group, after_id: start },
|
||||
};
|
||||
responses.add(insert);
|
||||
|
||||
// Adjust the last manipulator group's in handle
|
||||
let in_handle = Operation::SetManipulatorPoints {
|
||||
layer_path: layer_path.clone(),
|
||||
id: bezier_id.end,
|
||||
manipulator_type: ManipulatorType::InHandle,
|
||||
position: second.handle_end().map(|p| p.into()),
|
||||
let point = ManipulatorPointId::new(end, SelectedType::InHandle);
|
||||
let position = second.handle_end().unwrap_or(second.end());
|
||||
let in_handle = GraphOperationMessage::Vector {
|
||||
layer: layer_path.clone(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position },
|
||||
};
|
||||
responses.add(in_handle);
|
||||
|
||||
responses.extend([out_handle.into(), insert.into(), in_handle.into()]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -462,53 +435,57 @@ impl ShapeEditor {
|
||||
/// Handles the flipping between sharp corner and smooth (which can be activated by double clicking on an anchor with the Path tool).
|
||||
pub fn flip_sharp(&self, document: &Document, position: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
|
||||
let mut process_layer = |layer_path| {
|
||||
let manipulator_groups = document.layer(layer_path).ok()?.as_subpath()?.manipulator_groups();
|
||||
let vector_data = document.layer(layer_path).ok()?.as_vector_data()?;
|
||||
|
||||
let transform_to_screenspace = document.generate_transform_relative_to_viewport(layer_path).ok()?;
|
||||
let mut result = None;
|
||||
let mut closest_distance_squared = tolerance * tolerance;
|
||||
|
||||
// Find the closest anchor point on the current layer
|
||||
for (index, (&bezier_id, group)) in manipulator_groups.enumerate().enumerate() {
|
||||
if let Some(anchor) = &group.points[ManipulatorType::Anchor as usize] {
|
||||
let screenspace = transform_to_screenspace.transform_point2(anchor.position);
|
||||
for (subpath_index, subpath) in vector_data.subpaths.iter().enumerate() {
|
||||
for (manipulator_index, manipulator) in subpath.manipulator_groups().iter().enumerate() {
|
||||
let screenspace = transform_to_screenspace.transform_point2(manipulator.anchor);
|
||||
let distance_squared = screenspace.distance_squared(position);
|
||||
|
||||
if distance_squared < closest_distance_squared {
|
||||
closest_distance_squared = distance_squared;
|
||||
result = Some((anchor.position, index, bezier_id, group));
|
||||
result = Some((subpath_index, manipulator_index, manipulator));
|
||||
}
|
||||
}
|
||||
}
|
||||
let (anchor_position, index, bezier_id, group) = result?;
|
||||
let (subpath_index, index, manipulator) = result?;
|
||||
let anchor_position = manipulator.anchor;
|
||||
|
||||
let subpath = &vector_data.subpaths[subpath_index];
|
||||
|
||||
// Check by comparing the handle positions to the anchor if this maniuplator group is a point
|
||||
let already_sharp = match &group.points {
|
||||
[_, Some(in_handle), Some(out_handle)] => anchor_position.abs_diff_eq(in_handle.position, f64::EPSILON * 100.) && anchor_position.abs_diff_eq(out_handle.position, f64::EPSILON * 100.),
|
||||
[_, Some(handle), None] | [_, None, Some(handle)] => anchor_position.abs_diff_eq(handle.position, f64::EPSILON * 100.),
|
||||
[_, None, None] => true,
|
||||
let already_sharp = match (manipulator.in_handle, manipulator.out_handle) {
|
||||
(Some(in_handle), Some(out_handle)) => anchor_position.abs_diff_eq(in_handle, 1e-10) && anchor_position.abs_diff_eq(out_handle, 1e-10),
|
||||
(Some(handle), None) | (None, Some(handle)) => anchor_position.abs_diff_eq(handle, 1e-10),
|
||||
(None, None) => true,
|
||||
};
|
||||
|
||||
let manipulator_groups = subpath.manipulator_groups();
|
||||
let (in_handle, out_handle) = if already_sharp {
|
||||
let is_closed = manipulator_groups.last().filter(|group| group.is_close()).is_some();
|
||||
let is_closed = subpath.closed();
|
||||
|
||||
// Grab the next and previous manipulator groups by simply looking at the next / previous index
|
||||
let mut previous_position = index.checked_sub(1).and_then(|index| manipulator_groups.by_index(index)).and_then(|group| group.points[0].as_ref());
|
||||
let mut next_position = manipulator_groups.by_index(index + 1).and_then(|group| group.points[0].as_ref());
|
||||
let mut previous_position = index.checked_sub(1).and_then(|index| manipulator_groups.get(index)).map(|group| group.anchor);
|
||||
let mut next_position = manipulator_groups.get(index + 1).map(|group| group.anchor);
|
||||
|
||||
// Wrapping around closed path (assuming format is point elements then a single close path)
|
||||
// Wrapping around closed path
|
||||
if is_closed {
|
||||
previous_position = previous_position.or_else(|| manipulator_groups.iter().nth_back(1).and_then(|group| group.points[0].as_ref()));
|
||||
next_position = next_position.or_else(|| manipulator_groups.first().and_then(|group| group.points[0].as_ref()));
|
||||
previous_position = previous_position.or_else(|| manipulator_groups.last().map(|group| group.anchor));
|
||||
next_position = next_position.or_else(|| manipulator_groups.first().map(|group| group.anchor));
|
||||
}
|
||||
|
||||
// To find the length of the new tangent we just take the distance to the anchor and divide by 3 (pretty arbitrary)
|
||||
let length_previous = previous_position.map(|point| (point.position - anchor_position).length() / 3.);
|
||||
let length_next = next_position.map(|point| (point.position - anchor_position).length() / 3.);
|
||||
let length_previous = previous_position.map(|point| (point - anchor_position).length() / 3.);
|
||||
let length_next = next_position.map(|point| (point - anchor_position).length() / 3.);
|
||||
|
||||
// Use the position relative to the anchor
|
||||
let previous_angle = previous_position.map(|point| (point.position - anchor_position)).map(|pos| pos.y.atan2(pos.x));
|
||||
let next_angle = next_position.map(|point| (point.position - anchor_position)).map(|pos| pos.y.atan2(pos.x));
|
||||
let previous_angle = previous_position.map(|point| (point - anchor_position)).map(|pos| pos.y.atan2(pos.x));
|
||||
let next_angle = next_position.map(|point| (point - anchor_position)).map(|pos| pos.y.atan2(pos.x));
|
||||
|
||||
// The direction of the handles is either the perpendicular vector to the sum of the anchors' positions or just the anchor's position (if only one)
|
||||
let handle_direction = match (previous_angle, next_angle) {
|
||||
@@ -519,21 +496,20 @@ impl ShapeEditor {
|
||||
};
|
||||
|
||||
// Mirror the angle but not the distance
|
||||
responses.push_back(
|
||||
Operation::SetManipulatorHandleMirroring {
|
||||
layer_path: layer_path.to_vec(),
|
||||
id: bezier_id,
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorHandleMirroring {
|
||||
id: manipulator.id,
|
||||
mirror_angle: true,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
let (sin, cos) = handle_direction.sin_cos();
|
||||
let mut handle_vector = DVec2::new(cos, sin);
|
||||
|
||||
// Flip the vector if it is not facing towards the same direction as the anchor
|
||||
if previous_position.filter(|pos| (pos.position - anchor_position).normalize().dot(handle_vector) < 0.).is_some()
|
||||
|| next_position.filter(|pos| (pos.position - anchor_position).normalize().dot(handle_vector) > 0.).is_some()
|
||||
if previous_position.filter(|&pos| (pos - anchor_position).normalize().dot(handle_vector) < 0.).is_some()
|
||||
|| next_position.filter(|&pos| (pos - anchor_position).normalize().dot(handle_vector) > 0.).is_some()
|
||||
{
|
||||
handle_vector = -handle_vector;
|
||||
}
|
||||
@@ -548,34 +524,26 @@ impl ShapeEditor {
|
||||
|
||||
// Push both in and out handles into the correct position
|
||||
if let Some(in_handle) = in_handle {
|
||||
let in_handle = Operation::SetManipulatorPoints {
|
||||
layer_path: layer_path.to_vec(),
|
||||
id: bezier_id,
|
||||
manipulator_type: ManipulatorType::InHandle,
|
||||
position: Some(in_handle.into()),
|
||||
};
|
||||
responses.push_back(in_handle.into());
|
||||
let point = ManipulatorPointId::new(manipulator.id, SelectedType::InHandle);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: in_handle },
|
||||
});
|
||||
}
|
||||
if let Some(out_handle) = out_handle {
|
||||
let out_handle = Operation::SetManipulatorPoints {
|
||||
layer_path: layer_path.to_vec(),
|
||||
id: bezier_id,
|
||||
manipulator_type: ManipulatorType::OutHandle,
|
||||
position: Some(out_handle.into()),
|
||||
};
|
||||
responses.push_back(out_handle.into());
|
||||
let point = ManipulatorPointId::new(manipulator.id, SelectedType::OutHandle);
|
||||
responses.add(GraphOperationMessage::Vector {
|
||||
layer: layer_path.to_vec(),
|
||||
modification: VectorDataModification::SetManipulatorPosition { point, position: out_handle },
|
||||
});
|
||||
}
|
||||
Some(true)
|
||||
};
|
||||
for layer_path in &self.selected_layers {
|
||||
for layer_path in self.selected_shape_state.keys() {
|
||||
if let Some(result) = process_layer(layer_path) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn shape<'a>(&'a self, document: &'a Document, layer_id: &[u64]) -> Option<&'a Subpath> {
|
||||
document.layer(layer_id).ok()?.as_subpath()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ use crate::consts::{
|
||||
};
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use document_legacy::layers::layer_info::{Layer, LayerDataType};
|
||||
use document_legacy::layers::layer_info::Layer;
|
||||
use document_legacy::layers::style::{self, Stroke};
|
||||
use document_legacy::{LayerId, Operation};
|
||||
use graphene_std::vector::consts::ManipulatorType;
|
||||
use graphene_core::vector::{ManipulatorPointId, SelectedType};
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use std::f64::consts::PI;
|
||||
@@ -260,35 +260,26 @@ impl SnapManager {
|
||||
include_handles: bool,
|
||||
ignore_points: &[ManipulatorPointInfo],
|
||||
) {
|
||||
if let LayerDataType::Shape(shape_layer) = &layer.data {
|
||||
let transform = document_message_handler.document_legacy.multiply_transforms(path).unwrap();
|
||||
let snap_points = shape_layer
|
||||
.shape
|
||||
.manipulator_groups()
|
||||
.enumerate()
|
||||
.flat_map(|(id, shape)| {
|
||||
if include_handles {
|
||||
[
|
||||
(*id, &shape.points[ManipulatorType::Anchor]),
|
||||
(*id, &shape.points[ManipulatorType::InHandle]),
|
||||
(*id, &shape.points[ManipulatorType::OutHandle]),
|
||||
]
|
||||
} else {
|
||||
[(*id, &shape.points[ManipulatorType::Anchor]), (0, &None), (0, &None)]
|
||||
}
|
||||
})
|
||||
.filter_map(|(id, point)| point.as_ref().map(|val| (id, val)))
|
||||
.filter(|(id, point)| {
|
||||
!ignore_points.contains(&ManipulatorPointInfo {
|
||||
shape_layer_path: path,
|
||||
manipulator_group_id: *id,
|
||||
manipulator_type: point.manipulator_type,
|
||||
})
|
||||
})
|
||||
.map(|(_id, point)| DVec2::new(point.position.x, point.position.y))
|
||||
.map(|pos| transform.transform_point2(pos));
|
||||
self.add_snap_points(document_message_handler, input, snap_points);
|
||||
}
|
||||
let Some(vector_data) = &layer.as_vector_data() else { return };
|
||||
|
||||
let transform = document_message_handler.document_legacy.multiply_transforms(path).unwrap();
|
||||
let snap_points = vector_data
|
||||
.manipulator_groups()
|
||||
.flat_map(|group| {
|
||||
if include_handles {
|
||||
[
|
||||
Some((ManipulatorPointId::new(group.id, SelectedType::Anchor), group.anchor)),
|
||||
group.in_handle.map(|pos| (ManipulatorPointId::new(group.id, SelectedType::InHandle), pos)),
|
||||
group.out_handle.map(|pos| (ManipulatorPointId::new(group.id, SelectedType::OutHandle), pos)),
|
||||
]
|
||||
} else {
|
||||
[Some((ManipulatorPointId::new(group.id, SelectedType::Anchor), group.anchor)), None, None]
|
||||
}
|
||||
})
|
||||
.flatten()
|
||||
.filter(|&(point_id, _)| !ignore_points.contains(&ManipulatorPointInfo { shape_layer_path: path, point_id }))
|
||||
.map(|(_, pos)| transform.transform_point2(pos));
|
||||
self.add_snap_points(document_message_handler, input, snap_points);
|
||||
}
|
||||
|
||||
/// Adds all of the shape handles in the document, including bézier handles of the points specified
|
||||
|
||||
Reference in New Issue
Block a user