mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-08-03 21:08:18 +00:00
Bundle Graphite using Tauri (#873)
* Setup tauri component for graphite editor Integrate graphite into tauri app Split interpreted-executor out of graph-craft * Add gpu execution node * General Cleanup
This commit is contained in:
parent
52cc770a1e
commit
7d8f94462a
109 changed files with 5661 additions and 544 deletions
|
@ -12,13 +12,17 @@ license = "MIT OR Apache-2.0"
|
|||
derive = ["graph-proc-macros"]
|
||||
memoization = ["once_cell"]
|
||||
default = ["derive", "memoization"]
|
||||
gpu = ["graph-craft/gpu", "graphene-core/gpu"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
graphene-core = {path = "../gcore", features = ["async", "std"], default-features = false}
|
||||
graphene-core = {path = "../gcore", features = ["async", "std" ], default-features = false}
|
||||
borrow_stack = {path = "../borrow_stack"}
|
||||
dyn-any = {path = "../../libraries/dyn-any", features = ["derive"]}
|
||||
graph-proc-macros = {path = "../proc-macro", optional = true}
|
||||
graph-craft = {path = "../graph-craft"}
|
||||
bytemuck = {version = "1.8" }
|
||||
tempfile = "3"
|
||||
once_cell = {version= "1.10", optional = true}
|
||||
#pretty-token-stream = {path = "../../pretty-token-stream"}
|
||||
syn = {version = "1.0", default-features = false, features = ["parsing", "printing"]}
|
||||
|
@ -32,7 +36,7 @@ bezier-rs = { path = "../../libraries/bezier-rs" }
|
|||
kurbo = { git = "https://github.com/linebender/kurbo.git", features = [
|
||||
"serde",
|
||||
] }
|
||||
glam = { version = "0.17", features = ["serde"] }
|
||||
glam = { version = "0.22", features = ["serde"] }
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0"
|
||||
|
|
|
@ -30,7 +30,9 @@ where
|
|||
input.borrow().hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
|
||||
self.map.get_or_create_with(&hash, || CacheNode::new(self.node))
|
||||
self.map.get_or_create_with(&hash, ||{
|
||||
trace!("Creating new cache node");
|
||||
CacheNode::new(self.node)})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
135
node-graph/gstd/src/executor.rs
Normal file
135
node-graph/gstd/src/executor.rs
Normal file
|
@ -0,0 +1,135 @@
|
|||
use bytemuck::Pod;
|
||||
use core::marker::PhantomData;
|
||||
use dyn_any::StaticTypeSized;
|
||||
use graph_craft::document::*;
|
||||
use graph_craft::proto::*;
|
||||
use graphene_core::{raster::Image, value::ValueNode, Node};
|
||||
|
||||
pub struct MapGpuNode<NN: Node<()>, I: IntoIterator<Item = S>, S: StaticTypeSized + Sync + Send + Pod, O: StaticTypeSized + Sync + Send + Pod>(pub NN, PhantomData<(S, I, O)>);
|
||||
|
||||
impl<'n, I: IntoIterator<Item = S>, NN: Node<(), Output = &'n NodeNetwork> + Copy, S: StaticTypeSized + Sync + Send + Pod, O: StaticTypeSized + Sync + Send + Pod> Node<I>
|
||||
for &MapGpuNode<NN, I, S, O>
|
||||
{
|
||||
type Output = Vec<O>;
|
||||
fn eval(self, input: I) -> Self::Output {
|
||||
let network = self.0.eval(());
|
||||
|
||||
use graph_craft::executor::Compiler;
|
||||
use graph_craft::executor::Executor;
|
||||
use graph_craft::gpu::compiler::Metadata;
|
||||
let compiler = Compiler {};
|
||||
let proto_network = compiler.compile(network.clone(), true);
|
||||
|
||||
let m = Metadata::new("project".to_owned(), vec!["test@example.com".to_owned()]);
|
||||
let temp_dir = tempfile::tempdir().expect("failed to create tempdir");
|
||||
|
||||
use graph_craft::gpu::context::Context;
|
||||
use graph_craft::gpu::executor::GpuExecutor;
|
||||
let executor: GpuExecutor<S, O> = GpuExecutor::new(Context::new(), proto_network, m, temp_dir.path()).unwrap();
|
||||
|
||||
let data: Vec<_> = input.into_iter().collect();
|
||||
let result = executor.execute(Box::new(data)).unwrap();
|
||||
let result = dyn_any::downcast::<Vec<O>>(result).unwrap();
|
||||
*result
|
||||
}
|
||||
}
|
||||
impl<'n, I: IntoIterator<Item = S>, NN: Node<(), Output = &'n NodeNetwork> + Copy, S: StaticTypeSized + Sync + Send + Pod, O: StaticTypeSized + Sync + Send + Pod> Node<I> for MapGpuNode<NN, I, S, O> {
|
||||
type Output = Vec<O>;
|
||||
fn eval(self, input: I) -> Self::Output {
|
||||
let network = self.0.eval(());
|
||||
|
||||
use graph_craft::executor::Compiler;
|
||||
use graph_craft::executor::Executor;
|
||||
use graph_craft::gpu::compiler::Metadata;
|
||||
let compiler = Compiler {};
|
||||
let proto_network = compiler.compile(network.clone(), true);
|
||||
|
||||
let m = Metadata::new("project".to_owned(), vec!["test@example.com".to_owned()]);
|
||||
let temp_dir = tempfile::tempdir().expect("failed to create tempdir");
|
||||
|
||||
use graph_craft::gpu::context::Context;
|
||||
use graph_craft::gpu::executor::GpuExecutor;
|
||||
let executor: GpuExecutor<S, O> = GpuExecutor::new(Context::new(), proto_network, m, temp_dir.path()).unwrap();
|
||||
|
||||
let data: Vec<_> = input.into_iter().collect();
|
||||
let result = executor.execute(Box::new(data)).unwrap();
|
||||
let result = dyn_any::downcast::<Vec<O>>(result).unwrap();
|
||||
*result
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: IntoIterator<Item = S>, NN: Node<()>, S: StaticTypeSized + Sync + Pod + Send, O: StaticTypeSized + Sync + Send + Pod> MapGpuNode<NN, I, S, O> {
|
||||
pub const fn new(network: NN) -> Self {
|
||||
MapGpuNode(network, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MapGpuSingleImageNode<NN: Node<(), Output = String>>(pub NN);
|
||||
|
||||
impl<NN: Node<(), Output = String> + Copy> Node<Image> for MapGpuSingleImageNode<NN> {
|
||||
type Output = Image;
|
||||
fn eval(self, input: Image) -> Self::Output {
|
||||
let node = self.0.eval(());
|
||||
use graph_craft::document::*;
|
||||
|
||||
let identifier = NodeIdentifier {
|
||||
name: std::borrow::Cow::Owned(node),
|
||||
types: std::borrow::Cow::Borrowed(&[]),
|
||||
};
|
||||
|
||||
let network = NodeNetwork {
|
||||
inputs: vec![0],
|
||||
output: 0,
|
||||
nodes: [(
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "Image filter Node".into(),
|
||||
inputs: vec![NodeInput::Network],
|
||||
implementation: DocumentNodeImplementation::Unresolved(identifier),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let value_network = ValueNode::new(network);
|
||||
let map_node = MapGpuNode::new(&value_network);
|
||||
let data = map_node.eval(input.data.clone());
|
||||
Image { data, ..input }
|
||||
}
|
||||
}
|
||||
|
||||
impl<NN: Node<(), Output = String> + Copy> Node<Image> for &MapGpuSingleImageNode<NN> {
|
||||
type Output = Image;
|
||||
fn eval(self, input: Image) -> Self::Output {
|
||||
let node = self.0.eval(());
|
||||
use graph_craft::document::*;
|
||||
|
||||
let identifier = NodeIdentifier {
|
||||
name: std::borrow::Cow::Owned(node),
|
||||
types: std::borrow::Cow::Borrowed(&[]),
|
||||
};
|
||||
|
||||
let network = NodeNetwork {
|
||||
inputs: vec![0],
|
||||
output: 0,
|
||||
nodes: [(
|
||||
0,
|
||||
DocumentNode {
|
||||
name: "Image filter Node".into(),
|
||||
inputs: vec![NodeInput::Network],
|
||||
implementation: DocumentNodeImplementation::Unresolved(identifier),
|
||||
metadata: DocumentNodeMetadata::default(),
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
};
|
||||
|
||||
let value_network = ValueNode::new(network);
|
||||
let map_node = MapGpuNode::new(&value_network);
|
||||
let data = map_node.eval(input.data.clone());
|
||||
Image { data, ..input }
|
||||
}
|
||||
}
|
|
@ -9,8 +9,10 @@ extern crate log;
|
|||
pub mod memo;
|
||||
|
||||
pub mod raster;
|
||||
pub mod vector;
|
||||
|
||||
pub mod any;
|
||||
|
||||
#[cfg(feature = "gpu")]
|
||||
pub mod executor;
|
||||
|
||||
pub use graphene_core::*;
|
||||
|
|
|
@ -9,7 +9,10 @@ pub struct CacheNode<CachedNode: Node<I>, I> {
|
|||
impl<'n, CashedNode: Node<I> + Copy, I> Node<I> for &'n CacheNode<CashedNode, I> {
|
||||
type Output = &'n CashedNode::Output;
|
||||
fn eval(self, input: I) -> Self::Output {
|
||||
self.cache.get_or_init(|| self.node.eval(input))
|
||||
self.cache.get_or_init(|| {
|
||||
trace!("Creating new cache node");
|
||||
self.node.eval(input)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use core::marker::PhantomData;
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use graphene_core::ops::FlatMapResultNode;
|
||||
use graphene_core::raster::color::Color;
|
||||
use graphene_core::raster::{Color, Image};
|
||||
use graphene_core::structural::{ComposeNode, ConsNode};
|
||||
use graphene_core::{generic::FnNode, ops::MapResultNode, structural::Then, value::ValueNode, Node};
|
||||
use image::Pixel;
|
||||
|
@ -98,40 +98,6 @@ impl<Reader: std::io::Read> Node<Reader> for BufferNode {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DynAny, Default)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Image {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub data: Vec<Color>,
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub const fn empty() -> Self {
|
||||
Self {
|
||||
width: 0,
|
||||
height: 0,
|
||||
data: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Image {
|
||||
type Item = Color;
|
||||
type IntoIter = std::vec::IntoIter<Color>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.data.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Image {
|
||||
type Item = &'a Color;
|
||||
type IntoIter = std::slice::Iter<'a, Color>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.data.iter()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_node<'n, P: AsRef<Path> + 'n>() -> impl Node<P, Output = Result<Vec<u8>, Error>> {
|
||||
let fs = ValueNode(StdFs).clone();
|
||||
let fs = ConsNode::new(fs);
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
use std::ops::{Index, IndexMut};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[repr(usize)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Copy, Serialize, Deserialize)]
|
||||
pub enum ManipulatorType {
|
||||
Anchor,
|
||||
InHandle,
|
||||
OutHandle,
|
||||
}
|
||||
|
||||
impl ManipulatorType {
|
||||
pub fn from_index(index: usize) -> ManipulatorType {
|
||||
match index {
|
||||
0 => ManipulatorType::Anchor,
|
||||
1 => ManipulatorType::InHandle,
|
||||
2 => ManipulatorType::OutHandle,
|
||||
_ => ManipulatorType::Anchor,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn opposite_handle(self) -> ManipulatorType {
|
||||
match self {
|
||||
ManipulatorType::Anchor => ManipulatorType::Anchor,
|
||||
ManipulatorType::InHandle => ManipulatorType::OutHandle,
|
||||
ManipulatorType::OutHandle => ManipulatorType::InHandle,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Allows us to use ManipulatorType for indexing
|
||||
impl<T> Index<ManipulatorType> for [T; 3] {
|
||||
type Output = T;
|
||||
fn index(&self, mt: ManipulatorType) -> &T {
|
||||
&self[mt as usize]
|
||||
}
|
||||
}
|
||||
|
||||
// Allows us to use ManipulatorType for indexing, mutably
|
||||
impl<T> IndexMut<ManipulatorType> for [T; 3] {
|
||||
fn index_mut(&mut self, mt: ManipulatorType) -> &mut T {
|
||||
&mut self[mt as usize]
|
||||
}
|
||||
}
|
||||
|
||||
// Remove when no longer needed
|
||||
pub const SELECTION_THRESHOLD: f64 = 10.;
|
|
@ -1,158 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_core::Node;
|
||||
|
||||
use super::subpath::Subpath;
|
||||
|
||||
type VectorData = Subpath;
|
||||
|
||||
pub struct UnitCircleGenerator;
|
||||
|
||||
impl Node<()> for UnitCircleGenerator {
|
||||
type Output = VectorData;
|
||||
fn eval(self, input: ()) -> Self::Output {
|
||||
(&self).eval(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl Node<()> for &UnitCircleGenerator {
|
||||
type Output = VectorData;
|
||||
fn eval(self, _input: ()) -> Self::Output {
|
||||
Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct UnitSquareGenerator;
|
||||
|
||||
impl Node<()> for UnitSquareGenerator {
|
||||
type Output = VectorData;
|
||||
fn eval(self, input: ()) -> Self::Output {
|
||||
(&self).eval(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl Node<()> for &UnitSquareGenerator {
|
||||
type Output = VectorData;
|
||||
fn eval(self, _input: ()) -> Self::Output {
|
||||
Subpath::new_rect(DVec2::ZERO, DVec2::ONE)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PathGenerator(Arc<Subpath>);
|
||||
|
||||
impl Node<()> for PathGenerator {
|
||||
type Output = VectorData;
|
||||
fn eval(self, input: ()) -> Self::Output {
|
||||
(&self).eval(input)
|
||||
}
|
||||
}
|
||||
|
||||
impl Node<()> for &PathGenerator {
|
||||
type Output = VectorData;
|
||||
fn eval(self, _input: ()) -> Self::Output {
|
||||
(*self.0).clone()
|
||||
}
|
||||
}
|
||||
use crate::raster::Image;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BlitSubpath<N: Node<(), Output = Subpath>>(N);
|
||||
|
||||
impl<N: Node<(), Output = Subpath>> Node<Image> for BlitSubpath<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, input: Image) -> Self::Output {
|
||||
let subpath = self.0.eval(());
|
||||
info!("Blitting subpath {subpath:?}");
|
||||
input
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node<(), Output = Subpath> + Copy> Node<Image> for &BlitSubpath<N> {
|
||||
type Output = Image;
|
||||
fn eval(self, input: Image) -> Self::Output {
|
||||
let subpath = self.0.eval(());
|
||||
info!("Blitting subpath {subpath:?}");
|
||||
input
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Node<(), Output = Subpath>> BlitSubpath<N> {
|
||||
pub fn new(node: N) -> Self {
|
||||
Self(node)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct TransformSubpathNode<Translation, Rotation, Scale, Shear>
|
||||
where
|
||||
Translation: Node<(), Output = DVec2>,
|
||||
Rotation: Node<(), Output = f64>,
|
||||
Scale: Node<(), Output = DVec2>,
|
||||
Shear: Node<(), Output = DVec2>,
|
||||
{
|
||||
translate_node: Translation,
|
||||
rotate_node: Rotation,
|
||||
scale_node: Scale,
|
||||
shear_node: Shear,
|
||||
}
|
||||
|
||||
impl<Translation, Rotation, Scale, Shear> TransformSubpathNode<Translation, Rotation, Scale, Shear>
|
||||
where
|
||||
Translation: Node<(), Output = DVec2>,
|
||||
Rotation: Node<(), Output = f64>,
|
||||
Scale: Node<(), Output = DVec2>,
|
||||
Shear: Node<(), Output = DVec2>,
|
||||
{
|
||||
pub fn new(translate_node: Translation, rotate_node: Rotation, scale_node: Scale, shear_node: Shear) -> Self {
|
||||
Self {
|
||||
translate_node,
|
||||
rotate_node,
|
||||
scale_node,
|
||||
shear_node,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Translation, Rotation, Scale, Shear> Node<Subpath> for TransformSubpathNode<Translation, Rotation, Scale, Shear>
|
||||
where
|
||||
Translation: Node<(), Output = DVec2>,
|
||||
Rotation: Node<(), Output = f64>,
|
||||
Scale: Node<(), Output = DVec2>,
|
||||
Shear: Node<(), Output = DVec2>,
|
||||
{
|
||||
type Output = Subpath;
|
||||
fn eval(self, mut subpath: Subpath) -> Subpath {
|
||||
let translate = self.translate_node.eval(());
|
||||
let rotate = self.rotate_node.eval(());
|
||||
let scale = self.scale_node.eval(());
|
||||
let shear = self.shear_node.eval(());
|
||||
|
||||
let (sin, cos) = rotate.sin_cos();
|
||||
|
||||
subpath.apply_affine(DAffine2::from_cols_array(&[scale.x + cos, shear.y + sin, shear.x - sin, scale.y + cos, translate.x, translate.y]));
|
||||
subpath
|
||||
}
|
||||
}
|
||||
impl<Translation, Rotation, Scale, Shear> Node<Subpath> for &TransformSubpathNode<Translation, Rotation, Scale, Shear>
|
||||
where
|
||||
Translation: Node<(), Output = DVec2> + Copy,
|
||||
Rotation: Node<(), Output = f64> + Copy,
|
||||
Scale: Node<(), Output = DVec2> + Copy,
|
||||
Shear: Node<(), Output = DVec2> + Copy,
|
||||
{
|
||||
type Output = Subpath;
|
||||
fn eval(self, mut subpath: Subpath) -> Subpath {
|
||||
let translate = self.translate_node.eval(());
|
||||
let rotate = self.rotate_node.eval(());
|
||||
let scale = self.scale_node.eval(());
|
||||
let shear = self.shear_node.eval(());
|
||||
|
||||
let (sin, cos) = rotate.sin_cos();
|
||||
|
||||
subpath.apply_affine(DAffine2::from_cols_array(&[scale.x + cos, shear.y + sin, shear.x - sin, scale.y + cos, translate.x, translate.y]));
|
||||
subpath
|
||||
}
|
||||
}
|
|
@ -1,176 +0,0 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
/// Brief description: A vec that allows indexing elements by both index and an assigned unique ID
|
||||
/// Goals of this Data Structure:
|
||||
/// - Drop-in replacement for a Vec.
|
||||
/// - Provide an auto-assigned Unique ID per element upon insertion.
|
||||
/// - Add elements to the start or end.
|
||||
/// - Insert element by Unique ID. Insert directly after an existing element by its Unique ID.
|
||||
/// - Access data by providing Unique ID.
|
||||
/// - Maintain ordering among the elements.
|
||||
/// - Remove elements without changing Unique IDs.
|
||||
/// This data structure is somewhat similar to a linked list in terms of invariants.
|
||||
/// The downside is that currently it requires a lot of iteration.
|
||||
|
||||
type ElementId = u64;
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct IdBackedVec<T> {
|
||||
/// Contained elements
|
||||
elements: Vec<T>,
|
||||
/// The IDs of the [Elements] contained within this
|
||||
element_ids: Vec<ElementId>,
|
||||
/// The ID that will be assigned to the next element that is added to this
|
||||
next_id: ElementId,
|
||||
}
|
||||
|
||||
impl<T> IdBackedVec<T> {
|
||||
/// Creates a new empty vector
|
||||
pub const fn new() -> Self {
|
||||
IdBackedVec {
|
||||
elements: vec![],
|
||||
element_ids: vec![],
|
||||
next_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Push a new element to the start of the vector
|
||||
pub fn push_front(&mut self, element: T) -> Option<ElementId> {
|
||||
self.next_id += 1;
|
||||
self.elements.insert(0, element);
|
||||
self.element_ids.insert(0, self.next_id);
|
||||
Some(self.next_id)
|
||||
}
|
||||
|
||||
/// Push an element to the end of the vector
|
||||
pub fn push_end(&mut self, element: T) -> Option<ElementId> {
|
||||
self.next_id += 1;
|
||||
self.elements.push(element);
|
||||
self.element_ids.push(self.next_id);
|
||||
Some(self.next_id)
|
||||
}
|
||||
|
||||
/// Insert an element adjacent to the given ID
|
||||
pub fn insert(&mut self, element: T, id: ElementId) -> Option<ElementId> {
|
||||
if let Some(index) = self.index_from_id(id) {
|
||||
self.next_id += 1;
|
||||
self.elements.insert(index, element);
|
||||
self.element_ids.insert(index, self.next_id);
|
||||
return Some(self.next_id);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Push an element to the end of the vector
|
||||
/// Overridden from Vec, so adding values without creating an id cannot occur
|
||||
pub fn push(&mut self, element: T) -> Option<ElementId> {
|
||||
self.push_end(element)
|
||||
}
|
||||
|
||||
/// Add a range of elements of elements to the end of this vector
|
||||
pub fn push_range<I>(&mut self, elements: I) -> Vec<ElementId>
|
||||
where
|
||||
I: IntoIterator<Item = T>,
|
||||
{
|
||||
let mut ids = vec![];
|
||||
for element in elements {
|
||||
if let Some(id) = self.push_end(element) {
|
||||
ids.push(id);
|
||||
}
|
||||
}
|
||||
ids
|
||||
}
|
||||
|
||||
/// Remove an element with a given element ID from the within this container.
|
||||
/// This operation will return false if the element ID is not found.
|
||||
/// Preserve unique ID lookup by using swap end and updating hashmap
|
||||
pub fn remove(&mut self, to_remove_id: ElementId) -> Option<T> {
|
||||
if let Some(index) = self.index_from_id(to_remove_id) {
|
||||
self.element_ids.remove(index);
|
||||
return Some(self.elements.remove(index));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Get a single element with a given element ID from the within this container.
|
||||
pub fn by_id(&self, id: ElementId) -> Option<&T> {
|
||||
let index = self.index_from_id(id)?;
|
||||
Some(&self.elements[index])
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a single element with a given element ID from the within this container.
|
||||
pub fn by_id_mut(&mut self, id: ElementId) -> Option<&mut T> {
|
||||
let index = self.index_from_id(id)?;
|
||||
Some(&mut self.elements[index])
|
||||
}
|
||||
|
||||
/// Get an element based on its index
|
||||
pub fn by_index(&self, index: usize) -> Option<&T> {
|
||||
self.elements.get(index)
|
||||
}
|
||||
|
||||
/// Get a mutable element based on its index
|
||||
pub fn by_index_mut(&mut self, index: usize) -> Option<&mut T> {
|
||||
self.elements.get_mut(index)
|
||||
}
|
||||
|
||||
/// Clear the elements and unique ids
|
||||
pub fn clear(&mut self) {
|
||||
self.elements.clear();
|
||||
self.element_ids.clear();
|
||||
}
|
||||
|
||||
/// Enumerate the ids and elements in this container `(&ElementId, &T)`
|
||||
pub fn enumerate(&self) -> std::iter::Zip<core::slice::Iter<u64>, core::slice::Iter<T>> {
|
||||
self.element_ids.iter().zip(self.elements.iter())
|
||||
}
|
||||
|
||||
/// Mutably Enumerate the ids and elements in this container `(&ElementId, &mut T)`
|
||||
pub fn enumerate_mut(&mut self) -> impl Iterator<Item = (&ElementId, &mut T)> {
|
||||
self.element_ids.iter().zip(self.elements.iter_mut())
|
||||
}
|
||||
|
||||
/// If this container contains an element with the given ID
|
||||
pub fn contains(&self, id: ElementId) -> bool {
|
||||
self.element_ids.contains(&id)
|
||||
}
|
||||
|
||||
/// Get the index of an element with the given ID
|
||||
pub fn index_from_id(&self, element_id: ElementId) -> Option<usize> {
|
||||
// Though this is a linear traversal, it is still likely faster than using a hashmap
|
||||
self.element_ids.iter().position(|&id| id == element_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for IdBackedVec<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows for usage of UniqueElements as a Vec<T>
|
||||
impl<T> Deref for IdBackedVec<T> {
|
||||
type Target = [T];
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.elements
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Consider removing this, it could allow for ElementIds and Elements to get out of sync
|
||||
/// Allows for mutable usage of UniqueElements as a Vec<T>
|
||||
impl<T> DerefMut for IdBackedVec<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.elements
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows use with iterators
|
||||
/// Also allows constructing UniqueElements with collect
|
||||
impl<A> FromIterator<A> for IdBackedVec<A> {
|
||||
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
|
||||
let mut new = IdBackedVec::default();
|
||||
// Add to the end of the existing elements
|
||||
new.push_range(iter);
|
||||
new
|
||||
}
|
||||
}
|
|
@ -1,304 +0,0 @@
|
|||
use super::consts::ManipulatorType;
|
||||
use super::manipulator_point::ManipulatorPoint;
|
||||
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// [ManipulatorGroup] is used to represent an anchor point + handles on the path that can be moved.
|
||||
/// It contains 0-2 handles that are optionally available.
|
||||
///
|
||||
/// Overview:
|
||||
/// ```text
|
||||
/// ManipulatorGroup <- Container for the anchor metadata and optional ManipulatorPoint
|
||||
/// |
|
||||
/// [Option<ManipulatorPoint>; 3] <- [0] is the anchor's draggable point (but not metadata), [1] is the
|
||||
/// / | \ InHandle's draggable point, [2] is the OutHandle's draggable point
|
||||
/// / | \
|
||||
/// "Anchor" "InHandle" "OutHandle" <- These are ManipulatorPoints and the only editable "primitive"
|
||||
/// ```
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, Default)]
|
||||
pub struct ManipulatorGroup {
|
||||
/// Editable points for the anchor and handles.
|
||||
pub points: [Option<ManipulatorPoint>; 3],
|
||||
|
||||
#[serde(skip)]
|
||||
// TODO: Remove this from Graphene, editor state should be stored in the frontend if possible.
|
||||
/// The editor state of the anchor and handles.
|
||||
pub editor_state: ManipulatorGroupEditorState,
|
||||
}
|
||||
|
||||
impl ManipulatorGroup {
|
||||
/// Create a new anchor with the given position.
|
||||
pub fn new_with_anchor(anchor_pos: DVec2) -> Self {
|
||||
Self {
|
||||
// An anchor and 2x None's which represent non-existent handles
|
||||
points: [Some(ManipulatorPoint::new(anchor_pos, ManipulatorType::Anchor)), None, None],
|
||||
editor_state: ManipulatorGroupEditorState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new anchor with the given anchor position and handles.
|
||||
pub fn new_with_handles(anchor_pos: DVec2, handle_in_pos: Option<DVec2>, handle_out_pos: Option<DVec2>) -> Self {
|
||||
Self {
|
||||
points: match (handle_in_pos, handle_out_pos) {
|
||||
(Some(pos1), Some(pos2)) => [
|
||||
Some(ManipulatorPoint::new(anchor_pos, ManipulatorType::Anchor)),
|
||||
Some(ManipulatorPoint::new(pos1, ManipulatorType::InHandle)),
|
||||
Some(ManipulatorPoint::new(pos2, ManipulatorType::OutHandle)),
|
||||
],
|
||||
(None, Some(pos2)) => [
|
||||
Some(ManipulatorPoint::new(anchor_pos, ManipulatorType::Anchor)),
|
||||
None,
|
||||
Some(ManipulatorPoint::new(pos2, ManipulatorType::OutHandle)),
|
||||
],
|
||||
(Some(pos1), None) => [
|
||||
Some(ManipulatorPoint::new(anchor_pos, ManipulatorType::Anchor)),
|
||||
Some(ManipulatorPoint::new(pos1, ManipulatorType::InHandle)),
|
||||
None,
|
||||
],
|
||||
(None, None) => [Some(ManipulatorPoint::new(anchor_pos, ManipulatorType::Anchor)), None, None],
|
||||
},
|
||||
editor_state: ManipulatorGroupEditorState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Convert into bool in subpath
|
||||
/// Create a [ManipulatorGroup] that represents a close path command.
|
||||
pub fn closed() -> Self {
|
||||
Self {
|
||||
// An anchor (the first element) being `None` indicates a ClosePath (i.e. a path end command)
|
||||
points: [None, None, None],
|
||||
editor_state: ManipulatorGroupEditorState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Answers whether this [ManipulatorGroup] represent a close shape command.
|
||||
pub fn is_close(&self) -> bool {
|
||||
self.points[ManipulatorType::Anchor].is_none() && self.points[ManipulatorType::InHandle].is_none()
|
||||
}
|
||||
|
||||
/// Finds the closest [ManipulatorPoint] owned by this [ManipulatorGroup]. This may return the anchor or either handle.
|
||||
pub fn closest_point(&self, transform_space: &DAffine2, target: glam::DVec2) -> usize {
|
||||
let mut closest_index: usize = 0;
|
||||
let mut closest_distance_squared: f64 = f64::MAX; // Not ideal
|
||||
for (index, point) in self.points.iter().enumerate() {
|
||||
if let Some(point) = point {
|
||||
let distance_squared = transform_space.transform_point2(point.position).distance_squared(target);
|
||||
if distance_squared < closest_distance_squared {
|
||||
closest_distance_squared = distance_squared;
|
||||
closest_index = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
closest_index
|
||||
}
|
||||
|
||||
/// Move the selected points by the provided transform.
|
||||
pub fn move_selected_points(&mut self, delta: DVec2) {
|
||||
let mirror_angle = self.editor_state.mirror_angle_between_handles;
|
||||
let mirror_distance = self.editor_state.mirror_distance_between_handles;
|
||||
|
||||
// Move the point absolutely or relatively depending on if the point is under the cursor (the last selected point)
|
||||
let move_point = |point: &mut ManipulatorPoint, delta: DVec2| {
|
||||
point.position += delta;
|
||||
assert!(point.position.is_finite(), "Point is not finite!")
|
||||
};
|
||||
|
||||
// Find the correctly mirrored handle position based on mirroring settings
|
||||
let move_symmetrical_handle = |position: DVec2, opposing_handle: Option<&mut ManipulatorPoint>, center: DVec2| {
|
||||
// Early out for cases where we can't mirror
|
||||
if !mirror_angle || opposing_handle.is_none() {
|
||||
return;
|
||||
}
|
||||
let opposing_handle = opposing_handle.unwrap();
|
||||
|
||||
// Keep rotational similarity, but distance variable
|
||||
let radius = if mirror_distance { center.distance(position) } else { center.distance(opposing_handle.position) };
|
||||
|
||||
if let Some(offset) = (position - center).try_normalize() {
|
||||
opposing_handle.position = center - offset * radius;
|
||||
assert!(opposing_handle.position.is_finite(), "Opposing handle not finite!")
|
||||
}
|
||||
};
|
||||
|
||||
// If no points are selected, why are we here at all?
|
||||
if !self.any_points_selected() {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the anchor is selected, ignore any handle mirroring/dragging and drag all points
|
||||
if self.is_anchor_selected() {
|
||||
for point in self.points_mut() {
|
||||
move_point(point, delta);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If the anchor isn't selected, but both handles are, drag only handles
|
||||
if self.both_handles_selected() {
|
||||
for point in self.selected_handles_mut() {
|
||||
move_point(point, delta);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If the anchor isn't selected, and only one handle is selected
|
||||
// Drag the single handle
|
||||
let reflect_center = self.points[ManipulatorType::Anchor].as_ref().unwrap().position;
|
||||
let selected_handle = self.selected_handles_mut().next().unwrap();
|
||||
move_point(selected_handle, delta);
|
||||
|
||||
// Move the opposing handle symmetrically if our mirroring flags allow
|
||||
let selected_handle = &selected_handle.clone();
|
||||
let opposing_handle = self.opposing_handle_mut(selected_handle);
|
||||
move_symmetrical_handle(selected_handle.position, opposing_handle, reflect_center);
|
||||
}
|
||||
|
||||
/// Delete any [ManipulatorPoint] that are selected, this includes handles or the anchor.
|
||||
pub fn delete_selected(&mut self) {
|
||||
for point_option in self.points.iter_mut() {
|
||||
if let Some(point) = point_option {
|
||||
if point.editor_state.is_selected {
|
||||
*point_option = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if any points in this [ManipulatorGroup] are selected.
|
||||
pub fn any_points_selected(&self) -> bool {
|
||||
self.points.iter().flatten().any(|point| point.editor_state.is_selected)
|
||||
}
|
||||
|
||||
/// Returns true if the anchor point is selected.
|
||||
pub fn is_anchor_selected(&self) -> bool {
|
||||
if let Some(anchor) = &self.points[0] {
|
||||
anchor.editor_state.is_selected
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines if the two handle points are selected.
|
||||
pub fn both_handles_selected(&self) -> bool {
|
||||
self.points.iter().skip(1).flatten().filter(|pnt| pnt.editor_state.is_selected).count() == 2
|
||||
}
|
||||
|
||||
/// Set a point, given its [ManipulatorType] enum integer ID, to a chosen selected state.
|
||||
pub fn select_point(&mut self, point_id: usize, selected: bool) -> Option<&mut ManipulatorPoint> {
|
||||
if let Some(point) = self.points[point_id].as_mut() {
|
||||
point.set_selected(selected);
|
||||
}
|
||||
self.points[point_id].as_mut()
|
||||
}
|
||||
|
||||
/// Clear the selected points for this [ManipulatorGroup].
|
||||
pub fn clear_selected_points(&mut self) {
|
||||
for point in self.points.iter_mut().flatten() {
|
||||
point.set_selected(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides the points in this [ManipulatorGroup].
|
||||
pub fn points(&self) -> impl Iterator<Item = &ManipulatorPoint> {
|
||||
self.points.iter().flatten()
|
||||
}
|
||||
|
||||
/// Provides the points in this [ManipulatorGroup] as mutable.
|
||||
pub fn points_mut(&mut self) -> impl Iterator<Item = &mut ManipulatorPoint> {
|
||||
self.points.iter_mut().flatten()
|
||||
}
|
||||
|
||||
/// Provides the selected points in this [ManipulatorGroup].
|
||||
pub fn selected_points(&self) -> impl Iterator<Item = &ManipulatorPoint> {
|
||||
self.points.iter().flatten().filter(|pnt| pnt.editor_state.is_selected)
|
||||
}
|
||||
|
||||
/// Provides mutable selected points in this [ManipulatorGroup].
|
||||
pub fn selected_points_mut(&mut self) -> impl Iterator<Item = &mut ManipulatorPoint> {
|
||||
self.points.iter_mut().flatten().filter(|pnt| pnt.editor_state.is_selected)
|
||||
}
|
||||
|
||||
/// Provides the selected handles attached to this [ManipulatorGroup].
|
||||
pub fn selected_handles(&self) -> impl Iterator<Item = &ManipulatorPoint> {
|
||||
self.points.iter().skip(1).flatten().filter(|pnt| pnt.editor_state.is_selected)
|
||||
}
|
||||
|
||||
/// Provides the mutable selected handles attached to this [ManipulatorGroup].
|
||||
pub fn selected_handles_mut(&mut self) -> impl Iterator<Item = &mut ManipulatorPoint> {
|
||||
self.points.iter_mut().skip(1).flatten().filter(|pnt| pnt.editor_state.is_selected)
|
||||
}
|
||||
|
||||
/// Angle between handles, in radians.
|
||||
pub fn angle_between_handles(&self) -> f64 {
|
||||
if let [Some(a1), Some(h1), Some(h2)] = &self.points {
|
||||
(a1.position - h1.position).angle_between(a1.position - h2.position)
|
||||
} else {
|
||||
0.
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the opposing handle to the handle provided.
|
||||
/// Returns [None] if the provided handle is of type [ManipulatorType::Anchor].
|
||||
/// Returns [None] if the opposing handle doesn't exist.
|
||||
pub fn opposing_handle(&self, handle: &ManipulatorPoint) -> Option<&ManipulatorPoint> {
|
||||
if handle.manipulator_type == ManipulatorType::Anchor {
|
||||
return None;
|
||||
}
|
||||
self.points[handle.manipulator_type.opposite_handle()].as_ref()
|
||||
}
|
||||
|
||||
/// Returns the opposing handle to the handle provided, mutable.
|
||||
/// Returns [None] if the provided handle is of type [ManipulatorType::Anchor].
|
||||
/// Returns [None] if the opposing handle doesn't exist.
|
||||
pub fn opposing_handle_mut(&mut self, handle: &ManipulatorPoint) -> Option<&mut ManipulatorPoint> {
|
||||
if handle.manipulator_type == ManipulatorType::Anchor {
|
||||
return None;
|
||||
}
|
||||
self.points[handle.manipulator_type.opposite_handle()].as_mut()
|
||||
}
|
||||
|
||||
/// Set the mirroring state
|
||||
pub fn toggle_mirroring(&mut self, toggle_distance: bool, toggle_angle: bool) {
|
||||
if toggle_distance {
|
||||
self.editor_state.mirror_distance_between_handles = !self.editor_state.mirror_distance_between_handles;
|
||||
}
|
||||
if toggle_angle {
|
||||
self.editor_state.mirror_angle_between_handles = !self.editor_state.mirror_angle_between_handles;
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to more easily set position of [ManipulatorPoints]
|
||||
pub fn set_point_position(&mut self, point_index: usize, position: DVec2) {
|
||||
assert!(position.is_finite(), "Tried to set_point_position to non finite");
|
||||
if let Some(point) = &mut self.points[point_index] {
|
||||
point.position = position;
|
||||
} else {
|
||||
self.points[point_index] = Some(ManipulatorPoint::new(position, ManipulatorType::from_index(point_index)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply an affine transformation the points
|
||||
pub fn transform(&mut self, transform: &DAffine2) {
|
||||
for point in self.points_mut() {
|
||||
point.transform(transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ManipulatorGroupEditorState {
|
||||
// Whether the angle between the handles should be maintained
|
||||
pub mirror_angle_between_handles: bool,
|
||||
// Whether the distance between the handles should be equidistant to the anchor
|
||||
pub mirror_distance_between_handles: bool,
|
||||
}
|
||||
|
||||
impl Default for ManipulatorGroupEditorState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mirror_angle_between_handles: true,
|
||||
mirror_distance_between_handles: false,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
use super::consts::ManipulatorType;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// [ManipulatorPoint] represents any editable Bezier point, either an anchor or handle
|
||||
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ManipulatorPoint {
|
||||
/// The sibling element if this is a handle
|
||||
pub position: glam::DVec2,
|
||||
/// The type of manipulator this point is
|
||||
pub manipulator_type: ManipulatorType,
|
||||
|
||||
#[serde(skip)]
|
||||
/// The state specific to the editor
|
||||
// TODO Remove this from Graphene, editor state should be stored in the frontend if possible.
|
||||
pub editor_state: ManipulatorPointEditorState,
|
||||
}
|
||||
|
||||
impl Default for ManipulatorPoint {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
position: DVec2::ZERO,
|
||||
manipulator_type: ManipulatorType::Anchor,
|
||||
editor_state: ManipulatorPointEditorState::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ManipulatorPoint {
|
||||
/// Initialize a new [ManipulatorPoint].
|
||||
pub fn new(position: glam::DVec2, manipulator_type: ManipulatorType) -> Self {
|
||||
assert!(position.is_finite(), "tried to create point with non finite position");
|
||||
Self {
|
||||
position,
|
||||
manipulator_type,
|
||||
editor_state: ManipulatorPointEditorState::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets this [ManipulatorPoint] to a chosen selection state.
|
||||
pub fn set_selected(&mut self, selected: bool) {
|
||||
self.editor_state.is_selected = selected;
|
||||
}
|
||||
|
||||
/// Whether this [ManipulatorPoint] is currently selected.
|
||||
pub fn is_selected(&self) -> bool {
|
||||
self.editor_state.is_selected
|
||||
}
|
||||
|
||||
/// Apply given transform to this point
|
||||
pub fn transform(&mut self, delta: &DAffine2) {
|
||||
self.position = delta.transform_point2(self.position);
|
||||
assert!(self.position.is_finite(), "tried to transform point to non finite position");
|
||||
}
|
||||
|
||||
/// Move by a delta amount
|
||||
pub fn move_by(&mut self, delta: &DVec2) {
|
||||
self.position += *delta;
|
||||
assert!(self.position.is_finite(), "tried to move point to non finite position");
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub struct ManipulatorPointEditorState {
|
||||
/// Whether or not this manipulator point can be selected.
|
||||
pub can_be_selected: bool,
|
||||
/// Whether or not this manipulator point is currently selected.
|
||||
pub is_selected: bool,
|
||||
}
|
||||
|
||||
impl Default for ManipulatorPointEditorState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
can_be_selected: true,
|
||||
is_selected: false,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
pub mod consts;
|
||||
pub mod generator_nodes;
|
||||
pub mod id_vec;
|
||||
pub mod manipulator_group;
|
||||
pub mod manipulator_point;
|
||||
pub mod subpath;
|
|
@ -1,634 +0,0 @@
|
|||
use super::consts::ManipulatorType;
|
||||
use super::id_vec::IdBackedVec;
|
||||
use super::manipulator_group::ManipulatorGroup;
|
||||
use super::manipulator_point::ManipulatorPoint;
|
||||
|
||||
use dyn_any::{DynAny, StaticType};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use kurbo::{BezPath, PathEl, Shape};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// [Subpath] represents a single vector path, containing many [ManipulatorGroups].
|
||||
/// For each closed shape we keep a [Subpath] which contains the [ManipulatorGroup]s (handles and anchors) that define that shape.
|
||||
// TODO Add "closed" bool to subpath
|
||||
#[derive(PartialEq, Clone, Debug, Default, Serialize, Deserialize, DynAny)]
|
||||
pub struct Subpath(IdBackedVec<ManipulatorGroup>);
|
||||
|
||||
impl Subpath {
|
||||
// ** INITIALIZATION **
|
||||
|
||||
/// Create a new [Subpath] with no [ManipulatorGroup]s.
|
||||
pub const fn new() -> Self {
|
||||
Subpath(IdBackedVec::new())
|
||||
}
|
||||
|
||||
/// Construct a [Subpath] from a point iterator
|
||||
pub fn from_points(points: impl Iterator<Item = DVec2>, closed: bool) -> Self {
|
||||
let manipulator_groups = points.map(ManipulatorGroup::new_with_anchor);
|
||||
|
||||
let mut p_line = Subpath(IdBackedVec::default());
|
||||
|
||||
p_line.0.push_range(manipulator_groups);
|
||||
if closed {
|
||||
p_line.0.push(ManipulatorGroup::closed());
|
||||
}
|
||||
|
||||
p_line
|
||||
}
|
||||
|
||||
/// Create a new [Subpath] from a [kurbo Shape](Shape).
|
||||
/// This exists to smooth the transition away from Kurbo
|
||||
pub fn from_kurbo_shape<T: Shape>(shape: &T) -> Self {
|
||||
shape.path_elements(0.1).into()
|
||||
}
|
||||
|
||||
// ** PRIMITIVE CONSTRUCTION **
|
||||
|
||||
/// constructs a rectangle with `p1` as the lower left and `p2` as the top right
|
||||
pub fn new_rect(p1: DVec2, p2: DVec2) -> Self {
|
||||
Subpath(
|
||||
vec![
|
||||
ManipulatorGroup::new_with_anchor(p1),
|
||||
ManipulatorGroup::new_with_anchor(DVec2::new(p1.x, p2.y)),
|
||||
ManipulatorGroup::new_with_anchor(p2),
|
||||
ManipulatorGroup::new_with_anchor(DVec2::new(p2.x, p1.y)),
|
||||
ManipulatorGroup::closed(),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_ellipse(p1: DVec2, p2: DVec2) -> Self {
|
||||
let x_height = DVec2::new((p2.x - p1.x).abs(), 0.);
|
||||
let y_height = DVec2::new(0., (p2.y - p1.y).abs());
|
||||
let center = (p1 + p2) * 0.5;
|
||||
let top = center + y_height * 0.5;
|
||||
let bottom = center - y_height * 0.5;
|
||||
let left = center + x_height * 0.5;
|
||||
let right = center - x_height * 0.5;
|
||||
|
||||
// Constant explained here https://stackoverflow.com/a/27863181
|
||||
let curve_constant = 0.55228_3;
|
||||
let handle_offset_x = x_height * curve_constant * 0.5;
|
||||
let handle_offset_y = y_height * curve_constant * 0.5;
|
||||
|
||||
Subpath(
|
||||
vec![
|
||||
ManipulatorGroup::new_with_handles(top, Some(top + handle_offset_x), Some(top - handle_offset_x)),
|
||||
ManipulatorGroup::new_with_handles(right, Some(right + handle_offset_y), Some(right - handle_offset_y)),
|
||||
ManipulatorGroup::new_with_handles(bottom, Some(bottom - handle_offset_x), Some(bottom + handle_offset_x)),
|
||||
ManipulatorGroup::new_with_handles(left, Some(left - handle_offset_y), Some(left + handle_offset_y)),
|
||||
ManipulatorGroup::closed(),
|
||||
]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// constructs an ngon
|
||||
/// `radius` is the distance from the `center` to any vertex, or the radius of the circle the ngon may be inscribed inside
|
||||
/// `sides` is the number of sides
|
||||
pub fn new_ngon(center: DVec2, sides: u64, radius: f64) -> Self {
|
||||
let mut manipulator_groups = vec![];
|
||||
for i in 0..sides {
|
||||
let angle = (i as f64) * std::f64::consts::TAU / (sides as f64);
|
||||
let center = center + DVec2::ONE * radius;
|
||||
let position = ManipulatorGroup::new_with_anchor(DVec2::new(center.x + radius * f64::cos(angle), center.y + radius * f64::sin(angle)) * 0.5);
|
||||
|
||||
manipulator_groups.push(position);
|
||||
}
|
||||
manipulator_groups.push(ManipulatorGroup::closed());
|
||||
|
||||
Subpath(manipulator_groups.into_iter().collect())
|
||||
}
|
||||
|
||||
/// Constructs a line from `p1` to `p2`
|
||||
pub fn new_line(p1: DVec2, p2: DVec2) -> Self {
|
||||
Subpath(vec![ManipulatorGroup::new_with_anchor(p1), ManipulatorGroup::new_with_anchor(p2)].into_iter().collect())
|
||||
}
|
||||
|
||||
/// Constructs a set of lines from `p1` to `pN`
|
||||
pub fn new_poly_line<T: Into<glam::DVec2>>(points: Vec<T>) -> Self {
|
||||
let manipulator_groups = points.into_iter().map(|point| ManipulatorGroup::new_with_anchor(point.into()));
|
||||
let mut p_line = Subpath(IdBackedVec::default());
|
||||
p_line.0.push_range(manipulator_groups);
|
||||
p_line
|
||||
}
|
||||
|
||||
pub fn new_spline<T: Into<glam::DVec2>>(points: Vec<T>) -> Self {
|
||||
let mut new = Self::default();
|
||||
// shadow `points`
|
||||
let points: Vec<DVec2> = points.into_iter().map(Into::<glam::DVec2>::into).collect();
|
||||
|
||||
// Number of points = number of points to find handles for
|
||||
let n = points.len();
|
||||
|
||||
// matrix coefficients a, b and c (see https://mathworld.wolfram.com/CubicSpline.html)
|
||||
// because the 'a' coefficients are all 1 they need not be stored
|
||||
// this algorithm does a variation of the above algorithm.
|
||||
// Instead of using the traditional cubic: a + bt + ct^2 + dt^3, we use the bezier cubic.
|
||||
|
||||
let mut b = vec![DVec2::new(4.0, 4.0); n];
|
||||
b[0] = DVec2::new(2.0, 2.0);
|
||||
b[n - 1] = DVec2::new(2.0, 2.0);
|
||||
|
||||
let mut c = vec![DVec2::new(1.0, 1.0); n];
|
||||
|
||||
// 'd' is the the second point in a cubic bezier, which is what we solve for
|
||||
let mut d = vec![DVec2::ZERO; n];
|
||||
|
||||
d[0] = DVec2::new(2.0 * points[1].x + points[0].x, 2.0 * points[1].y + points[0].y);
|
||||
d[n - 1] = DVec2::new(3.0 * points[n - 1].x, 3.0 * points[n - 1].y);
|
||||
for idx in 1..(n - 1) {
|
||||
d[idx] = DVec2::new(4.0 * points[idx].x + 2.0 * points[idx + 1].x, 4.0 * points[idx].y + 2.0 * points[idx + 1].y);
|
||||
}
|
||||
|
||||
// Solve with Thomas algorithm (see https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm)
|
||||
// do row operations to eliminate `a` coefficients
|
||||
c[0] /= -b[0];
|
||||
d[0] /= -b[0];
|
||||
#[allow(clippy::assign_op_pattern)]
|
||||
for i in 1..n {
|
||||
b[i] += c[i - 1];
|
||||
// for some reason the below line makes the borrow checker mad
|
||||
//d[i] += d[i-1]
|
||||
d[i] = d[i] + d[i - 1];
|
||||
c[i] /= -b[i];
|
||||
d[i] /= -b[i];
|
||||
}
|
||||
|
||||
// at this point b[i] == -a[i + 1], a[i] == 0,
|
||||
// do row operations to eliminate 'c' coefficients and solve
|
||||
d[n - 1] *= -1.0;
|
||||
#[allow(clippy::assign_op_pattern)]
|
||||
for i in (0..n - 1).rev() {
|
||||
d[i] = d[i] - (c[i] * d[i + 1]);
|
||||
d[i] *= -1.0; //d[i] /= b[i]
|
||||
}
|
||||
|
||||
// given the second point in the n'th cubic bezier, the third point is given by 2 * points[n+1] - b[n+1].
|
||||
// to find 'handle1_pos' for the n'th point we need the n-1 cubic bezier
|
||||
new.0.push_end(ManipulatorGroup::new_with_handles(points[0], None, Some(d[0])));
|
||||
for i in 1..n - 1 {
|
||||
new.0.push_end(ManipulatorGroup::new_with_handles(points[i], Some(2.0 * points[i] - d[i]), Some(d[i])));
|
||||
}
|
||||
new.0.push_end(ManipulatorGroup::new_with_handles(points[n - 1], Some(2.0 * points[n - 1] - d[n - 1]), None));
|
||||
|
||||
new
|
||||
}
|
||||
|
||||
/// Move the selected points by the delta vector
|
||||
pub fn move_selected(&mut self, delta: DVec2) {
|
||||
self.selected_manipulator_groups_any_points_mut()
|
||||
.for_each(|manipulator_group| manipulator_group.move_selected_points(delta));
|
||||
}
|
||||
|
||||
/// Delete the selected points from the [Subpath]
|
||||
pub fn delete_selected(&mut self) {
|
||||
let mut ids_to_delete: Vec<u64> = vec![];
|
||||
for (id, manipulator_group) in self.manipulator_groups_mut().enumerate_mut() {
|
||||
if manipulator_group.is_anchor_selected() {
|
||||
ids_to_delete.push(*id);
|
||||
} else {
|
||||
manipulator_group.delete_selected();
|
||||
}
|
||||
}
|
||||
|
||||
for id in ids_to_delete {
|
||||
self.manipulator_groups_mut().remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply a transformation to all of the Subpath points
|
||||
pub fn apply_affine(&mut self, affine: DAffine2) {
|
||||
for manipulator_group in self.manipulator_groups_mut().iter_mut() {
|
||||
manipulator_group.transform(&affine);
|
||||
}
|
||||
}
|
||||
|
||||
// ** SELECTION OF POINTS **
|
||||
|
||||
/// Set a single point to a chosen selection state by providing `(manipulator group ID, manipulator type)`.
|
||||
pub fn select_point(&mut self, point: (u64, ManipulatorType), selected: bool) -> Option<&mut ManipulatorGroup> {
|
||||
let (manipulator_group_id, point_id) = point;
|
||||
if let Some(manipulator_group) = self.manipulator_groups_mut().by_id_mut(manipulator_group_id) {
|
||||
manipulator_group.select_point(point_id as usize, selected);
|
||||
|
||||
Some(manipulator_group)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Set points in the [Subpath] to a chosen selection state, given by `(manipulator group ID, manipulator type)`.
|
||||
pub fn select_points(&mut self, points: &[(u64, ManipulatorType)], selected: bool) {
|
||||
points.iter().for_each(|point| {
|
||||
self.select_point(*point, selected);
|
||||
});
|
||||
}
|
||||
|
||||
/// Select all the anchors in this shape
|
||||
pub fn select_all_anchors(&mut self) {
|
||||
for manipulator_group in self.manipulator_groups_mut().iter_mut() {
|
||||
manipulator_group.select_point(ManipulatorType::Anchor as usize, true);
|
||||
}
|
||||
}
|
||||
|
||||
/// Select an anchor by index
|
||||
pub fn select_anchor_by_index(&mut self, manipulator_group_index: usize) -> Option<&mut ManipulatorGroup> {
|
||||
if let Some(manipulator_group) = self.manipulator_groups_mut().by_index_mut(manipulator_group_index) {
|
||||
manipulator_group.select_point(ManipulatorType::Anchor as usize, true);
|
||||
|
||||
Some(manipulator_group)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// The last anchor in the shape
|
||||
pub fn select_last_anchor(&mut self) -> Option<&mut ManipulatorGroup> {
|
||||
if let Some(manipulator_group) = self.manipulator_groups_mut().last_mut() {
|
||||
manipulator_group.select_point(ManipulatorType::Anchor as usize, true);
|
||||
|
||||
Some(manipulator_group)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear all the selected manipulator groups, i.e., clear the selected points inside the manipulator groups
|
||||
pub fn clear_selected_manipulator_groups(&mut self) {
|
||||
for manipulator_group in self.manipulator_groups_mut().iter_mut() {
|
||||
manipulator_group.clear_selected_points();
|
||||
}
|
||||
}
|
||||
|
||||
// ** ACCESSING MANIPULATORGROUPS **
|
||||
|
||||
/// Return all the selected anchors, reference
|
||||
pub fn selected_manipulator_groups(&self) -> impl Iterator<Item = &ManipulatorGroup> {
|
||||
self.manipulator_groups().iter().filter(|manipulator_group| manipulator_group.is_anchor_selected())
|
||||
}
|
||||
|
||||
/// Return all the selected anchors, mutable
|
||||
pub fn selected_manipulator_groups_mut(&mut self) -> impl Iterator<Item = &mut ManipulatorGroup> {
|
||||
self.manipulator_groups_mut().iter_mut().filter(|manipulator_group| manipulator_group.is_anchor_selected())
|
||||
}
|
||||
|
||||
/// Return all the selected [ManipulatorPoint]s by reference
|
||||
pub fn selected_manipulator_groups_any_points(&self) -> impl Iterator<Item = &ManipulatorGroup> {
|
||||
self.manipulator_groups().iter().filter(|manipulator_group| manipulator_group.any_points_selected())
|
||||
}
|
||||
|
||||
/// Return all the selected [ManipulatorPoint]s by mutable reference
|
||||
pub fn selected_manipulator_groups_any_points_mut(&mut self) -> impl Iterator<Item = &mut ManipulatorGroup> {
|
||||
self.manipulator_groups_mut().iter_mut().filter(|manipulator_group| manipulator_group.any_points_selected())
|
||||
}
|
||||
|
||||
/// An alias for `self.0`
|
||||
pub fn manipulator_groups(&self) -> &IdBackedVec<ManipulatorGroup> {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Returns a [ManipulatorPoint] from the last [ManipulatorGroup] in the [Subpath].
|
||||
pub fn last_point(&self, control_type: ManipulatorType) -> Option<&ManipulatorPoint> {
|
||||
self.manipulator_groups().last().and_then(|manipulator_group| manipulator_group.points[control_type].as_ref())
|
||||
}
|
||||
|
||||
/// Returns a [ManipulatorPoint] from the last [ManipulatorGroup], mutably
|
||||
pub fn last_point_mut(&mut self, control_type: ManipulatorType) -> Option<&mut ManipulatorPoint> {
|
||||
self.manipulator_groups_mut().last_mut().and_then(|manipulator_group| manipulator_group.points[control_type].as_mut())
|
||||
}
|
||||
|
||||
/// Returns a [ManipulatorPoint] from the first [ManipulatorGroup]
|
||||
pub fn first_point(&self, control_type: ManipulatorType) -> Option<&ManipulatorPoint> {
|
||||
self.manipulator_groups().first().and_then(|manipulator_group| manipulator_group.points[control_type].as_ref())
|
||||
}
|
||||
|
||||
/// Returns a [ManipulatorPoint] from the first [ManipulatorGroup]
|
||||
pub fn first_point_mut(&mut self, control_type: ManipulatorType) -> Option<&mut ManipulatorPoint> {
|
||||
self.manipulator_groups_mut().first_mut().and_then(|manipulator_group| manipulator_group.points[control_type].as_mut())
|
||||
}
|
||||
|
||||
/// Should we close the shape?
|
||||
pub fn should_close_shape(&self) -> bool {
|
||||
if self.last_point(ManipulatorType::Anchor).is_none() {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.first_point(ManipulatorType::Anchor)
|
||||
.unwrap()
|
||||
.position
|
||||
.distance(self.last_point(ManipulatorType::Anchor).unwrap().position)
|
||||
< 0.001 // TODO Replace with constant, a small epsilon
|
||||
}
|
||||
|
||||
/// Close the shape if able
|
||||
pub fn close_shape(&mut self) {
|
||||
if self.should_close_shape() {
|
||||
self.manipulator_groups_mut().push_end(ManipulatorGroup::closed());
|
||||
}
|
||||
}
|
||||
|
||||
/// An alias for `self.0` mutable
|
||||
pub fn manipulator_groups_mut(&mut self) -> &mut IdBackedVec<ManipulatorGroup> {
|
||||
&mut self.0
|
||||
}
|
||||
|
||||
/// Return the bounding box of the shape.
|
||||
pub fn bounding_box(&self) -> Option<[DVec2; 2]> {
|
||||
self.bezier_iter()
|
||||
.map(|bezier| bezier.internal.bounding_box())
|
||||
.reduce(|[a_min, a_max], [b_min, b_max]| [a_min.min(b_min), a_max.max(b_max)])
|
||||
}
|
||||
|
||||
/// Generate an SVG `path` elements's `d` attribute: `<path d="...">`.
|
||||
pub fn to_svg(&mut self) -> String {
|
||||
fn write_positions(result: &mut String, values: [Option<DVec2>; 3]) {
|
||||
use std::fmt::Write;
|
||||
let count = values.into_iter().flatten().count();
|
||||
for (index, pos) in values.into_iter().flatten().enumerate() {
|
||||
write!(result, "{},{}", pos.x, pos.y).unwrap();
|
||||
if index != count - 1 {
|
||||
result.push(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut result = String::new();
|
||||
// The out position from the previous ManipulatorGroup
|
||||
let mut last_out_handle = None;
|
||||
// The values from the last moveto (for closing the path)
|
||||
let (mut first_in_handle, mut first_in_anchor) = (None, None);
|
||||
// Should the next element be a moveto?
|
||||
let mut start_new_contour = true;
|
||||
for manipulator_group in self.manipulator_groups().iter() {
|
||||
let in_handle = manipulator_group.points[ManipulatorType::InHandle].as_ref().map(|point| point.position);
|
||||
let anchor = manipulator_group.points[ManipulatorType::Anchor].as_ref().map(|point| point.position);
|
||||
let out_handle = manipulator_group.points[ManipulatorType::OutHandle].as_ref().map(|point| point.position);
|
||||
|
||||
let command = match (last_out_handle.is_some(), in_handle.is_some(), anchor.is_some()) {
|
||||
(_, _, true) if start_new_contour => 'M',
|
||||
(true, false, true) | (false, true, true) => 'Q',
|
||||
(true, true, true) => 'C',
|
||||
(false, false, true) => 'L',
|
||||
(_, false, false) => 'Z',
|
||||
_ => panic!("Invalid shape {:#?}", self),
|
||||
};
|
||||
|
||||
// Complete the last curve
|
||||
if command == 'Z' {
|
||||
if last_out_handle.is_some() && first_in_handle.is_some() {
|
||||
result.push('C');
|
||||
write_positions(&mut result, [last_out_handle, first_in_handle, first_in_anchor]);
|
||||
} else if last_out_handle.is_some() || first_in_handle.is_some() {
|
||||
result.push('Q');
|
||||
write_positions(&mut result, [last_out_handle, first_in_handle, first_in_anchor]);
|
||||
}
|
||||
result.push('Z');
|
||||
}
|
||||
// Update the last moveto position
|
||||
else if command == 'M' {
|
||||
(first_in_handle, first_in_anchor) = (in_handle, anchor);
|
||||
result.push(command);
|
||||
write_positions(&mut result, [None, None, anchor]);
|
||||
}
|
||||
// Write other path commands (line to/quadratic to/cubic to)
|
||||
else {
|
||||
result.push(command);
|
||||
write_positions(&mut result, [last_out_handle, in_handle, anchor]);
|
||||
}
|
||||
|
||||
start_new_contour = command == 'Z';
|
||||
last_out_handle = out_handle;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Convert to an iter over [`bezier_rs::Bezier`] segments.
|
||||
pub fn bezier_iter(&self) -> PathIter {
|
||||
PathIter {
|
||||
path: self.manipulator_groups().enumerate(),
|
||||
last_anchor: None,
|
||||
last_out_handle: None,
|
||||
last_id: None,
|
||||
first_in_handle: None,
|
||||
first_anchor: None,
|
||||
first_id: None,
|
||||
start_new_contour: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around [`bezier_rs::Bezier`] containing also the IDs for the [`ManipulatorGroup`]s where the points are from.
|
||||
pub struct BezierId {
|
||||
/// The internal [`bezier_rs::Bezier`].
|
||||
pub internal: bezier_rs::Bezier,
|
||||
/// The ID of the [ManipulatorGroup] of the start point and, if cubic, the start handle.
|
||||
pub start: u64,
|
||||
/// The ID of the [ManipulatorGroup] of the end point and, if cubic, the end handle.
|
||||
pub end: u64,
|
||||
/// The ID of the [ManipulatorGroup] of the handle on a quadratic (if applicable).
|
||||
pub mid: Option<u64>,
|
||||
}
|
||||
|
||||
impl BezierId {
|
||||
/// Construct a `BezierId` by encapsulating a [`bezier_rs::Bezier`] and providing the IDs of the [`ManipulatorGroup`]s the constituent points belong to.
|
||||
fn new(internal: bezier_rs::Bezier, start: u64, end: u64, mid: Option<u64>) -> Self {
|
||||
Self { internal, start, end, mid }
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over [`bezier_rs::Bezier`] segments constructable via [`Subpath::bezier_iter`].
|
||||
pub struct PathIter<'a> {
|
||||
path: std::iter::Zip<core::slice::Iter<'a, u64>, core::slice::Iter<'a, ManipulatorGroup>>,
|
||||
|
||||
last_anchor: Option<DVec2>,
|
||||
last_out_handle: Option<DVec2>,
|
||||
last_id: Option<u64>,
|
||||
|
||||
first_in_handle: Option<DVec2>,
|
||||
first_anchor: Option<DVec2>,
|
||||
first_id: Option<u64>,
|
||||
|
||||
start_new_contour: bool,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for PathIter<'a> {
|
||||
type Item = BezierId;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
use bezier_rs::Bezier;
|
||||
|
||||
let mut result = None;
|
||||
|
||||
while result.is_none() {
|
||||
let (&id, manipulator_group) = self.path.next()?;
|
||||
|
||||
let in_handle = manipulator_group.points[ManipulatorType::InHandle].as_ref().map(|point| point.position);
|
||||
let anchor = manipulator_group.points[ManipulatorType::Anchor].as_ref().map(|point| point.position);
|
||||
let out_handle = manipulator_group.points[ManipulatorType::OutHandle].as_ref().map(|point| point.position);
|
||||
|
||||
let mut start_new_contour = false;
|
||||
|
||||
// Move to
|
||||
if anchor.is_some() && self.start_new_contour {
|
||||
// Update the last moveto position
|
||||
(self.first_in_handle, self.first_anchor) = (in_handle, anchor);
|
||||
self.first_id = Some(id);
|
||||
}
|
||||
// Cubic to
|
||||
else if let (Some(p1), Some(p2), Some(p3), Some(p4), Some(last_id)) = (self.last_anchor, self.last_out_handle, in_handle, anchor, self.last_id) {
|
||||
result = Some(BezierId::new(Bezier::from_cubic_dvec2(p1, p2, p3, p4), last_id, id, None));
|
||||
}
|
||||
// Quadratic to
|
||||
else if let (Some(p1), Some(p2), Some(p3), Some(last_id)) = (self.last_anchor, self.last_out_handle.or(in_handle), anchor, self.last_id) {
|
||||
let mid = if self.last_out_handle.is_some() { last_id } else { id };
|
||||
result = Some(BezierId::new(Bezier::from_quadratic_dvec2(p1, p2, p3), last_id, id, Some(mid)));
|
||||
}
|
||||
// Line to
|
||||
else if let (Some(p1), Some(p2), Some(last_id)) = (self.last_anchor, anchor, self.last_id) {
|
||||
result = Some(BezierId::new(Bezier::from_linear_dvec2(p1, p2), last_id, id, None));
|
||||
}
|
||||
// Close path
|
||||
else if in_handle.is_none() && anchor.is_none() {
|
||||
start_new_contour = true;
|
||||
if let (Some(last_id), Some(first_id)) = (self.last_id, self.first_id) {
|
||||
// Complete the last curve
|
||||
if let (Some(p1), Some(p2), Some(p3), Some(p4)) = (self.last_anchor, self.last_out_handle, self.first_in_handle, self.first_anchor) {
|
||||
result = Some(BezierId::new(Bezier::from_cubic_dvec2(p1, p2, p3, p4), last_id, first_id, None));
|
||||
} else if let (Some(p1), Some(p2), Some(p3)) = (self.last_anchor, self.last_out_handle.or(self.first_in_handle), self.first_anchor) {
|
||||
let mid = if self.last_out_handle.is_some() { last_id } else { first_id };
|
||||
result = Some(BezierId::new(Bezier::from_quadratic_dvec2(p1, p2, p3), last_id, first_id, Some(mid)));
|
||||
} else if let (Some(p1), Some(p2)) = (self.last_anchor, self.first_anchor) {
|
||||
result = Some(BezierId::new(Bezier::from_linear_dvec2(p1, p2), last_id, first_id, None));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.start_new_contour = start_new_contour;
|
||||
self.last_out_handle = out_handle;
|
||||
self.last_anchor = anchor;
|
||||
self.last_id = Some(id);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Subpath> for BezPath {
|
||||
/// Create a [BezPath] from a [Subpath].
|
||||
fn from(subpath: &Subpath) -> Self {
|
||||
// Take manipulator groups and create path elements: line, quad or curve, or a close indicator
|
||||
let manipulator_groups_to_path_el = |first: &ManipulatorGroup, second: &ManipulatorGroup| -> (PathEl, bool) {
|
||||
match [
|
||||
&first.points[ManipulatorType::OutHandle],
|
||||
&second.points[ManipulatorType::InHandle],
|
||||
&second.points[ManipulatorType::Anchor],
|
||||
] {
|
||||
[None, None, Some(anchor)] => (PathEl::LineTo(point_to_kurbo(anchor)), false),
|
||||
[None, Some(in_handle), Some(anchor)] => (PathEl::QuadTo(point_to_kurbo(in_handle), point_to_kurbo(anchor)), false),
|
||||
[Some(out_handle), None, Some(anchor)] => (PathEl::QuadTo(point_to_kurbo(out_handle), point_to_kurbo(anchor)), false),
|
||||
[Some(out_handle), Some(in_handle), Some(anchor)] => (PathEl::CurveTo(point_to_kurbo(out_handle), point_to_kurbo(in_handle), point_to_kurbo(anchor)), false),
|
||||
[Some(out_handle), None, None] => {
|
||||
if let Some(first_anchor) = subpath.manipulator_groups().first() {
|
||||
(
|
||||
if let Some(in_handle) = &first_anchor.points[ManipulatorType::InHandle] {
|
||||
PathEl::CurveTo(
|
||||
point_to_kurbo(out_handle),
|
||||
point_to_kurbo(in_handle),
|
||||
point_to_kurbo(first_anchor.points[ManipulatorType::Anchor].as_ref().unwrap()),
|
||||
)
|
||||
} else {
|
||||
PathEl::QuadTo(point_to_kurbo(out_handle), point_to_kurbo(first_anchor.points[ManipulatorType::Anchor].as_ref().unwrap()))
|
||||
},
|
||||
true,
|
||||
)
|
||||
} else {
|
||||
(PathEl::ClosePath, true)
|
||||
}
|
||||
}
|
||||
[None, None, None] => (PathEl::ClosePath, true),
|
||||
_ => panic!("Invalid path element {:#?}", subpath),
|
||||
}
|
||||
};
|
||||
|
||||
if subpath.manipulator_groups().is_empty() {
|
||||
return BezPath::new();
|
||||
}
|
||||
|
||||
let mut bez_path = vec![];
|
||||
let mut start_new_shape = true;
|
||||
|
||||
for elements in subpath.manipulator_groups().windows(2) {
|
||||
let first = &elements[0];
|
||||
let second = &elements[1];
|
||||
|
||||
// Tell kurbo cursor to move to the first anchor
|
||||
if start_new_shape {
|
||||
if let Some(anchor) = &first.points[ManipulatorType::Anchor] {
|
||||
bez_path.push(PathEl::MoveTo(point_to_kurbo(anchor)));
|
||||
}
|
||||
}
|
||||
|
||||
// Create a path element from our first and second manipulator groups in the window
|
||||
let (path_el, should_start_new_shape) = manipulator_groups_to_path_el(first, second);
|
||||
start_new_shape = should_start_new_shape;
|
||||
bez_path.push(path_el);
|
||||
if should_start_new_shape && bez_path.last().filter(|&&el| el == PathEl::ClosePath).is_none() {
|
||||
bez_path.push(PathEl::ClosePath)
|
||||
}
|
||||
}
|
||||
|
||||
BezPath::from_vec(bez_path)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Iterator<Item = PathEl>> From<T> for Subpath {
|
||||
/// Create a Subpath from a [BezPath].
|
||||
fn from(path: T) -> Self {
|
||||
let mut subpath = Subpath::new();
|
||||
let mut closed = false;
|
||||
for path_el in path {
|
||||
if closed {
|
||||
warn!("Verbs appear after the close path in a subpath. This will probably cause crashes.");
|
||||
}
|
||||
match path_el {
|
||||
PathEl::MoveTo(p) => {
|
||||
subpath.manipulator_groups_mut().push_end(ManipulatorGroup::new_with_anchor(kurbo_point_to_dvec2(p)));
|
||||
if !subpath.0.is_empty() {
|
||||
warn!("A move to path element appears part way through a subpath. This will be treated as a line to verb.");
|
||||
}
|
||||
}
|
||||
PathEl::LineTo(p) => {
|
||||
subpath.manipulator_groups_mut().push_end(ManipulatorGroup::new_with_anchor(kurbo_point_to_dvec2(p)));
|
||||
}
|
||||
PathEl::QuadTo(p0, p1) => {
|
||||
subpath.manipulator_groups_mut().push_end(ManipulatorGroup::new_with_anchor(kurbo_point_to_dvec2(p1)));
|
||||
subpath.manipulator_groups_mut().last_mut().unwrap().points[ManipulatorType::InHandle] = Some(ManipulatorPoint::new(kurbo_point_to_dvec2(p0), ManipulatorType::InHandle));
|
||||
}
|
||||
PathEl::CurveTo(p0, p1, p2) => {
|
||||
subpath.manipulator_groups_mut().last_mut().unwrap().points[ManipulatorType::OutHandle] = Some(ManipulatorPoint::new(kurbo_point_to_dvec2(p0), ManipulatorType::OutHandle));
|
||||
subpath.manipulator_groups_mut().push_end(ManipulatorGroup::new_with_anchor(kurbo_point_to_dvec2(p2)));
|
||||
subpath.manipulator_groups_mut().last_mut().unwrap().points[ManipulatorType::InHandle] = Some(ManipulatorPoint::new(kurbo_point_to_dvec2(p1), ManipulatorType::InHandle));
|
||||
}
|
||||
PathEl::ClosePath => {
|
||||
subpath.manipulator_groups_mut().push_end(ManipulatorGroup::closed());
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subpath
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn point_to_kurbo(point: &ManipulatorPoint) -> kurbo::Point {
|
||||
kurbo::Point::new(point.position.x, point.position.y)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn kurbo_point_to_dvec2(point: kurbo::Point) -> DVec2 {
|
||||
DVec2::new(point.x, point.y)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue