Add nondestructive vector editing (#1676)

* Initial vector modify node

* Initial extraction of data from monitor nodes

* Migrate to point id

* Start converting to modify node

* Non destructive spline tool (tout le reste est cassé)

* Fix unconnected modify node

* Fix freehand tool

* Pen tool

* Migrate demo art

* Select points

* Fix the demo artwork

* Fix the X and Y inputs for path tool

* G1 continous toggle

* Delete points

* Fix test

* Insert point

* Improve robustness of handles

* Fix GRS shortcuts on path

* Dragging points

* Fix build

* Preserve opposing handle lengths

* Update demo art and snapping

* Fix polygon tool

* Double click end anchor

* Improve dragging

* Fix text shifting

* Select only connected verts

* Colinear alt

* Cleanup

* Fix imports

* Improve pen tool avoiding handle placement

* Improve disolve

* Remove pivot widget from Transform node properties

* Fix demo art

* Fix bugs

* Re-save demo artwork

* Code review

* Serialize hashmap as tuple vec to enable deserialize_inputs

* Fix migrate

* Add document upgrade function to editor_api.rs

* Finalize document upgrading

* Rename to the Path node

* Remove smoothing from Freehand tool

* Upgrade demo artwork

* Propertly disable raw-rs tests

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: Adam <adamgerhant@gmail.com>
Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
James Lindsay
2024-07-05 21:42:40 +01:00
committed by GitHub
parent fd3613018a
commit 1652c713a6
96 changed files with 3343 additions and 2622 deletions

View File

@@ -65,3 +65,7 @@ rand = { workspace = true, default-features = false, features = ["std_rng"] }
[dev-dependencies]
tokio = { workspace = true, features = ["rt", "macros"] }
[lints.rust]
# the spirv target is not in the list of common cfgs so must be added manually
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_arch, values("spirv"))'] }

View File

@@ -334,8 +334,14 @@ impl GraphicElementRendered for VectorData {
fn add_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
let stroke_width = self.style.stroke().as_ref().map_or(0., crate::vector::style::Stroke::weight);
click_targets.extend(self.region_bezier_paths().map(|(_, subpath)| ClickTarget { stroke_width, subpath }));
click_targets.extend(self.stroke_bezier_paths().map(|subpath| ClickTarget { stroke_width, subpath }));
let filled = self.style.fill() != &crate::vector::style::Fill::None;
let fill = |mut subpath: bezier_rs::Subpath<_>| {
if filled {
subpath.set_closed(true);
}
subpath
};
click_targets.extend(self.stroke_bezier_paths().map(fill).map(|subpath| ClickTarget { stroke_width, subpath }));
}
fn to_usvg_node(&self) -> usvg::Node {
@@ -475,7 +481,7 @@ impl GraphicElementRendered for crate::ArtboardGroup {
}
fn contains_artboard(&self) -> bool {
self.artboards.len() > 0
!self.artboards.is_empty()
}
}

View File

@@ -581,8 +581,9 @@ fn vibrance_node(color: Color, vibrance: f64) -> Color {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum RedGreenBlue {
#[default]
Red,
Green,
Blue,
@@ -600,8 +601,9 @@ impl core::fmt::Display for RedGreenBlue {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum RedGreenBlueAlpha {
#[default]
Red,
Green,
Blue,
@@ -621,8 +623,9 @@ impl core::fmt::Display for RedGreenBlueAlpha {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum NoiseType {
#[default]
Perlin,
OpenSimplex2,
OpenSimplex2S,
@@ -662,8 +665,9 @@ impl NoiseType {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum FractalType {
#[default]
None,
FBm,
Ridged,
@@ -700,8 +704,9 @@ impl FractalType {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum CellularDistanceFunction {
#[default]
Euclidean,
EuclideanSq,
Manhattan,
@@ -732,9 +737,10 @@ impl CellularDistanceFunction {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum CellularReturnType {
CellValue,
#[default]
Nearest,
NextNearest,
Average,
@@ -773,8 +779,9 @@ impl CellularReturnType {
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum DomainWarpType {
#[default]
None,
OpenSimplex2,
OpenSimplex2Reduced,
@@ -867,8 +874,9 @@ fn channel_mixer_node(
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum RelativeAbsolute {
#[default]
Relative,
Absolute,
}
@@ -885,8 +893,9 @@ impl core::fmt::Display for RelativeAbsolute {
#[repr(C)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "std", derive(specta::Type))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DynAny)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, DynAny)]
pub enum SelectiveColorChoice {
#[default]
Reds,
Yellows,
Greens,

View File

@@ -774,7 +774,7 @@ impl Color {
/// ```
#[inline(always)]
pub fn to_rgba8_srgb(&self) -> [u8; 4] {
let gamma = self.to_gamma_srgb();
let gamma = self.to_gamma_srgb().to_gamma_srgb();
[(gamma.red * 255.) as u8, (gamma.green * 255.) as u8, (gamma.blue * 255.) as u8, (gamma.alpha * 255.) as u8]
}

View File

@@ -17,5 +17,5 @@ pub struct TextGeneratorNode<Text, FontName, Size> {
#[node_fn(TextGeneratorNode)]
fn generate_text<'a: 'input, T>(editor: EditorApi<'a, T>, text: String, font_name: Font, font_size: f64) -> crate::vector::VectorData {
let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data));
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None))
crate::vector::VectorData::from_subpaths(to_path(&text, buzz_face, font_size, None), false)
}

View File

@@ -15,7 +15,11 @@ impl Font {
Self { font_family, font_style }
}
}
impl Default for Font {
fn default() -> Self {
Self::new(crate::consts::DEFAULT_FONT_FAMILY.into(), crate::consts::DEFAULT_FONT_STYLE.into())
}
}
/// A cache of all loaded font data and preview urls along with the default font (send from `init_app` in `editor_api.rs`)
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq)]
pub struct FontCache {

View File

@@ -1,4 +1,4 @@
use crate::uuid::ManipulatorGroupId;
use crate::vector::PointId;
use bezier_rs::{ManipulatorGroup, Subpath};
@@ -7,13 +7,13 @@ use rustybuzz::ttf_parser::{GlyphId, OutlineBuilder};
use rustybuzz::{GlyphBuffer, UnicodeBuffer};
struct Builder {
current_subpath: Subpath<ManipulatorGroupId>,
other_subpaths: Vec<Subpath<ManipulatorGroupId>>,
current_subpath: Subpath<PointId>,
other_subpaths: Vec<Subpath<PointId>>,
pos: DVec2,
offset: DVec2,
ascender: f64,
scale: f64,
id: ManipulatorGroupId,
id: PointId,
}
impl Builder {
@@ -37,7 +37,7 @@ impl OutlineBuilder for Builder {
fn quad_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32) {
let [handle, anchor] = [self.point(x1, y1), self.point(x2, y2)];
self.current_subpath.last_manipulator_group_mut().unwrap().out_handle = Some(handle);
self.current_subpath.push_manipulator_group(ManipulatorGroup::new_anchor_with_id(anchor, self.id.next_id()));
self.current_subpath.push_manipulator_group(ManipulatorGroup::new_with_id(anchor, None, None, self.id.next_id()));
}
fn curve_to(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) {
@@ -80,7 +80,7 @@ fn wrap_word(line_width: Option<f64>, glyph_buffer: &GlyphBuffer, scale: f64, x_
false
}
pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, font_size: f64, line_width: Option<f64>) -> Vec<Subpath<ManipulatorGroupId>> {
pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, font_size: f64, line_width: Option<f64>) -> Vec<Subpath<PointId>> {
let buzz_face = match buzz_face {
Some(face) => face,
// Show blank layer if font has not loaded
@@ -96,7 +96,7 @@ pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, font_size: f64, li
offset: DVec2::ZERO,
ascender: (buzz_face.ascender() as f64 / buzz_face.height() as f64) * font_size / scale,
scale,
id: ManipulatorGroupId::ZERO,
id: PointId::ZERO,
};
for line in str.split('\n') {

View File

@@ -152,7 +152,7 @@ pub struct TransformNode<TransformTarget, Translation, Rotation, Scale, Shear, P
pub(crate) rotate: Rotation,
pub(crate) scale: Scale,
pub(crate) shear: Shear,
pub(crate) pivot: Pivot,
pub(crate) _pivot: Pivot,
}
#[derive(Debug, Clone, Copy, dyn_any::DynAny, PartialEq)]
@@ -165,7 +165,7 @@ pub enum RenderQuality {
Scale(f32),
/// Flip a coin to decide if the render should be available with the current quality or done at full quality
/// This should be used to gradually update the render quality of a cached node
Probabilty(f32),
Probability(f32),
/// Render at full quality
Full,
}
@@ -249,23 +249,18 @@ pub(crate) async fn transform_vector_data<Fut: Future>(
rotate: f64,
scale: DVec2,
shear: DVec2,
pivot: DVec2,
_pivot: DVec2,
) -> Fut::Output
where
Fut::Output: TransformMut,
{
// TODO: This is hack and might break for Vector data because the pivot may be incorrect
let transform = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
let modification = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
if !footprint.ignore_modifications {
let pivot_transform = DAffine2::from_translation(pivot);
let modification = pivot_transform * transform * pivot_transform.inverse();
*footprint.transform_mut() = footprint.transform() * modification;
}
let mut data = self.transform_target.eval(footprint).await;
let pivot_transform = DAffine2::from_translation(data.local_pivot(pivot));
let modification = pivot_transform * transform * pivot_transform.inverse();
let data_transform = data.transform_mut();
*data_transform = modification * (*data_transform);

View File

@@ -1,5 +1,3 @@
use dyn_any::{DynAny, StaticType};
#[derive(Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct Uuid(
#[serde(with = "u64_string")]
@@ -69,33 +67,3 @@ mod uuid_generation {
}
pub use uuid_generation::*;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ManipulatorGroupId(u64);
impl bezier_rs::Identifier for ManipulatorGroupId {
fn new() -> Self {
Self(generate_uuid())
}
}
impl ManipulatorGroupId {
pub const ZERO: ManipulatorGroupId = ManipulatorGroupId(0);
pub fn next_id(&mut self) -> Self {
let old = self.0;
self.0 += 1;
Self(old)
}
pub(crate) fn inner(self) -> u64 {
self.0
}
}
impl From<crate::vector::PointId> for ManipulatorGroupId {
fn from(value: crate::vector::PointId) -> Self {
Self(value.inner())
}
}

View File

@@ -1,5 +1,5 @@
use crate::uuid::ManipulatorGroupId;
use crate::vector::VectorData;
use super::HandleId;
use crate::vector::{PointId, VectorData};
use crate::Node;
use bezier_rs::Subpath;
@@ -28,7 +28,14 @@ fn ellipse_generator(_input: (), radius_x: f64, radius_y: f64) -> VectorData {
let radius = DVec2::new(radius_x, radius_y);
let corner1 = -radius;
let corner2 = radius;
super::VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2))
let mut ellipse = super::VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2));
let len = ellipse.segment_domain.ids().len();
for i in 0..len {
ellipse
.colinear_manipulators
.push([HandleId::end(ellipse.segment_domain.ids()[i]), HandleId::primary(ellipse.segment_domain.ids()[(i + 1) % len])]);
}
ellipse
}
#[derive(Debug, Clone, Copy)]
@@ -46,7 +53,7 @@ trait CornerRadius {
impl CornerRadius for f64 {
fn generate(self, size: DVec2, clamped: bool) -> super::VectorData {
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
super::VectorData::from_subpaths(vec![Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])])
super::VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4]))
}
}
impl CornerRadius for [f64; 4] {
@@ -66,7 +73,7 @@ impl CornerRadius for [f64; 4] {
} else {
self
};
super::VectorData::from_subpaths(vec![Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)])
super::VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius))
}
}
@@ -122,7 +129,11 @@ pub struct SplineGenerator<Positions> {
#[node_macro::node_fn(SplineGenerator)]
fn spline_generator(_input: (), positions: Vec<DVec2>) -> VectorData {
super::VectorData::from_subpath(Subpath::new_cubic_spline(positions))
let mut spline = super::VectorData::from_subpath(Subpath::new_cubic_spline(positions));
for pair in spline.segment_domain.ids().windows(2) {
spline.colinear_manipulators.push([HandleId::end(pair[0]), HandleId::primary(pair[1])]);
}
spline
}
// TODO(TrueDoctor): I removed the Arc requirement we should think about when it makes sense to use it vs making a generic value node
@@ -132,9 +143,12 @@ pub struct PathGenerator<ColinearManipulators> {
}
#[node_macro::node_fn(PathGenerator)]
fn generate_path(path_data: Vec<Subpath<ManipulatorGroupId>>, colinear_manipulators: Vec<ManipulatorGroupId>) -> super::VectorData {
let mut vector_data = super::VectorData::from_subpaths(path_data);
vector_data.colinear_manipulators = colinear_manipulators;
fn generate_path(path_data: Vec<Subpath<PointId>>, colinear_manipulators: Vec<PointId>) -> super::VectorData {
let mut vector_data = super::VectorData::from_subpaths(path_data, false);
vector_data.colinear_manipulators = colinear_manipulators
.iter()
.filter_map(|&point| super::ManipulatorPointId::Anchor(point).get_handle_pair(&vector_data))
.collect();
vector_data
}

View File

@@ -371,16 +371,18 @@ impl From<Fill> for FillChoice {
/// Enum describing the type of [Fill].
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash, specta::Type)]
#[derive(Debug, Clone, Copy, Default, PartialEq, serde::Serialize, serde::Deserialize, DynAny, Hash, specta::Type)]
pub enum FillType {
#[default]
Solid,
Gradient,
}
/// The stroke (outline) style of an SVG element.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]
pub enum LineCap {
#[default]
Butt,
Round,
Square,
@@ -397,8 +399,9 @@ impl Display for LineCap {
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type)]
pub enum LineJoin {
#[default]
Miter,
Bevel,
Round,

View File

@@ -1,13 +1,15 @@
mod attributes;
mod modification;
pub use attributes::*;
pub use modification::*;
use super::style::{PathStyle, Stroke};
use crate::Color;
use crate::{uuid::ManipulatorGroupId, AlphaBlending};
pub use attributes::*;
use crate::{AlphaBlending, Color};
use bezier_rs::ManipulatorGroup;
use dyn_any::{DynAny, StaticType};
use core::borrow::Borrow;
use glam::{DAffine2, DVec2};
/// [VectorData] is passed between nodes.
@@ -20,7 +22,7 @@ pub struct VectorData {
pub alpha_blending: AlphaBlending,
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
/// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have both `subpath` and `colinear_manipulators` inputs"` to find it).
pub colinear_manipulators: Vec<ManipulatorGroupId>,
pub colinear_manipulators: Vec<[HandleId; 2]>,
pub point_domain: PointDomain,
pub segment_domain: SegmentDomain,
@@ -54,15 +56,15 @@ impl VectorData {
}
/// Construct some new vector data from a single subpath with an identity transform and black fill.
pub fn from_subpath(subpath: bezier_rs::Subpath<ManipulatorGroupId>) -> Self {
Self::from_subpaths([subpath])
pub fn from_subpath(subpath: impl Borrow<bezier_rs::Subpath<PointId>>) -> Self {
Self::from_subpaths([subpath], false)
}
/// Push a subpath to the vector data
pub fn append_subpath<Id: bezier_rs::Identifier + Into<PointId> + Copy>(&mut self, subpath: bezier_rs::Subpath<Id>) {
for point in subpath.manipulator_groups() {
self.point_domain.push(point.id.into(), point.anchor);
}
pub fn append_subpath(&mut self, subpath: impl Borrow<bezier_rs::Subpath<PointId>>, preserve_id: bool) {
let subpath: &bezier_rs::Subpath<PointId> = subpath.borrow();
let stroke_id = StrokeId::ZERO;
let mut point_id = self.point_domain.next_id();
let handles = |a: &ManipulatorGroup<_>, b: &ManipulatorGroup<_>| match (a.out_handle, b.in_handle) {
(None, None) => bezier_rs::BezierHandles::Linear,
@@ -70,33 +72,57 @@ impl VectorData {
(Some(handle_start), Some(handle_end)) => bezier_rs::BezierHandles::Cubic { handle_start, handle_end },
};
let [mut first_seg, mut last_seg] = [None, None];
let mut segment_id = self.segment_domain.next_id();
let mut last_point = None;
let mut first_point = None;
for pair in subpath.manipulator_groups().windows(2) {
let id = SegmentId::generate();
let start = last_point.unwrap_or_else(|| {
let id = if preserve_id && !self.point_domain.ids().contains(&pair[0].id) {
pair[0].id
} else {
point_id.next_id()
};
self.point_domain.push(id, pair[0].anchor);
id
});
first_point = Some(first_point.unwrap_or(start));
let end = if preserve_id && !self.point_domain.ids().contains(&pair[1].id) {
pair[1].id
} else {
point_id.next_id()
};
self.point_domain.push(end, pair[1].anchor);
let id = segment_id.next_id();
first_seg = Some(first_seg.unwrap_or(id));
last_seg = Some(id);
self.segment_domain.push(id, pair[0].id.into(), pair[1].id.into(), handles(&pair[0], &pair[1]), StrokeId::generate());
self.segment_domain.push(id, start, end, handles(&pair[0], &pair[1]), stroke_id);
last_point = Some(end);
}
let fill_id = FillId::ZERO;
if subpath.closed() {
if let (Some(last), Some(first)) = (subpath.manipulator_groups().last(), subpath.manipulator_groups().first()) {
let id = SegmentId::generate();
if let (Some(last), Some(first), Some(first_id), Some(last_id)) = (subpath.manipulator_groups().last(), subpath.manipulator_groups().first(), first_point, last_point) {
let id = segment_id.next_id();
first_seg = Some(first_seg.unwrap_or(id));
last_seg = Some(id);
self.segment_domain.push(id, last.id.into(), first.id.into(), handles(last, first), StrokeId::generate());
self.segment_domain.push(id, last_id, first_id, handles(last, first), stroke_id);
}
if let [Some(first_seg), Some(last_seg)] = [first_seg, last_seg] {
self.region_domain.push(RegionId::generate(), first_seg..=last_seg, FillId::generate());
self.region_domain.push(self.region_domain.next_id(), first_seg..=last_seg, fill_id);
}
}
}
/// Construct some new vector data from subpaths with an identity transform and black fill.
pub fn from_subpaths(subpaths: impl IntoIterator<Item = bezier_rs::Subpath<ManipulatorGroupId>>) -> Self {
pub fn from_subpaths(subpaths: impl IntoIterator<Item = impl Borrow<bezier_rs::Subpath<PointId>>>, preserve_id: bool) -> Self {
let mut vector_data = Self::empty();
for subpath in subpaths.into_iter() {
vector_data.append_subpath(subpath);
vector_data.append_subpath(subpath, preserve_id);
}
vector_data
@@ -142,6 +168,33 @@ impl VectorData {
pub fn local_pivot(&self, normalized_pivot: DVec2) -> DVec2 {
self.transform.transform_point2(self.layerspace_pivot(normalized_pivot))
}
/// Points connected to a single segment
pub fn single_connected_points(&self) -> impl Iterator<Item = PointId> + '_ {
self.point_domain.ids().iter().copied().filter(|&point| self.segment_domain.connected_count(point) == 1)
}
/// Computes if all the connected handles are colinear for an anchor, or if that handle is colinear for a handle.
pub fn colinear(&self, point: ManipulatorPointId) -> bool {
let has_handle = |target| self.colinear_manipulators.iter().flatten().any(|&handle| handle == target);
match point {
ManipulatorPointId::Anchor(id) => {
self.segment_domain.start_connected(id).all(|segment| has_handle(HandleId::primary(segment))) && self.segment_domain.end_connected(id).all(|segment| has_handle(HandleId::end(segment)))
}
ManipulatorPointId::PrimaryHandle(segment) => has_handle(HandleId::primary(segment)),
ManipulatorPointId::EndHandle(segment) => has_handle(HandleId::end(segment)),
}
}
pub fn other_colinear_handle(&self, handle: HandleId) -> Option<HandleId> {
let pair = self.colinear_manipulators.iter().find(|pair| pair.iter().any(|&val| val == handle))?;
let other = pair.iter().copied().find(|&val| val != handle)?;
if handle.to_manipulator_point().get_anchor(self) == other.to_manipulator_point().get_anchor(self) {
Some(other)
} else {
None
}
}
}
impl Default for VectorData {
@@ -150,64 +203,192 @@ impl Default for VectorData {
}
}
/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curviture).
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ManipulatorPointId {
pub group: ManipulatorGroupId,
pub manipulator_type: SelectedType,
pub enum ManipulatorPointId {
/// A control anchor - the start or end point of a bézier.
Anchor(PointId),
/// The handle for a bézier - the first handle on a cubic and the only handle on a quadratic.
PrimaryHandle(SegmentId),
/// The end handle on a cubic bézier.
EndHandle(SegmentId),
}
impl ManipulatorPointId {
pub fn new(group: ManipulatorGroupId, manipulator_type: SelectedType) -> Self {
Self { group, manipulator_type }
/// Attempt to retrieve the manipulator position in layer space (no transformation applied).
#[must_use]
pub fn get_position(&self, vector_data: &VectorData) -> Option<DVec2> {
match self {
ManipulatorPointId::Anchor(id) => vector_data.point_domain.position_from_id(*id),
ManipulatorPointId::PrimaryHandle(id) => vector_data.segment_from_id(*id).and_then(|bezier| bezier.handle_start()),
ManipulatorPointId::EndHandle(id) => vector_data.segment_from_id(*id).and_then(|bezier| bezier.handle_end()),
}
}
/// Attempt to get a pair of handles. For an anchor this is the first to handles connected. For a handle it is self and the first opposing handle.
#[must_use]
pub fn get_handle_pair(self, vector_data: &VectorData) -> Option<[HandleId; 2]> {
match self {
ManipulatorPointId::Anchor(point) => vector_data.segment_domain.all_connected(point).take(2).collect::<Vec<_>>().try_into().ok(),
ManipulatorPointId::PrimaryHandle(segment) => {
let point = vector_data.segment_domain.segment_start_from_id(segment)?;
let current = HandleId::primary(segment);
let other = vector_data.segment_domain.all_connected(point).find(|&value| value != current);
other.map(|other| [current, other])
}
ManipulatorPointId::EndHandle(segment) => {
let point = vector_data.segment_domain.segment_end_from_id(segment)?;
let current = HandleId::end(segment);
let other = vector_data.segment_domain.all_connected(point).find(|&value| value != current);
other.map(|other| [current, other])
}
}
}
/// Attempt to find the closest anchor. If self is already an anchor then it is just self. If it is a start or end handle, then the start or end point is chosen.
#[must_use]
pub fn get_anchor(self, vector_data: &VectorData) -> Option<PointId> {
match self {
ManipulatorPointId::Anchor(point) => Some(point),
ManipulatorPointId::PrimaryHandle(segment) => vector_data.segment_domain.segment_start_from_id(segment),
ManipulatorPointId::EndHandle(segment) => vector_data.segment_domain.segment_end_from_id(segment),
}
}
/// Attempt to convert self to a [`HandleId`], returning none for an anchor.
#[must_use]
pub fn as_handle(self) -> Option<HandleId> {
match self {
ManipulatorPointId::PrimaryHandle(segment) => Some(HandleId::primary(segment)),
ManipulatorPointId::EndHandle(segment) => Some(HandleId::end(segment)),
ManipulatorPointId::Anchor(_) => None,
}
}
/// Attempt to convert self to an anchor, returning None for a handle.
#[must_use]
pub fn as_anchor(self) -> Option<PointId> {
match self {
ManipulatorPointId::Anchor(point) => Some(point),
_ => None,
}
}
}
/// The type of handle found on a bézier curve.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SelectedType {
Anchor = 1 << 0,
InHandle = 1 << 1,
OutHandle = 1 << 2,
pub enum HandleType {
/// The first handle on a cubic bézier or the only handle on a quadratic bézier.
Primary,
/// The second handle on a cubic bézier.
End,
}
impl SelectedType {
/// Get the location of the [SelectedType] in the [ManipulatorGroup]
pub fn get_position(&self, manipulator_group: &ManipulatorGroup<ManipulatorGroupId>) -> Option<DVec2> {
match self {
Self::Anchor => Some(manipulator_group.anchor),
Self::InHandle => manipulator_group.in_handle,
Self::OutHandle => manipulator_group.out_handle,
/// Represents a primary or end handle found in a particular segment.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HandleId {
pub ty: HandleType,
pub segment: SegmentId,
}
impl HandleId {
/// Construct a handle for the first handle on a cubic bézier or the only handle on a quadratic bézier.
#[must_use]
pub const fn primary(segment: SegmentId) -> Self {
Self { ty: HandleType::Primary, segment }
}
/// Construct a handle for the end handle on a cubic bézier.
#[must_use]
pub const fn end(segment: SegmentId) -> Self {
Self { ty: HandleType::End, segment }
}
/// Convert to [`ManipulatorPointId`].
#[must_use]
pub fn to_manipulator_point(self) -> ManipulatorPointId {
match self.ty {
HandleType::Primary => ManipulatorPointId::PrimaryHandle(self.segment),
HandleType::End => ManipulatorPointId::EndHandle(self.segment),
}
}
/// Get the closest [SelectedType] in the [ManipulatorGroup].
pub fn closest_widget(manipulator_group: &ManipulatorGroup<ManipulatorGroupId>, transform_space: DAffine2, target: DVec2, hide_handle_distance: f64) -> (Self, f64) {
let anchor = transform_space.transform_point2(manipulator_group.anchor);
// Skip handles under the anchor
let not_under_anchor = |&(selected_type, position): &(SelectedType, DVec2)| selected_type == Self::Anchor || position.distance_squared(anchor) > hide_handle_distance.powi(2);
let compute_distance = |selected_type: Self| {
selected_type.get_position(manipulator_group).and_then(|position| {
Some((selected_type, transform_space.transform_point2(position)))
.filter(not_under_anchor)
.map(|(selected_type, pos)| (selected_type, pos.distance_squared(target)))
})
};
[Self::Anchor, Self::InHandle, Self::OutHandle]
.into_iter()
.filter_map(compute_distance)
.min_by(|a, b| a.1.total_cmp(&b.1))
.unwrap_or((Self::Anchor, manipulator_group.anchor.distance_squared(target)))
}
/// Opposite handle
pub fn opposite(&self) -> Self {
match self {
SelectedType::Anchor => SelectedType::Anchor,
SelectedType::InHandle => SelectedType::OutHandle,
SelectedType::OutHandle => SelectedType::InHandle,
/// Set the handle's position relative to the anchor which is the start anchor for the primary handle and end anchor for the end handle.
#[must_use]
pub fn set_relative_position(self, relative_position: DVec2) -> VectorModificationType {
let Self { ty, segment } = self;
match ty {
HandleType::Primary => VectorModificationType::SetPrimaryHandle { segment, relative_position },
HandleType::End => VectorModificationType::SetEndHandle { segment, relative_position },
}
}
/// Check if handle
pub fn is_handle(self) -> bool {
self != SelectedType::Anchor
/// Convert an end handle to the primary handle and a primary handle to an end handle. Note that the new handle may not exist (e.g. for a quadratic bézier).
#[must_use]
pub fn opposite(self) -> Self {
match self.ty {
HandleType::Primary => Self::end(self.segment),
HandleType::End => Self::primary(self.segment),
}
}
}
#[cfg(test)]
fn assert_subpath_eq(generated: &Vec<bezier_rs::Subpath<PointId>>, expected: &[bezier_rs::Subpath<PointId>]) {
assert_eq!(generated.len(), expected.len());
for (generated, expected) in generated.iter().zip(expected) {
assert_eq!(generated.manipulator_groups().len(), expected.manipulator_groups().len());
assert_eq!(generated.closed(), expected.closed());
for (generated, expected) in generated.manipulator_groups().iter().zip(expected.manipulator_groups()) {
assert_eq!(generated.in_handle, expected.in_handle);
assert_eq!(generated.out_handle, expected.out_handle);
assert_eq!(generated.anchor, expected.anchor);
}
}
}
#[test]
fn construct_closed_subpath() {
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
let vector_data = VectorData::from_subpath(&circle);
assert_eq!(vector_data.point_domain.ids().len(), 4);
let bézier_paths = vector_data.segment_bezier_iter().map(|(_, bézier, _, _)| bézier).collect::<Vec<_>>();
assert_eq!(bézier_paths.len(), 4);
assert!(bézier_paths.iter().all(|&bézier| circle.iter().any(|original_bézier| original_bézier == bézier)));
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
assert_subpath_eq(&generated, &[circle]);
}
#[test]
fn construct_open_subpath() {
let bézier = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X);
let subpath = bezier_rs::Subpath::from_bezier(&bézier);
let vector_data = VectorData::from_subpath(&subpath);
assert_eq!(vector_data.point_domain.ids().len(), 2);
let bézier_paths = vector_data.segment_bezier_iter().map(|(_, bézier, _, _)| bézier).collect::<Vec<_>>();
assert_eq!(bézier_paths, vec![bézier]);
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
assert_subpath_eq(&generated, &[subpath]);
}
#[test]
fn construct_many_subpath() {
let curve = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X);
let curve = bezier_rs::Subpath::from_bezier(&curve);
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
let vector_data = VectorData::from_subpaths([&curve, &circle], false);
assert_eq!(vector_data.point_domain.ids().len(), 6);
let bézier_paths = vector_data.segment_bezier_iter().map(|(_, bézier, _, _)| bézier).collect::<Vec<_>>();
assert_eq!(bézier_paths.len(), 5);
assert!(bézier_paths.iter().all(|&bézier| circle.iter().chain(curve.iter()).any(|original_bézier| original_bézier == bézier)));
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
assert_subpath_eq(&generated, &[curve, circle]);
}

View File

@@ -1,8 +1,11 @@
use super::HandleId;
use dyn_any::{DynAny, StaticType};
use glam::{DAffine2, DVec2};
use std::collections::HashMap;
/// A simple macro for creating strongly typed ids (to avoid confusion when passing around ids).
macro_rules! create_ids {
($($id:ident),*) => {
$(
@@ -12,14 +15,23 @@ macro_rules! create_ids {
pub struct $id(u64);
impl $id {
pub const ZERO: $id = $id(0);
/// Generate a new random id
pub fn generate() -> Self {
Self(crate::uuid::generate_uuid())
}
/// Gets the inner raw value.
pub fn inner(self) -> u64 {
self.0
}
/// Adds one to the current value and returns the old value. Note that the ids are not going to be unique unless you use the largest id.
pub fn next_id(&mut self) -> Self {
self.0 += 1;
*self
}
}
)*
};
@@ -55,7 +67,17 @@ impl PointDomain {
self.positions.clear();
}
pub fn retain(&mut self, f: impl Fn(&PointId) -> bool) {
let mut keep = self.id.iter().map(&f);
self.positions.retain(|_| keep.next().unwrap_or_default());
self.id.retain(f);
}
pub fn push(&mut self, id: PointId, position: DVec2) {
if self.id.contains(&id) {
warn!("Duplicate point");
return;
}
self.id.push(id);
self.positions.push(position);
}
@@ -64,11 +86,19 @@ impl PointDomain {
&self.positions
}
pub fn positions_mut(&mut self) -> impl Iterator<Item = (PointId, &mut DVec2)> {
self.id.iter().copied().zip(self.positions.iter_mut())
}
pub fn ids(&self) -> &[PointId] {
&self.id
}
pub fn pos_from_id(&self, id: PointId) -> Option<DVec2> {
pub fn next_id(&self) -> PointId {
self.ids().iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(PointId::ZERO)
}
pub fn position_from_id(&self, id: PointId) -> Option<DVec2> {
let pos = self.resolve_id(id).map(|index| self.positions[index]);
if pos.is_none() {
warn!("Resolving pos of invalid id");
@@ -99,7 +129,6 @@ pub struct SegmentDomain {
ids: Vec<SegmentId>,
start_point: Vec<PointId>,
end_point: Vec<PointId>,
// TODO: Also store handle points as `PointId`s rather than Bezier-rs's internal `DVec2`s
handles: Vec<bezier_rs::BezierHandles>,
stroke: Vec<StrokeId>,
}
@@ -123,21 +152,116 @@ impl SegmentDomain {
self.stroke.clear();
}
pub fn push(&mut self, id: SegmentId, start: PointId, end: PointId, handles: bezier_rs::BezierHandles, stroke: StrokeId) {
self.ids.push(id);
self.start_point.push(start);
self.end_point.push(end);
self.handles.push(handles);
self.stroke.push(stroke);
pub fn retain(&mut self, f: impl Fn(&SegmentId) -> bool) {
let mut keep = self.ids.iter().map(&f);
self.start_point.retain(|_| keep.next().unwrap_or_default());
let mut keep = self.ids.iter().map(&f);
self.end_point.retain(|_| keep.next().unwrap_or_default());
let mut keep = self.ids.iter().map(&f);
self.handles.retain(|_| keep.next().unwrap_or_default());
let mut keep = self.ids.iter().map(&f);
self.stroke.retain(|_| keep.next().unwrap_or_default());
self.ids.retain(f);
}
fn resolve_id(&self, id: SegmentId) -> Option<usize> {
pub fn ids(&self) -> &[SegmentId] {
&self.ids
}
pub fn next_id(&self) -> SegmentId {
self.ids().iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(SegmentId::ZERO)
}
pub fn start_point(&self) -> &[PointId] {
&self.start_point
}
pub fn end_point(&self) -> &[PointId] {
&self.end_point
}
pub fn handles(&self) -> &[bezier_rs::BezierHandles] {
&self.handles
}
pub fn stroke(&self) -> &[StrokeId] {
&self.stroke
}
pub fn push(&mut self, id: SegmentId, start: PointId, end: PointId, handles: bezier_rs::BezierHandles, stroke: StrokeId) {
if self.ids.contains(&id) {
warn!("Duplicate segment");
return;
}
// Attempt to keep line joins?
let after = self.end_point.iter().copied().position(|other_end| other_end == start || other_end == end);
let before = self.start_point.iter().copied().position(|other_start| other_start == start || other_start == end);
let (index, flip) = match (before, after) {
(_, Some(after)) => (after + 1, self.end_point[after] == end),
(Some(before), _) => (before, self.start_point[before] == start),
(None, None) => (self.ids.len(), false),
};
self.ids.insert(index, id);
self.start_point.insert(index, if flip { end } else { start });
self.end_point.insert(index, if flip { start } else { end });
self.handles.insert(index, if flip { handles.flipped() } else { handles });
self.stroke.insert(index, stroke);
}
pub fn start_point_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut PointId)> {
self.ids.iter().copied().zip(self.start_point.iter_mut())
}
pub fn end_point_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut PointId)> {
self.ids.iter().copied().zip(self.end_point.iter_mut())
}
pub fn handles_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut bezier_rs::BezierHandles, PointId, PointId)> {
let nested = self.ids.iter().zip(&mut self.handles).zip(&self.start_point).zip(&self.end_point);
nested.map(|(((&a, b), &c), &d)| (a, b, c, d))
}
pub fn stroke_mut(&mut self) -> impl Iterator<Item = (SegmentId, &mut StrokeId)> {
self.ids.iter().copied().zip(self.stroke.iter_mut())
}
pub fn segment_start_from_id(&self, segment: SegmentId) -> Option<PointId> {
self.id_to_index(segment).and_then(|index| self.start_point.get(index)).copied()
}
pub fn segment_end_from_id(&self, segment: SegmentId) -> Option<PointId> {
self.id_to_index(segment).and_then(|index| self.end_point.get(index)).copied()
}
/// Returns an array for the start and end points of a segment.
pub fn points_from_id(&self, segment: SegmentId) -> Option<[PointId; 2]> {
self.segment_start_from_id(segment).and_then(|start| self.segment_end_from_id(segment).map(|end| [start, end]))
}
/// Attempts to find another point in the segment that is not the one passed in.
pub fn other_point(&self, segment: SegmentId, current: PointId) -> Option<PointId> {
self.points_from_id(segment).and_then(|points| points.into_iter().find(|&point| point != current))
}
/// Gets all points connected to the current one but not including the current one.
pub fn connected_points(&self, current: PointId) -> impl Iterator<Item = PointId> + '_ {
self.start_point.iter().zip(&self.end_point).filter_map(move |(&a, &b)| match (a == current, b == current) {
(true, false) => Some(b),
(false, true) => Some(a),
_ => None,
})
}
fn id_to_index(&self, id: SegmentId) -> Option<usize> {
debug_assert_eq!(self.ids.len(), self.handles.len());
debug_assert_eq!(self.ids.len(), self.start_point.len());
debug_assert_eq!(self.ids.len(), self.end_point.len());
self.ids.iter().position(|&check_id| check_id == id)
}
fn resolve_range(&self, range: &core::ops::RangeInclusive<SegmentId>) -> Option<core::ops::RangeInclusive<usize>> {
match (self.resolve_id(*range.start()), self.resolve_id(*range.end())) {
(Some(start), Some(end)) => Some(start..=end),
match (self.id_to_index(*range.start()), self.id_to_index(*range.end())) {
(Some(start), Some(end)) if start.max(end) < self.handles.len().min(self.ids.len()).min(self.start_point.len()).min(self.end_point.len()) => Some(start..=end),
_ => {
warn!("Resolving range with invalid id");
None
@@ -158,6 +282,26 @@ impl SegmentDomain {
*handles = handles.apply_transformation(|p| transform.transform_point2(p));
}
}
/// Enumerate all segments that start at the point.
pub fn start_connected(&self, point: PointId) -> impl Iterator<Item = SegmentId> + '_ {
self.start_point.iter().zip(&self.ids).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg)
}
/// Enumerate all segments that end at the point.
pub fn end_connected(&self, point: PointId) -> impl Iterator<Item = SegmentId> + '_ {
self.end_point.iter().zip(&self.ids).filter(move |&(&found_point, _)| found_point == point).map(|(_, &seg)| seg)
}
/// Enumerate all segments that start or end at a point, converting them to [`HandleId`s]. Note that the handles may not exist e.g. for a linear segment.
pub fn all_connected(&self, point: PointId) -> impl Iterator<Item = HandleId> + '_ {
self.start_connected(point).map(HandleId::primary).chain(self.end_connected(point).map(HandleId::end))
}
/// Enumerate the number of segments connected to a point. If a segment starts and ends at a point then it is counted twice.
pub fn connected_count(&self, point: PointId) -> usize {
self.all_connected(point).count()
}
}
#[derive(Clone, Debug, Default, PartialEq, Hash, DynAny)]
@@ -184,7 +328,19 @@ impl RegionDomain {
self.fill.clear();
}
pub fn retain(&mut self, f: impl Fn(&RegionId) -> bool) {
let mut keep = self.ids.iter().map(&f);
self.segment_range.retain(|_| keep.next().unwrap_or_default());
let mut keep = self.ids.iter().map(&f);
self.fill.retain(|_| keep.next().unwrap_or_default());
self.ids.retain(&f);
}
pub fn push(&mut self, id: RegionId, segment_range: core::ops::RangeInclusive<SegmentId>, fill: FillId) {
if self.ids.contains(&id) {
warn!("Duplicate region");
return;
}
self.ids.push(id);
self.segment_range.push(segment_range);
self.fill.push(fill);
@@ -194,6 +350,30 @@ impl RegionDomain {
self.ids.iter().position(|&check_id| check_id == id)
}
pub fn next_id(&self) -> RegionId {
self.ids.iter().copied().max_by(|a, b| a.0.cmp(&b.0)).map(|mut id| id.next_id()).unwrap_or(RegionId::ZERO)
}
pub fn segment_range_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut core::ops::RangeInclusive<SegmentId>)> {
self.ids.iter().copied().zip(self.segment_range.iter_mut())
}
pub fn fill_mut(&mut self) -> impl Iterator<Item = (RegionId, &mut FillId)> {
self.ids.iter().copied().zip(self.fill.iter_mut())
}
pub fn ids(&self) -> &[RegionId] {
&self.ids
}
pub fn segment_range(&self) -> &[core::ops::RangeInclusive<SegmentId>] {
&self.segment_range
}
pub fn fill(&self) -> &[FillId] {
&self.fill
}
fn concat(&mut self, other: &Self, _transform: DAffine2, id_map: &IdMap) {
self.ids.extend(other.ids.iter().map(|id| *id_map.region_map.get(id).unwrap_or(id)));
self.segment_range.extend(
@@ -209,15 +389,22 @@ impl RegionDomain {
impl super::VectorData {
/// Construct a [`bezier_rs::Bezier`] curve spanning from the resolved position of the start and end points with the specified handles. Returns [`None`] if either ID is invalid.
fn segment_to_bezier(&self, start: PointId, end: PointId, handles: bezier_rs::BezierHandles) -> Option<bezier_rs::Bezier> {
let start = self.point_domain.pos_from_id(start)?;
let end = self.point_domain.pos_from_id(end)?;
let start = self.point_domain.position_from_id(start)?;
let end = self.point_domain.position_from_id(end)?;
Some(bezier_rs::Bezier { start, end, handles })
}
/// Tries to convert a segment with the specified id to a [`bezier_rs::Bezier`], returning None if the id is invalid.
pub fn segment_from_id(&self, id: SegmentId) -> Option<bezier_rs::Bezier> {
let index = self.segment_domain.resolve_id(id)?;
self.segment_to_bezier(self.segment_domain.start_point[index], self.segment_domain.end_point[index], self.segment_domain.handles[index])
self.segment_points_from_id(id).map(|(_, _, bezier)| bezier)
}
/// Tries to convert a segment with the specified id to the start and end points and a [`bezier_rs::Bezier`], returning None if the id is invalid.
pub fn segment_points_from_id(&self, id: SegmentId) -> Option<(PointId, PointId, bezier_rs::Bezier)> {
let index: usize = self.segment_domain.id_to_index(id)?;
let start = self.segment_domain.start_point[index];
let end = self.segment_domain.end_point[index];
Some((start, end, self.segment_to_bezier(start, end, self.segment_domain.handles[index])?))
}
/// Iterator over all of the [`bezier_rs::Bezier`] following the order that they are stored in the segment domain, skipping invalid segments.
@@ -237,17 +424,6 @@ impl super::VectorData {
let mut first_point = None;
let mut groups = Vec::new();
let mut last: Option<(PointId, bezier_rs::BezierHandles)> = None;
let end_point = |last: Option<(PointId, bezier_rs::BezierHandles)>, next: Option<PointId>, groups: &mut Vec<_>| {
if let Some((disconnected_previous, previous_handle)) = last.filter(|(end, _)| !next.is_some_and(|next| next == *end)) {
groups.push(bezier_rs::ManipulatorGroup {
anchor: self.point_domain.pos_from_id(disconnected_previous)?,
in_handle: previous_handle.end(),
out_handle: None,
id: disconnected_previous,
});
}
Some(())
};
for (handle, start, end) in segments {
if last.is_some_and(|(previous_end, _)| previous_end != start) {
@@ -255,10 +431,9 @@ impl super::VectorData {
return None;
}
first_point = Some(first_point.unwrap_or(start));
end_point(last, Some(start), &mut groups)?;
groups.push(bezier_rs::ManipulatorGroup {
anchor: self.point_domain.pos_from_id(start)?,
anchor: self.point_domain.position_from_id(start)?,
in_handle: last.and_then(|(_, handle)| handle.end()),
out_handle: handle.start(),
id: start,
@@ -266,8 +441,21 @@ impl super::VectorData {
last = Some((end, handle));
}
end_point(last, None, &mut groups)?;
let closed = groups.len() > 1 && last.map(|(point, _)| point) == first_point;
if let Some((end, last_handle)) = last {
if closed {
groups[0].in_handle = last_handle.end();
} else {
groups.push(bezier_rs::ManipulatorGroup {
anchor: self.point_domain.position_from_id(end)?,
in_handle: last_handle.end(),
out_handle: None,
id: end,
});
}
}
Some(bezier_rs::Subpath::new(groups, closed))
}
@@ -279,10 +467,13 @@ impl super::VectorData {
.zip(&self.region_domain.segment_range)
.filter_map(|(&id, segment_range)| self.segment_domain.resolve_range(segment_range).map(|range| (id, range)))
.filter_map(|(id, range)| {
let segments_iter = self.segment_domain.handles[range.clone()]
let segments_iter = self
.segment_domain
.handles
.get(range.clone())?
.iter()
.zip(&self.segment_domain.start_point[range.clone()])
.zip(&self.segment_domain.end_point[range])
.zip(self.segment_domain.start_point.get(range.clone())?)
.zip(self.segment_domain.end_point.get(range)?)
.map(|((&handles, &start), &end)| (handles, start, end));
self.subpath_from_segments(segments_iter).map(|subpath| (id, subpath))
@@ -294,6 +485,17 @@ impl super::VectorData {
StrokePathIter { vector_data: self, segment_index: 0 }
}
/// Construct an iterator [`bezier_rs::ManipulatorGroup`] for stroke.
pub fn manipulator_groups(&self) -> impl Iterator<Item = bezier_rs::ManipulatorGroup<PointId>> + '_ {
self.stroke_bezier_paths().flat_map(|mut path| std::mem::take(path.manipulator_groups_mut()))
}
/// Get manipulator by id
pub fn manipulator_group_id(&self, id: impl Into<PointId>) -> Option<bezier_rs::ManipulatorGroup<PointId>> {
let id = id.into();
self.manipulator_groups().find(|group| group.id == id)
}
/// Transforms this vector data
pub fn transform(&mut self, transform: DAffine2) {
self.point_domain.transform(transform);
@@ -301,6 +503,7 @@ impl super::VectorData {
}
}
#[derive(Clone)]
pub struct StrokePathIter<'a> {
vector_data: &'a super::VectorData,
segment_index: usize,
@@ -339,11 +542,6 @@ impl bezier_rs::Identifier for PointId {
Self::generate()
}
}
impl From<crate::uuid::ManipulatorGroupId> for PointId {
fn from(value: crate::uuid::ManipulatorGroupId) -> Self {
Self(value.inner())
}
}
impl crate::vector::ConcatElement for super::VectorData {
fn concat(&mut self, other: &Self, transform: glam::DAffine2) {
@@ -369,6 +567,7 @@ impl crate::vector::ConcatElement for super::VectorData {
}
}
/// Represents the conversion of ids used when concatenating vector data with conflicting ids.
struct IdMap {
point_map: HashMap<PointId, PointId>,
segment_map: HashMap<SegmentId, SegmentId>,

View File

@@ -0,0 +1,530 @@
use super::*;
use crate::Node;
use bezier_rs::BezierHandles;
use dyn_any::{DynAny, StaticType};
use std::collections::{HashMap, HashSet};
/// Represents a procedural change to the [`PointDomain`] in [`VectorData`].
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PointModification {
add: Vec<PointId>,
remove: HashSet<PointId>,
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
delta: HashMap<PointId, DVec2>,
}
impl PointModification {
/// Apply this modification to the specified [`PointDomain`].
pub fn apply(&self, point_domain: &mut PointDomain, segment_domain: &mut SegmentDomain) {
point_domain.retain(|id| !self.remove.contains(id));
for (id, position) in point_domain.positions_mut() {
let Some(&delta) = self.delta.get(&id) else { continue };
if !delta.is_finite() {
warn!("Invalid delta when applying a point modification");
continue;
}
*position += delta;
for (_, handles, start, end) in segment_domain.handles_mut() {
if start == id {
handles.move_start(delta);
}
if end == id {
handles.move_end(delta);
}
}
}
for &add_id in &self.add {
let Some(&position) = self.delta.get(&add_id) else { continue };
if !position.is_finite() {
warn!("Invalid position when applying a point modification");
continue;
}
point_domain.push(add_id, position);
}
}
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
pub fn create_from_vector(vector_data: &VectorData) -> Self {
Self {
add: vector_data.point_domain.ids().to_vec(),
remove: HashSet::new(),
delta: vector_data.point_domain.ids().iter().copied().zip(vector_data.point_domain.positions().iter().cloned()).collect(),
}
}
fn push(&mut self, id: PointId, position: DVec2) {
self.add.push(id);
self.delta.insert(id, position);
}
fn remove(&mut self, id: PointId) {
self.remove.insert(id);
self.add.retain(|&add| add != id);
self.delta.remove(&id);
}
}
/// Represents a procedural change to the [`SegmentDomain`] in [`VectorData`].
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SegmentModification {
add: Vec<SegmentId>,
remove: HashSet<SegmentId>,
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
start_point: HashMap<SegmentId, PointId>,
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
end_point: HashMap<SegmentId, PointId>,
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
handle_primary: HashMap<SegmentId, Option<DVec2>>,
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
handle_end: HashMap<SegmentId, Option<DVec2>>,
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
stroke: HashMap<SegmentId, StrokeId>,
}
impl SegmentModification {
/// Apply this modification to the specified [`SegmentDomain`].
pub fn apply(&self, segment_domain: &mut SegmentDomain, point_domain: &PointDomain) {
segment_domain.retain(|id| !self.remove.contains(id));
for (id, point) in segment_domain.start_point_mut() {
let Some(&new) = self.start_point.get(&id) else { continue };
if !point_domain.ids().contains(&new) {
warn!("Invalid start ID when applying a segment modification");
continue;
}
*point = new;
}
for (id, point) in segment_domain.end_point_mut() {
let Some(&new) = self.end_point.get(&id) else { continue };
if !point_domain.ids().contains(&new) {
warn!("Invalid end ID when applying a segment modification");
continue;
}
*point = new;
}
for (id, handles, start, end) in segment_domain.handles_mut() {
let Some(start) = point_domain.position_from_id(start) else { continue };
let Some(end) = point_domain.position_from_id(end) else { continue };
// Compute the actual start and end position based on the offset from the anchor
let start = self.handle_primary.get(&id).copied().map(|handle| handle.map(|handle| handle + start));
let end = self.handle_end.get(&id).copied().map(|handle| handle.map(|handle| handle + end));
if !start.unwrap_or_default().map_or(true, |start| start.is_finite()) || !end.unwrap_or_default().map_or(true, |end| end.is_finite()) {
warn!("Invalid handles when applying a segment modification");
continue;
}
match (start, end) {
// The new handles are fully specified by the modification
(Some(Some(handle_start)), Some(Some(handle_end))) => *handles = BezierHandles::Cubic { handle_start, handle_end },
(Some(Some(handle)), Some(None)) | (Some(None), Some(Some(handle))) => *handles = BezierHandles::Quadratic { handle },
(Some(None), Some(None)) => *handles = BezierHandles::Linear,
// Remove the end handle
(None, Some(None)) => {
if let BezierHandles::Cubic { handle_start, .. } = *handles {
*handles = BezierHandles::Quadratic { handle: handle_start }
}
}
// Change the end handle
(None, Some(Some(handle_end))) => match *handles {
BezierHandles::Linear => *handles = BezierHandles::Quadratic { handle: handle_end },
BezierHandles::Quadratic { handle: handle_start } => *handles = BezierHandles::Cubic { handle_start, handle_end },
BezierHandles::Cubic { handle_start, .. } => *handles = BezierHandles::Cubic { handle_start, handle_end },
},
// Remove the start handle
(Some(None), None) => *handles = BezierHandles::Linear,
// Change the start handle
(Some(Some(handle_start)), None) => match *handles {
BezierHandles::Linear => *handles = BezierHandles::Quadratic { handle: handle_start },
BezierHandles::Quadratic { .. } => *handles = BezierHandles::Quadratic { handle: handle_start },
BezierHandles::Cubic { handle_end, .. } => *handles = BezierHandles::Cubic { handle_start, handle_end },
},
// No change
(None, None) => {}
};
}
for (id, stroke) in segment_domain.stroke_mut() {
let Some(&new) = self.stroke.get(&id) else { continue };
*stroke = new;
}
for &add_id in &self.add {
let Some(&start) = self.start_point.get(&add_id) else { continue };
let Some(&end) = self.end_point.get(&add_id) else { continue };
let Some(&handle_start) = self.handle_primary.get(&add_id) else { continue };
let Some(&handle_end) = self.handle_end.get(&add_id) else { continue };
let Some(&stroke) = self.stroke.get(&add_id) else { continue };
if !point_domain.ids().contains(&start) {
warn!("invalid start id");
continue;
}
if !point_domain.ids().contains(&end) {
warn!("invalid end id");
continue;
}
let Some(start_position) = point_domain.position_from_id(start) else { continue };
let Some(end_position) = point_domain.position_from_id(end) else { continue };
let handles = match (handle_start, handle_end) {
(Some(handle_start), Some(handle_end)) => BezierHandles::Cubic {
handle_start: handle_start + start_position,
handle_end: handle_end + end_position,
},
(Some(handle), None) | (None, Some(handle)) => BezierHandles::Quadratic { handle: handle + start_position },
(None, None) => BezierHandles::Linear,
};
if !handles.is_finite() {
warn!("invalid handles");
continue;
}
segment_domain.push(add_id, start, end, handles, stroke);
}
}
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
pub fn create_from_vector(vector_data: &VectorData) -> Self {
Self {
add: vector_data.segment_domain.ids().to_vec(),
remove: HashSet::new(),
start_point: vector_data.segment_domain.ids().iter().copied().zip(vector_data.segment_domain.start_point().iter().cloned()).collect(),
end_point: vector_data.segment_domain.ids().iter().copied().zip(vector_data.segment_domain.end_point().iter().cloned()).collect(),
handle_primary: vector_data.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_start().map(|handle| handle - b.start))).collect(),
handle_end: vector_data.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_end().map(|handle| handle - b.end))).collect(),
stroke: vector_data.segment_domain.ids().iter().copied().zip(vector_data.segment_domain.stroke().iter().cloned()).collect(),
}
}
fn push(&mut self, id: SegmentId, points: [PointId; 2], handles: [Option<DVec2>; 2], stroke: StrokeId) {
self.remove.remove(&id);
self.add.push(id);
self.start_point.insert(id, points[0]);
self.end_point.insert(id, points[1]);
self.handle_primary.insert(id, handles[0]);
self.handle_end.insert(id, handles[1]);
self.stroke.insert(id, stroke);
}
fn remove(&mut self, id: SegmentId) {
self.remove.insert(id);
self.add.retain(|&add| add != id);
self.start_point.remove(&id);
self.end_point.remove(&id);
self.handle_primary.remove(&id);
self.handle_end.remove(&id);
self.stroke.remove(&id);
}
}
/// Represents a procedural change to the [`RegionDomain`] in [`VectorData`].
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RegionModification {
add: Vec<RegionId>,
remove: HashSet<RegionId>,
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
segment_range: HashMap<RegionId, core::ops::RangeInclusive<SegmentId>>,
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
fill: HashMap<RegionId, FillId>,
}
impl RegionModification {
/// Apply this modification to the specified [`RegionDomain`].
pub fn apply(&self, region_domain: &mut RegionDomain) {
region_domain.retain(|id| !self.remove.contains(id));
for (id, segment_range) in region_domain.segment_range_mut() {
let Some(new) = self.segment_range.get(&id) else { continue };
*segment_range = new.clone(); // Range inclusive is not copy
}
for (id, fill) in region_domain.fill_mut() {
let Some(&new) = self.fill.get(&id) else { continue };
*fill = new;
}
for &add_id in &self.add {
let Some(segment_range) = self.segment_range.get(&add_id) else { continue };
let Some(&fill) = self.fill.get(&add_id) else { continue };
region_domain.push(add_id, segment_range.clone(), fill);
}
}
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
pub fn create_from_vector(vector_data: &VectorData) -> Self {
Self {
add: vector_data.region_domain.ids().to_vec(),
remove: HashSet::new(),
segment_range: vector_data.region_domain.ids().iter().copied().zip(vector_data.region_domain.segment_range().iter().cloned()).collect(),
fill: vector_data.region_domain.ids().iter().copied().zip(vector_data.region_domain.fill().iter().cloned()).collect(),
}
}
}
/// Represents a procedural change to the [`VectorData`].
#[derive(Clone, Debug, Default, PartialEq, DynAny)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct VectorModification {
points: PointModification,
segments: SegmentModification,
regions: RegionModification,
add_g1_continuous: HashSet<[HandleId; 2]>,
remove_g1_continuous: HashSet<[HandleId; 2]>,
}
/// A modification type that can be added to a [`VectorModification`].
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum VectorModificationType {
InsertSegment { id: SegmentId, points: [PointId; 2], handles: [Option<DVec2>; 2] },
InsertPoint { id: PointId, position: DVec2 },
RemoveSegment { id: SegmentId },
RemovePoint { id: PointId },
SetG1Continuous { handles: [HandleId; 2], enabled: bool },
SetHandles { segment: SegmentId, handles: [Option<DVec2>; 2] },
SetPrimaryHandle { segment: SegmentId, relative_position: DVec2 },
SetEndHandle { segment: SegmentId, relative_position: DVec2 },
SetStartPoint { segment: SegmentId, id: PointId },
SetEndPoint { segment: SegmentId, id: PointId },
ApplyPointDelta { point: PointId, delta: DVec2 },
ApplyPrimaryDelta { segment: SegmentId, delta: DVec2 },
ApplyEndDelta { segment: SegmentId, delta: DVec2 },
}
impl VectorModification {
/// Apply this modification to the specified [`VectorData`].
pub fn apply(&self, vector_data: &mut VectorData) {
self.points.apply(&mut vector_data.point_domain, &mut vector_data.segment_domain);
self.segments.apply(&mut vector_data.segment_domain, &vector_data.point_domain);
self.regions.apply(&mut vector_data.region_domain);
let valid = |val: &[HandleId; 2]| vector_data.segment_domain.ids().contains(&val[0].segment) && vector_data.segment_domain.ids().contains(&val[1].segment);
vector_data
.colinear_manipulators
.retain(|val| !self.remove_g1_continuous.contains(val) && !self.remove_g1_continuous.contains(&[val[1], val[0]]) && valid(val));
for handles in &self.add_g1_continuous {
if !vector_data.colinear_manipulators.iter().any(|test| test == handles || test == &[handles[1], handles[0]]) && valid(handles) {
vector_data.colinear_manipulators.push(*handles);
}
}
}
/// Add a [`VectorModificationType`] to this modification.
pub fn modify(&mut self, vector_data_modification: &VectorModificationType) {
match vector_data_modification {
VectorModificationType::InsertSegment { id, points, handles } => self.segments.push(*id, *points, *handles, StrokeId::ZERO),
VectorModificationType::InsertPoint { id, position } => self.points.push(*id, *position),
VectorModificationType::RemoveSegment { id } => self.segments.remove(*id),
VectorModificationType::RemovePoint { id } => self.points.remove(*id),
VectorModificationType::SetG1Continuous { handles, enabled } => {
if *enabled {
if !self.add_g1_continuous.contains(&[handles[1], handles[0]]) {
self.add_g1_continuous.insert(*handles);
}
self.remove_g1_continuous.remove(handles);
self.remove_g1_continuous.remove(&[handles[1], handles[0]]);
} else {
if !self.remove_g1_continuous.contains(&[handles[1], handles[0]]) {
self.remove_g1_continuous.insert(*handles);
}
self.add_g1_continuous.remove(handles);
self.add_g1_continuous.remove(&[handles[1], handles[0]]);
}
}
VectorModificationType::SetHandles { segment, handles } => {
self.segments.handle_primary.insert(*segment, handles[0]);
self.segments.handle_end.insert(*segment, handles[1]);
}
VectorModificationType::SetPrimaryHandle { segment, relative_position } => {
self.segments.handle_primary.insert(*segment, Some(*relative_position));
}
VectorModificationType::SetEndHandle { segment, relative_position } => {
self.segments.handle_end.insert(*segment, Some(*relative_position));
}
VectorModificationType::SetStartPoint { segment, id } => {
self.segments.start_point.insert(*segment, *id);
}
VectorModificationType::SetEndPoint { segment, id } => {
self.segments.end_point.insert(*segment, *id);
}
VectorModificationType::ApplyPointDelta { point, delta } => {
*self.points.delta.entry(*point).or_default() += *delta;
}
VectorModificationType::ApplyPrimaryDelta { segment, delta } => {
let position = self.segments.handle_primary.entry(*segment).or_default();
*position = Some(position.unwrap_or_default() + *delta);
}
VectorModificationType::ApplyEndDelta { segment, delta } => {
let position = self.segments.handle_end.entry(*segment).or_default();
*position = Some(position.unwrap_or_default() + *delta);
}
}
}
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
pub fn create_from_vector(vector_data: &VectorData) -> Self {
Self {
points: PointModification::create_from_vector(vector_data),
segments: SegmentModification::create_from_vector(vector_data),
regions: RegionModification::create_from_vector(vector_data),
add_g1_continuous: vector_data.colinear_manipulators.iter().copied().collect(),
remove_g1_continuous: HashSet::new(),
}
}
}
impl core::hash::Hash for VectorModification {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
// TODO: properly implement (hashing a hashset is difficult because ordering is unstable)
PointId::generate().hash(state);
}
}
/// A node that applies a procedural modification to some [`VectorData`].
#[derive(Debug, Clone, Copy)]
pub struct PathModify<VectorModificationNode> {
modification: VectorModificationNode,
}
#[node_macro::node_fn(PathModify)]
fn path_modify(mut vector_data: VectorData, modification: VectorModification) -> VectorData {
modification.apply(&mut vector_data);
vector_data
}
#[test]
fn modify_new() {
let vector_data = VectorData::from_subpaths(
[bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE), bezier_rs::Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO)],
false,
);
let modify = VectorModification::create_from_vector(&vector_data);
let mut new = VectorData::empty();
modify.apply(&mut new);
assert_eq!(vector_data, new);
}
#[test]
fn modify_existing() {
use bezier_rs::{Bezier, Subpath};
let subpaths = [
Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE),
Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO),
Subpath::from_beziers(
&[
Bezier::from_quadratic_dvec2(DVec2::new(0., 0.), DVec2::new(5., 10.), DVec2::new(10., 0.)),
Bezier::from_quadratic_dvec2(DVec2::new(10., 0.), DVec2::new(15., 10.), DVec2::new(20., 0.)),
],
false,
),
];
let mut vector_data = VectorData::from_subpaths(&subpaths, false);
let mut modify_new = VectorModification::create_from_vector(&vector_data);
let mut modify_original = VectorModification::default();
for modification in [&mut modify_new, &mut modify_original] {
let point = vector_data.point_domain.ids()[0];
modification.modify(&VectorModificationType::ApplyPointDelta { point, delta: DVec2::X * 0.5 });
let point = vector_data.point_domain.ids()[9];
modification.modify(&VectorModificationType::ApplyPointDelta { point, delta: DVec2::X });
}
let mut new = VectorData::empty();
modify_new.apply(&mut new);
modify_original.apply(&mut vector_data);
assert_eq!(vector_data, new);
assert_eq!(vector_data.point_domain.positions()[0], DVec2::X);
assert_eq!(vector_data.point_domain.positions()[9], DVec2::new(11., 0.));
assert_eq!(
vector_data.segment_bezier_iter().nth(8).unwrap().1,
Bezier::from_quadratic_dvec2(DVec2::new(0., 0.), DVec2::new(5., 10.), DVec2::new(11., 0.))
);
assert_eq!(
vector_data.segment_bezier_iter().nth(9).unwrap().1,
Bezier::from_quadratic_dvec2(DVec2::new(11., 0.), DVec2::new(16., 10.), DVec2::new(20., 0.))
);
}
// TODO: Eventually remove this (probably starting late 2024)
use serde::de::{SeqAccess, Visitor};
use serde::ser::SerializeSeq;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use std::hash::Hash;
fn serialize_hashmap<K, V, S>(hashmap: &HashMap<K, V>, serializer: S) -> Result<S::Ok, S::Error>
where
K: Serialize + Eq + Hash,
V: Serialize,
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(hashmap.len()))?;
for (key, value) in hashmap {
seq.serialize_element(&(key, value))?;
}
seq.end()
}
fn deserialize_hashmap<'de, K, V, D>(deserializer: D) -> Result<HashMap<K, V>, D::Error>
where
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
D: Deserializer<'de>,
{
struct HashMapVisitor<K, V> {
marker: std::marker::PhantomData<fn() -> HashMap<K, V>>,
}
impl<'de, K, V> Visitor<'de> for HashMapVisitor<K, V>
where
K: Deserialize<'de> + Eq + Hash,
V: Deserialize<'de>,
{
type Value = HashMap<K, V>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a sequence of tuples")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut hashmap = HashMap::new();
while let Some((key, value)) = seq.next_element()? {
hashmap.insert(key, value);
}
Ok(hashmap)
}
}
let visitor = HashMapVisitor { marker: std::marker::PhantomData };
deserializer.deserialize_seq(visitor)
}

View File

@@ -170,11 +170,11 @@ fn solidify_stroke(vector_data: VectorData) -> VectorData {
// This is where we determine whether we have a closed or open path. Ex: Oval vs line segment.
if subpath_out.1.is_some() {
// Two closed subpaths, closed shape. Add both subpaths.
result.append_subpath(subpath_out.0);
result.append_subpath(subpath_out.1.unwrap());
result.append_subpath(subpath_out.0, false);
result.append_subpath(subpath_out.1.unwrap(), false);
} else {
// One closed subpath, open path.
result.append_subpath(subpath_out.0);
result.append_subpath(subpath_out.0, false);
}
}
@@ -363,7 +363,7 @@ pub struct PoissonDiskPoints<SeparationDiskDiameter> {
fn poisson_disk_points(vector_data: VectorData, separation_disk_diameter: f64) -> VectorData {
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
let mut result = VectorData::empty();
for (_, mut subpath) in vector_data.region_bezier_paths() {
for mut subpath in vector_data.stroke_bezier_paths() {
if subpath.manipulator_groups().len() < 3 {
continue;
}
@@ -400,6 +400,8 @@ fn splines_from_points(mut vector_data: VectorData) -> VectorData {
let first_handles = bezier_rs::solve_spline_first_handle(points.positions());
let stroke_id = StrokeId::ZERO;
for (start_index, end_index) in (0..(points.positions().len())).zip(1..(points.positions().len())) {
let handle_start = first_handles[start_index];
let handle_end = points.positions()[end_index] * 2. - first_handles[end_index];
@@ -407,7 +409,7 @@ fn splines_from_points(mut vector_data: VectorData) -> VectorData {
vector_data
.segment_domain
.push(SegmentId::generate(), points.ids()[start_index], points.ids()[end_index], handles, StrokeId::generate())
.push(SegmentId::generate(), points.ids()[start_index], points.ids()[end_index], handles, stroke_id)
}
vector_data
@@ -484,7 +486,7 @@ async fn morph<SourceFuture: Future<Output = VectorData>, TargetFuture: Future<O
manipulator.anchor = manipulator.anchor.lerp(target.anchor, time);
}
result.append_subpath(source_path);
result.append_subpath(source_path, true);
}
// Mismatched subpath count
for mut source_path in source_paths {

View File

@@ -65,6 +65,7 @@ fn migrate_layer_to_merge<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
}
Ok(s)
}
// TODO: Eventually remove this (probably starting late 2024)
#[derive(Debug, serde::Deserialize)]
#[serde(untagged)]
@@ -98,7 +99,6 @@ where
{
let input_versions = Vec::<NodeInputVersions>::deserialize(deserializer)?;
// Convert Vec<NodeOutput> to Vec<NodeInput>
let inputs = input_versions
.into_iter()
.map(|old_input| {
@@ -136,8 +136,9 @@ pub struct DocumentNode {
/// - From other nodes within this graph [`NodeInput::Node`],
/// - A constant value [`NodeInput::Value`],
/// - A [`NodeInput::Network`] which specifies that this input is from outside the graph, which is resolved in the graph flattening step in the case of nested networks.
/// In the root network, it is resolved when evaluating the borrow tree.
/// Ensure the click target in the encapsulating network is updated when the inputs cause the node shape to change (currently only when exposing/hiding an input) by using network.update_click_target(node_id).
///
/// In the root network, it is resolved when evaluating the borrow tree.
/// Ensure the click target in the encapsulating network is updated when the inputs cause the node shape to change (currently only when exposing/hiding an input) by using network.update_click_target(node_id).
#[serde(deserialize_with = "deserialize_inputs")]
pub inputs: Vec<NodeInput>,
/// Manual composition is a way to override the default composition flow of one node into another.
@@ -1214,7 +1215,7 @@ impl NodeNetwork {
for (nested_input_index, nested_input) in nested_node.clone().inputs.iter().enumerate() {
if let NodeInput::Network { import_index, .. } = nested_input {
let parent_input = node.inputs.get(*import_index).expect("Import index should always exist");
let parent_input = node.inputs.get(*import_index).expect(&format!("Import index {} should always exist", import_index));
match *parent_input {
// If the input to self is a node, connect the corresponding output of the inner network to it
NodeInput::Node { node_id, output_index, lambda } => {
@@ -1240,7 +1241,7 @@ impl NodeNetwork {
// Match the document node input and the exports of the inner network if the export is a NodeInput::Network
// for (i, export) in inner_network.exports.iter().enumerate() {
// if let NodeInput::Network { import_index, .. } = export {
// let parent_input = node.inputs.get(*import_index).expect("Import index should always exist");
// let parent_input = node.inputs.get(*import_index).expect(&format!("Import index {} should always exist", import_index));
// match *parent_input {
// // If the input to self is a node, connect the corresponding output of the inner network to it
// NodeInput::Node { node_id, output_index, lambda } => {

View File

@@ -13,11 +13,101 @@ pub use glam::{DAffine2, DVec2, IVec2, UVec2};
use std::hash::Hash;
pub use std::sync::Arc;
/// A type that is known, allowing serialization (serde::Deserialize is not object safe)
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TaggedValue {
None,
/// Macro to generate the tagged value enum.
macro_rules! tagged_value {
($ ($( #[$meta:meta] )* $identifier:ident ($ty:ty) ),* $(,)?) => {
/// A type that is known, allowing serialization (serde::Deserialize is not object safe)
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TaggedValue {
None,
$( $(#[$meta] ) *$identifier( $ty ), )*
RenderOutput(RenderOutput),
SurfaceFrame(graphene_core::SurfaceFrame),
}
// We must manually implement hashing because some values are floats and so do not reproducibly hash (see FakeHash below)
#[allow(clippy::derived_hash_with_manual_eq)]
impl Hash for TaggedValue {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
match self {
Self::None => {}
$( Self::$identifier(x) => {x.hash(state)}),*
Self::RenderOutput(x) => x.hash(state),
Self::SurfaceFrame(x) => x.hash(state),
}
}
}
impl<'a> TaggedValue {
/// Converts to a Box<dyn DynAny> - this isn't very neat but I'm not sure of a better approach
pub fn to_any(self) -> Any<'a> {
match self {
Self::None => Box::new(()),
$( Self::$identifier(x) => Box::new(x), )*
Self::RenderOutput(x) => Box::new(x),
Self::SurfaceFrame(x) => Box::new(x),
}
}
/// Creates a graphene_core::Type::Concrete(TypeDescriptor { .. }) with the type of the value inside the tagged value
pub fn ty(&self) -> Type {
match self {
Self::None => concrete!(()),
$( Self::$identifier(_) => concrete!($ty), )*
Self::RenderOutput(_) => concrete!(RenderOutput),
Self::SurfaceFrame(_) => concrete!(graphene_core::SurfaceFrame),
}
}
/// Attempts to downcast the dynamic type to a tagged value
pub fn try_from_any(input: Box<dyn DynAny<'a> + 'a>) -> Result<Self, String> {
use dyn_any::downcast;
use std::any::TypeId;
match DynAny::type_id(input.as_ref()) {
x if x == TypeId::of::<()>() => Ok(TaggedValue::None),
$( x if x == TypeId::of::<$ty>() => Ok(TaggedValue::$identifier(*downcast(input).unwrap())), )*
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::WasmSurfaceHandleFrame>() => {
let frame = *downcast::<graphene_core::WasmSurfaceHandleFrame>(input).unwrap();
Ok(TaggedValue::SurfaceFrame(frame.into()))
}
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
}
}
pub fn from_type(input: &Type) -> Self {
match input {
Type::Generic(_) => {
log::warn!("Generic type should be resolved");
TaggedValue::None
}
Type::Concrete(concrete_type) => {
let Some(internal_id) = concrete_type.id else {
return TaggedValue::None;
};
use std::any::TypeId;
// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types
// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.
match internal_id {
x if x == TypeId::of::<()>() => TaggedValue::None,
$( x if x == TypeId::of::<$ty>() => TaggedValue::$identifier(Default::default()), )*
_ => TaggedValue::None,
}
}
Type::Fn(_, output) => TaggedValue::from_type(output),
Type::Future(_) => {
log::warn!("Future type not used");
TaggedValue::None
}
}
}
}
};
}
tagged_value! {
String(String),
U32(u32),
U64(u64),
@@ -33,8 +123,7 @@ pub enum TaggedValue {
ImaginateCache(ImaginateCache),
ImageFrame(graphene_core::raster::ImageFrame<Color>),
Color(graphene_core::raster::color::Color),
Subpaths(Vec<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>),
RcSubpath(Arc<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>),
Subpaths(Vec<bezier_rs::Subpath<graphene_core::vector::PointId>>),
BlendMode(BlendMode),
LuminanceCalculation(LuminanceCalculation),
ImaginateSamplingMethod(ImaginateSamplingMethod),
@@ -66,7 +155,7 @@ pub enum TaggedValue {
GradientStops(graphene_core::vector::style::GradientStops),
Quantization(graphene_core::quantization::QuantizationChannels),
OptionalColor(Option<graphene_core::raster::color::Color>),
ManipulatorGroupIds(Vec<graphene_core::uuid::ManipulatorGroupId>),
PointIds(Vec<graphene_core::vector::PointId>),
Font(graphene_core::text::Font),
BrushStrokes(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
BrushCache(BrushCache),
@@ -76,168 +165,14 @@ pub enum TaggedValue {
GraphicElement(graphene_core::GraphicElement),
ArtboardGroup(graphene_core::ArtboardGroup),
Curve(graphene_core::raster::curve::Curve),
SurfaceFrame(graphene_core::SurfaceFrame),
Footprint(graphene_core::transform::Footprint),
RenderOutput(RenderOutput),
Palette(Vec<Color>),
VectorModification(graphene_core::vector::VectorModification),
CentroidType(graphene_core::vector::misc::CentroidType),
BooleanOperation(graphene_core::vector::misc::BooleanOperation),
}
#[allow(clippy::derived_hash_with_manual_eq)]
impl Hash for TaggedValue {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
match self {
Self::None => {}
Self::String(x) => x.hash(state),
Self::U32(x) => x.hash(state),
Self::U64(x) => x.hash(state),
Self::F64(x) => x.to_bits().hash(state),
Self::Bool(x) => x.hash(state),
Self::UVec2(x) => x.to_array().iter().for_each(|x| x.hash(state)),
Self::IVec2(x) => x.hash(state),
Self::DVec2(x) => x.to_array().iter().for_each(|x| x.to_bits().hash(state)),
Self::OptionalDVec2(None) => 0.hash(state),
Self::OptionalDVec2(Some(x)) => {
1.hash(state);
Self::DVec2(*x).hash(state)
}
Self::DAffine2(x) => x.to_cols_array().iter().for_each(|x| x.to_bits().hash(state)),
Self::Image(x) => x.hash(state),
Self::ImaginateCache(x) => x.hash(state),
Self::Color(x) => x.hash(state),
Self::Subpaths(x) => x.iter().for_each(|subpath| subpath.hash(state)),
Self::RcSubpath(x) => x.hash(state),
Self::BlendMode(x) => x.hash(state),
Self::LuminanceCalculation(x) => x.hash(state),
Self::ImaginateSamplingMethod(x) => x.hash(state),
Self::ImaginateMaskStartingFill(x) => x.hash(state),
Self::ImaginateController(x) => x.hash(state),
Self::ImageFrame(x) => x.hash(state),
Self::VectorData(x) => x.hash(state),
Self::Fill(x) => x.hash(state),
Self::Stroke(x) => x.hash(state),
Self::F64Array4(x) => x.iter().for_each(|x| x.to_bits().hash(state)),
Self::VecF64(x) => x.iter().for_each(|val| val.to_bits().hash(state)),
Self::VecDVec2(x) => x.iter().for_each(|val| val.to_array().iter().for_each(|x| x.to_bits().hash(state))),
Self::RedGreenBlue(x) => x.hash(state),
Self::RedGreenBlueAlpha(x) => x.hash(state),
Self::NoiseType(x) => x.hash(state),
Self::FractalType(x) => x.hash(state),
Self::CellularDistanceFunction(x) => x.hash(state),
Self::CellularReturnType(x) => x.hash(state),
Self::DomainWarpType(x) => x.hash(state),
Self::RelativeAbsolute(x) => x.hash(state),
Self::SelectiveColorChoice(x) => x.hash(state),
Self::LineCap(x) => x.hash(state),
Self::LineJoin(x) => x.hash(state),
Self::FillType(x) => x.hash(state),
Self::FillChoice(x) => x.hash(state),
Self::Gradient(x) => x.hash(state),
Self::GradientType(x) => x.hash(state),
Self::GradientStops(x) => {
x.0.len().hash(state);
for (position, color) in &x.0 {
position.to_bits().hash(state);
color.hash(state);
}
}
Self::Quantization(x) => x.hash(state),
Self::OptionalColor(x) => x.hash(state),
Self::ManipulatorGroupIds(x) => x.hash(state),
Self::Font(x) => x.hash(state),
Self::BrushStrokes(x) => x.hash(state),
Self::BrushCache(x) => x.hash(state),
Self::Segments(x) => {
for segment in x {
segment.hash(state)
}
}
Self::DocumentNode(x) => x.hash(state),
Self::GraphicGroup(x) => x.hash(state),
Self::GraphicElement(x) => x.hash(state),
Self::ArtboardGroup(x) => x.hash(state),
Self::Curve(x) => x.hash(state),
Self::SurfaceFrame(x) => x.hash(state),
Self::Footprint(x) => x.hash(state),
Self::RenderOutput(x) => x.hash(state),
Self::Palette(x) => x.hash(state),
Self::CentroidType(x) => x.hash(state),
Self::BooleanOperation(x) => x.hash(state),
}
}
}
impl<'a> TaggedValue {
/// Converts to a Box<dyn DynAny> - this isn't very neat but I'm not sure of a better approach
pub fn to_any(self) -> Any<'a> {
match self {
TaggedValue::None => Box::new(()),
TaggedValue::String(x) => Box::new(x),
TaggedValue::U32(x) => Box::new(x),
TaggedValue::U64(x) => Box::new(x),
TaggedValue::F64(x) => Box::new(x),
TaggedValue::Bool(x) => Box::new(x),
TaggedValue::UVec2(x) => Box::new(x),
TaggedValue::IVec2(x) => Box::new(x),
TaggedValue::DVec2(x) => Box::new(x),
TaggedValue::OptionalDVec2(x) => Box::new(x),
TaggedValue::DAffine2(x) => Box::new(x),
TaggedValue::Image(x) => Box::new(x),
TaggedValue::ImaginateCache(x) => Box::new(x),
TaggedValue::ImageFrame(x) => Box::new(x),
TaggedValue::Color(x) => Box::new(x),
TaggedValue::Subpaths(x) => Box::new(x),
TaggedValue::RcSubpath(x) => Box::new(x),
TaggedValue::BlendMode(x) => Box::new(x),
TaggedValue::LuminanceCalculation(x) => Box::new(x),
TaggedValue::ImaginateSamplingMethod(x) => Box::new(x),
TaggedValue::ImaginateMaskStartingFill(x) => Box::new(x),
TaggedValue::ImaginateController(x) => Box::new(x),
TaggedValue::VectorData(x) => Box::new(x),
TaggedValue::Fill(x) => Box::new(x),
TaggedValue::Stroke(x) => Box::new(x),
TaggedValue::F64Array4(x) => Box::new(x),
TaggedValue::VecF64(x) => Box::new(x),
TaggedValue::VecDVec2(x) => Box::new(x),
TaggedValue::RedGreenBlue(x) => Box::new(x),
TaggedValue::RedGreenBlueAlpha(x) => Box::new(x),
TaggedValue::NoiseType(x) => Box::new(x),
TaggedValue::FractalType(x) => Box::new(x),
TaggedValue::CellularDistanceFunction(x) => Box::new(x),
TaggedValue::CellularReturnType(x) => Box::new(x),
TaggedValue::DomainWarpType(x) => Box::new(x),
TaggedValue::RelativeAbsolute(x) => Box::new(x),
TaggedValue::SelectiveColorChoice(x) => Box::new(x),
TaggedValue::LineCap(x) => Box::new(x),
TaggedValue::LineJoin(x) => Box::new(x),
TaggedValue::FillType(x) => Box::new(x),
TaggedValue::FillChoice(x) => Box::new(x),
TaggedValue::Gradient(x) => Box::new(x),
TaggedValue::GradientType(x) => Box::new(x),
TaggedValue::GradientStops(x) => Box::new(x),
TaggedValue::Quantization(x) => Box::new(x),
TaggedValue::OptionalColor(x) => Box::new(x),
TaggedValue::ManipulatorGroupIds(x) => Box::new(x),
TaggedValue::Font(x) => Box::new(x),
TaggedValue::BrushStrokes(x) => Box::new(x),
TaggedValue::BrushCache(x) => Box::new(x),
TaggedValue::Segments(x) => Box::new(x),
TaggedValue::DocumentNode(x) => Box::new(x),
TaggedValue::GraphicGroup(x) => Box::new(x),
TaggedValue::GraphicElement(x) => Box::new(x),
TaggedValue::ArtboardGroup(x) => Box::new(x),
TaggedValue::Curve(x) => Box::new(x),
TaggedValue::SurfaceFrame(x) => Box::new(x),
TaggedValue::Footprint(x) => Box::new(x),
TaggedValue::RenderOutput(x) => Box::new(x),
TaggedValue::Palette(x) => Box::new(x),
TaggedValue::CentroidType(x) => Box::new(x),
TaggedValue::BooleanOperation(x) => Box::new(x),
}
}
pub fn to_string(&self) -> String {
match self {
TaggedValue::String(x) => x.to_string(),
@@ -248,7 +183,6 @@ impl<'a> TaggedValue {
_ => panic!("Cannot convert to string"),
}
}
pub fn to_primitive_string(&self) -> String {
match self {
TaggedValue::None => "()".to_string(),
@@ -262,233 +196,6 @@ impl<'a> TaggedValue {
_ => panic!("Cannot convert to primitive string"),
}
}
pub fn ty(&self) -> Type {
match self {
TaggedValue::None => concrete!(()),
TaggedValue::String(_) => concrete!(String),
TaggedValue::U32(_) => concrete!(u32),
TaggedValue::U64(_) => concrete!(u64),
TaggedValue::F64(_) => concrete!(f64),
TaggedValue::Bool(_) => concrete!(bool),
TaggedValue::UVec2(_) => concrete!(UVec2),
TaggedValue::IVec2(_) => concrete!(IVec2),
TaggedValue::DVec2(_) => concrete!(DVec2),
TaggedValue::OptionalDVec2(_) => concrete!(Option<DVec2>),
TaggedValue::Image(_) => concrete!(graphene_core::raster::Image<Color>),
TaggedValue::ImaginateCache(_) => concrete!(ImaginateCache),
TaggedValue::ImageFrame(_) => concrete!(graphene_core::raster::ImageFrame<Color>),
TaggedValue::Color(_) => concrete!(graphene_core::raster::Color),
TaggedValue::Subpaths(_) => concrete!(Vec<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>),
TaggedValue::RcSubpath(_) => concrete!(Arc<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>),
TaggedValue::BlendMode(_) => concrete!(BlendMode),
TaggedValue::ImaginateSamplingMethod(_) => concrete!(ImaginateSamplingMethod),
TaggedValue::ImaginateMaskStartingFill(_) => concrete!(ImaginateMaskStartingFill),
TaggedValue::ImaginateController(_) => concrete!(ImaginateController),
TaggedValue::DAffine2(_) => concrete!(DAffine2),
TaggedValue::LuminanceCalculation(_) => concrete!(LuminanceCalculation),
TaggedValue::VectorData(_) => concrete!(graphene_core::vector::VectorData),
TaggedValue::Fill(_) => concrete!(graphene_core::vector::style::Fill),
TaggedValue::Stroke(_) => concrete!(graphene_core::vector::style::Stroke),
TaggedValue::F64Array4(_) => concrete!([f64; 4]),
TaggedValue::VecF64(_) => concrete!(Vec<f64>),
TaggedValue::VecDVec2(_) => concrete!(Vec<DVec2>),
TaggedValue::RedGreenBlue(_) => concrete!(graphene_core::raster::RedGreenBlue),
TaggedValue::RedGreenBlueAlpha(_) => concrete!(graphene_core::raster::RedGreenBlueAlpha),
TaggedValue::NoiseType(_) => concrete!(graphene_core::raster::NoiseType),
TaggedValue::FractalType(_) => concrete!(graphene_core::raster::FractalType),
TaggedValue::CellularDistanceFunction(_) => concrete!(graphene_core::raster::CellularDistanceFunction),
TaggedValue::CellularReturnType(_) => concrete!(graphene_core::raster::CellularReturnType),
TaggedValue::DomainWarpType(_) => concrete!(graphene_core::raster::DomainWarpType),
TaggedValue::RelativeAbsolute(_) => concrete!(graphene_core::raster::RelativeAbsolute),
TaggedValue::SelectiveColorChoice(_) => concrete!(graphene_core::raster::SelectiveColorChoice),
TaggedValue::LineCap(_) => concrete!(graphene_core::vector::style::LineCap),
TaggedValue::LineJoin(_) => concrete!(graphene_core::vector::style::LineJoin),
TaggedValue::FillType(_) => concrete!(graphene_core::vector::style::FillType),
TaggedValue::FillChoice(_) => concrete!(graphene_core::vector::style::FillChoice),
TaggedValue::Gradient(_) => concrete!(graphene_core::vector::style::Gradient),
TaggedValue::GradientType(_) => concrete!(graphene_core::vector::style::GradientType),
TaggedValue::GradientStops(_) => concrete!(graphene_core::vector::style::GradientStops),
TaggedValue::Quantization(_) => concrete!(graphene_core::quantization::QuantizationChannels),
TaggedValue::OptionalColor(_) => concrete!(Option<graphene_core::Color>),
TaggedValue::ManipulatorGroupIds(_) => concrete!(Vec<graphene_core::uuid::ManipulatorGroupId>),
TaggedValue::Font(_) => concrete!(graphene_core::text::Font),
TaggedValue::BrushStrokes(_) => concrete!(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
TaggedValue::BrushCache(_) => concrete!(BrushCache),
TaggedValue::Segments(_) => concrete!(graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>),
TaggedValue::DocumentNode(_) => concrete!(crate::document::DocumentNode),
TaggedValue::GraphicGroup(_) => concrete!(graphene_core::GraphicGroup),
TaggedValue::GraphicElement(_) => concrete!(graphene_core::GraphicElement),
TaggedValue::ArtboardGroup(_) => concrete!(graphene_core::ArtboardGroup),
TaggedValue::Curve(_) => concrete!(graphene_core::raster::curve::Curve),
TaggedValue::SurfaceFrame(_) => concrete!(graphene_core::SurfaceFrame),
TaggedValue::Footprint(_) => concrete!(graphene_core::transform::Footprint),
TaggedValue::RenderOutput(_) => concrete!(RenderOutput),
TaggedValue::Palette(_) => concrete!(Vec<Color>),
TaggedValue::CentroidType(_) => concrete!(graphene_core::vector::misc::CentroidType),
TaggedValue::BooleanOperation(_) => concrete!(graphene_core::vector::misc::BooleanOperation),
}
}
pub fn try_from_any(input: Box<dyn DynAny<'a> + 'a>) -> Result<Self, String> {
use dyn_any::downcast;
use std::any::TypeId;
match DynAny::type_id(input.as_ref()) {
x if x == TypeId::of::<()>() => Ok(TaggedValue::None),
x if x == TypeId::of::<String>() => Ok(TaggedValue::String(*downcast(input).unwrap())),
x if x == TypeId::of::<u32>() => Ok(TaggedValue::U32(*downcast(input).unwrap())),
x if x == TypeId::of::<u64>() => Ok(TaggedValue::U64(*downcast(input).unwrap())),
x if x == TypeId::of::<f64>() => Ok(TaggedValue::F64(*downcast(input).unwrap())),
x if x == TypeId::of::<bool>() => Ok(TaggedValue::Bool(*downcast(input).unwrap())),
x if x == TypeId::of::<UVec2>() => Ok(TaggedValue::UVec2(*downcast(input).unwrap())),
x if x == TypeId::of::<IVec2>() => Ok(TaggedValue::IVec2(*downcast(input).unwrap())),
x if x == TypeId::of::<DVec2>() => Ok(TaggedValue::DVec2(*downcast(input).unwrap())),
x if x == TypeId::of::<Option<DVec2>>() => Ok(TaggedValue::OptionalDVec2(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::Image<Color>>() => Ok(TaggedValue::Image(*downcast(input).unwrap())),
x if x == TypeId::of::<ImaginateCache>() => Ok(TaggedValue::ImaginateCache(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::ImageFrame<Color>>() => Ok(TaggedValue::ImageFrame(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::Color>() => Ok(TaggedValue::Color(*downcast(input).unwrap())),
x if x == TypeId::of::<Vec<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>>() => Ok(TaggedValue::Subpaths(*downcast(input).unwrap())),
x if x == TypeId::of::<Arc<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>>() => Ok(TaggedValue::RcSubpath(*downcast(input).unwrap())),
x if x == TypeId::of::<BlendMode>() => Ok(TaggedValue::BlendMode(*downcast(input).unwrap())),
x if x == TypeId::of::<ImaginateSamplingMethod>() => Ok(TaggedValue::ImaginateSamplingMethod(*downcast(input).unwrap())),
x if x == TypeId::of::<ImaginateMaskStartingFill>() => Ok(TaggedValue::ImaginateMaskStartingFill(*downcast(input).unwrap())),
x if x == TypeId::of::<ImaginateController>() => Ok(TaggedValue::ImaginateController(*downcast(input).unwrap())),
x if x == TypeId::of::<DAffine2>() => Ok(TaggedValue::DAffine2(*downcast(input).unwrap())),
x if x == TypeId::of::<LuminanceCalculation>() => Ok(TaggedValue::LuminanceCalculation(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::vector::VectorData>() => Ok(TaggedValue::VectorData(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::vector::style::Fill>() => Ok(TaggedValue::Fill(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::vector::style::Stroke>() => Ok(TaggedValue::Stroke(*downcast(input).unwrap())),
x if x == TypeId::of::<Vec<f64>>() => Ok(TaggedValue::VecF64(*downcast(input).unwrap())),
x if x == TypeId::of::<Vec<DVec2>>() => Ok(TaggedValue::VecDVec2(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::RedGreenBlue>() => Ok(TaggedValue::RedGreenBlue(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::RedGreenBlueAlpha>() => Ok(TaggedValue::RedGreenBlueAlpha(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::NoiseType>() => Ok(TaggedValue::NoiseType(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::FractalType>() => Ok(TaggedValue::FractalType(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::CellularDistanceFunction>() => Ok(TaggedValue::CellularDistanceFunction(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::CellularReturnType>() => Ok(TaggedValue::CellularReturnType(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::DomainWarpType>() => Ok(TaggedValue::DomainWarpType(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::RelativeAbsolute>() => Ok(TaggedValue::RelativeAbsolute(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::SelectiveColorChoice>() => Ok(TaggedValue::SelectiveColorChoice(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::vector::style::LineCap>() => Ok(TaggedValue::LineCap(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::vector::style::LineJoin>() => Ok(TaggedValue::LineJoin(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::vector::style::FillType>() => Ok(TaggedValue::FillType(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::vector::style::FillChoice>() => Ok(TaggedValue::FillChoice(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::vector::style::Gradient>() => Ok(TaggedValue::Gradient(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::vector::style::GradientType>() => Ok(TaggedValue::GradientType(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::vector::style::GradientStops>() => Ok(TaggedValue::GradientStops(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::quantization::QuantizationChannels>() => Ok(TaggedValue::Quantization(*downcast(input).unwrap())),
x if x == TypeId::of::<Option<graphene_core::Color>>() => Ok(TaggedValue::OptionalColor(*downcast(input).unwrap())),
x if x == TypeId::of::<Vec<graphene_core::uuid::ManipulatorGroupId>>() => Ok(TaggedValue::ManipulatorGroupIds(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::text::Font>() => Ok(TaggedValue::Font(*downcast(input).unwrap())),
x if x == TypeId::of::<Vec<graphene_core::vector::brush_stroke::BrushStroke>>() => Ok(TaggedValue::BrushStrokes(*downcast(input).unwrap())),
x if x == TypeId::of::<BrushCache>() => Ok(TaggedValue::BrushCache(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>>() => Ok(TaggedValue::Segments(*downcast(input).unwrap())),
x if x == TypeId::of::<crate::document::DocumentNode>() => Ok(TaggedValue::DocumentNode(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::GraphicGroup>() => Ok(TaggedValue::GraphicGroup(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::GraphicElement>() => Ok(TaggedValue::GraphicElement(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::ArtboardGroup>() => Ok(TaggedValue::ArtboardGroup(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())),
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::WasmSurfaceHandleFrame>() => {
let frame = *downcast::<graphene_core::WasmSurfaceHandleFrame>(input).unwrap();
Ok(TaggedValue::SurfaceFrame(frame.into()))
}
x if x == TypeId::of::<graphene_core::transform::Footprint>() => Ok(TaggedValue::Footprint(*downcast(input).unwrap())),
x if x == TypeId::of::<Vec<Color>>() => Ok(TaggedValue::Palette(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::vector::misc::CentroidType>() => Ok(TaggedValue::CentroidType(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::vector::misc::BooleanOperation>() => Ok(TaggedValue::BooleanOperation(*downcast(input).unwrap())),
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
}
}
pub fn from_type(input: &Type) -> Self {
match input {
Type::Generic(_) => {
log::warn!("Generic type should be resolved");
TaggedValue::None
}
Type::Concrete(concrete_type) => {
let Some(internal_id) = concrete_type.id else {
return TaggedValue::None;
};
use std::any::TypeId;
// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types
// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.
match internal_id {
x if x == TypeId::of::<()>() => TaggedValue::None,
x if x == TypeId::of::<String>() => TaggedValue::String(Default::default()),
x if x == TypeId::of::<u32>() => TaggedValue::U32(Default::default()),
x if x == TypeId::of::<u64>() => TaggedValue::U64(Default::default()),
x if x == TypeId::of::<f64>() => TaggedValue::F64(Default::default()),
x if x == TypeId::of::<bool>() => TaggedValue::Bool(Default::default()),
x if x == TypeId::of::<UVec2>() => TaggedValue::UVec2(Default::default()),
x if x == TypeId::of::<IVec2>() => TaggedValue::IVec2(Default::default()),
x if x == TypeId::of::<DVec2>() => TaggedValue::DVec2(Default::default()),
x if x == TypeId::of::<Option<DVec2>>() => TaggedValue::OptionalDVec2(Default::default()),
x if x == TypeId::of::<graphene_core::raster::Image<Color>>() => TaggedValue::Image(Default::default()),
x if x == TypeId::of::<ImaginateCache>() => TaggedValue::ImaginateCache(Default::default()),
x if x == TypeId::of::<graphene_core::raster::ImageFrame<Color>>() => TaggedValue::ImageFrame(Default::default()),
x if x == TypeId::of::<graphene_core::raster::Color>() => TaggedValue::Color(Default::default()),
x if x == TypeId::of::<Vec<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>>() => TaggedValue::Subpaths(vec![]),
x if x == TypeId::of::<Arc<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>>() => TaggedValue::None,
x if x == TypeId::of::<BlendMode>() => TaggedValue::BlendMode(Default::default()),
x if x == TypeId::of::<ImaginateSamplingMethod>() => TaggedValue::ImaginateSamplingMethod(Default::default()),
x if x == TypeId::of::<ImaginateMaskStartingFill>() => TaggedValue::ImaginateMaskStartingFill(Default::default()),
x if x == TypeId::of::<ImaginateController>() => TaggedValue::ImaginateController(Default::default()),
x if x == TypeId::of::<DAffine2>() => TaggedValue::DAffine2(Default::default()),
x if x == TypeId::of::<LuminanceCalculation>() => TaggedValue::LuminanceCalculation(Default::default()),
x if x == TypeId::of::<graphene_core::vector::VectorData>() => TaggedValue::VectorData(Default::default()),
x if x == TypeId::of::<graphene_core::vector::style::Fill>() => TaggedValue::Fill(Default::default()),
x if x == TypeId::of::<graphene_core::vector::style::Stroke>() => TaggedValue::Stroke(Default::default()),
x if x == TypeId::of::<Vec<f64>>() => TaggedValue::VecF64(Default::default()),
x if x == TypeId::of::<Vec<DVec2>>() => TaggedValue::VecDVec2(Default::default()),
x if x == TypeId::of::<graphene_core::raster::RedGreenBlue>() => TaggedValue::RedGreenBlue(graphene_core::raster::RedGreenBlue::Red),
x if x == TypeId::of::<graphene_core::raster::RedGreenBlueAlpha>() => TaggedValue::RedGreenBlueAlpha(graphene_core::raster::RedGreenBlueAlpha::Red),
x if x == TypeId::of::<graphene_core::raster::NoiseType>() => TaggedValue::NoiseType(graphene_core::raster::NoiseType::Perlin),
x if x == TypeId::of::<graphene_core::raster::FractalType>() => TaggedValue::FractalType(graphene_core::raster::FractalType::None),
x if x == TypeId::of::<graphene_core::raster::CellularDistanceFunction>() => TaggedValue::CellularDistanceFunction(graphene_core::raster::CellularDistanceFunction::Euclidean),
x if x == TypeId::of::<graphene_core::raster::CellularReturnType>() => TaggedValue::CellularReturnType(graphene_core::raster::CellularReturnType::Nearest),
x if x == TypeId::of::<graphene_core::raster::DomainWarpType>() => TaggedValue::DomainWarpType(graphene_core::raster::DomainWarpType::None),
x if x == TypeId::of::<graphene_core::raster::RelativeAbsolute>() => TaggedValue::RelativeAbsolute(graphene_core::raster::RelativeAbsolute::Relative),
x if x == TypeId::of::<graphene_core::raster::SelectiveColorChoice>() => TaggedValue::SelectiveColorChoice(graphene_core::raster::SelectiveColorChoice::Reds),
x if x == TypeId::of::<graphene_core::vector::style::LineCap>() => TaggedValue::LineCap(graphene_core::vector::style::LineCap::Butt),
x if x == TypeId::of::<graphene_core::vector::style::LineJoin>() => TaggedValue::LineJoin(graphene_core::vector::style::LineJoin::Miter),
x if x == TypeId::of::<graphene_core::vector::style::FillType>() => TaggedValue::FillType(graphene_core::vector::style::FillType::Solid),
x if x == TypeId::of::<graphene_core::vector::style::GradientType>() => TaggedValue::GradientType(Default::default()),
x if x == TypeId::of::<graphene_core::vector::style::GradientStops>() => TaggedValue::GradientStops(Default::default()),
x if x == TypeId::of::<graphene_core::quantization::QuantizationChannels>() => TaggedValue::Quantization(Default::default()),
x if x == TypeId::of::<Option<graphene_core::Color>>() => TaggedValue::OptionalColor(Default::default()),
x if x == TypeId::of::<Vec<graphene_core::uuid::ManipulatorGroupId>>() => TaggedValue::ManipulatorGroupIds(Default::default()),
x if x == TypeId::of::<graphene_core::text::Font>() => TaggedValue::Font(graphene_core::text::Font::new(
graphene_core::consts::DEFAULT_FONT_FAMILY.into(),
graphene_core::consts::DEFAULT_FONT_STYLE.into(),
)),
x if x == TypeId::of::<Vec<graphene_core::vector::brush_stroke::BrushStroke>>() => TaggedValue::BrushStrokes(Default::default()),
x if x == TypeId::of::<BrushCache>() => TaggedValue::BrushCache(Default::default()),
x if x == TypeId::of::<graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>>() => TaggedValue::Segments(Default::default()),
x if x == TypeId::of::<crate::document::DocumentNode>() => TaggedValue::DocumentNode(Default::default()),
x if x == TypeId::of::<graphene_core::GraphicGroup>() => TaggedValue::GraphicGroup(Default::default()),
x if x == TypeId::of::<graphene_core::GraphicElement>() => TaggedValue::GraphicElement(Default::default()),
x if x == TypeId::of::<graphene_core::Artboard>() => TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY),
x if x == TypeId::of::<graphene_core::ArtboardGroup>() => TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY),
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => TaggedValue::None,
x if x == TypeId::of::<RenderOutput>() => TaggedValue::None,
x if x == TypeId::of::<graphene_core::WasmSurfaceHandleFrame>() => TaggedValue::None,
x if x == TypeId::of::<graphene_core::transform::Footprint>() => TaggedValue::Footprint(Default::default()),
x if x == TypeId::of::<Vec<Color>>() => TaggedValue::Palette(Default::default()),
x if x == TypeId::of::<graphene_core::vector::misc::CentroidType>() => TaggedValue::CentroidType(Default::default()),
x if x == TypeId::of::<graphene_core::vector::misc::BooleanOperation>() => TaggedValue::BooleanOperation(Default::default()),
_ => TaggedValue::None,
}
}
Type::Fn(_, output) => TaggedValue::from_type(output),
Type::Future(_) => {
log::warn!("Future type not used");
TaggedValue::None
}
}
}
}
pub struct UpcastNode {
@@ -514,3 +221,53 @@ pub enum RenderOutput {
Svg(String),
Image(Vec<u8>),
}
/// We hash the floats and so-forth despite it not being reproducible because all inputs to the node graph must be hashed otherwise the graph execution breaks (so sorry about this hack)
trait FakeHash {
fn hash<H: core::hash::Hasher>(&self, state: &mut H);
}
mod fake_hash {
use super::*;
impl FakeHash for f64 {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.to_bits().hash(state)
}
}
impl FakeHash for DVec2 {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.to_array().iter().for_each(|x| x.to_bits().hash(state))
}
}
impl FakeHash for DAffine2 {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.to_cols_array().iter().for_each(|x| x.to_bits().hash(state))
}
}
impl<X: FakeHash> FakeHash for Option<X> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
if let Some(x) = self {
1.hash(state);
x.hash(state);
} else {
0.hash(state);
}
}
}
impl<X: FakeHash> FakeHash for Vec<X> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.len().hash(state);
self.iter().for_each(|x| x.hash(state))
}
}
impl<T: FakeHash, const N: usize> FakeHash for [T; N] {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.iter().for_each(|x| x.hash(state))
}
}
impl FakeHash for (f64, Color) {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
self.1.hash(state)
}
}
}

View File

@@ -9,7 +9,6 @@ pub struct Compiler {}
impl Compiler {
pub fn compile(&self, mut network: NodeNetwork) -> Result<impl Iterator<Item = ProtoNetwork>, String> {
println!("flattening");
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
for id in node_ids {
network.flatten(id);

View File

@@ -605,7 +605,8 @@ impl core::fmt::Debug for GraphErrorType {
"Node graph type error! If this just appeared while editing the graph,\n\
consider using undo to go back and try another way to connect the nodes.\n\
\n\
No node implementation exists for type ({parameters}).\n\
No node implementation exists for type:\n\
({parameters})\n\
\n\
Caused by{}:\n\
{}",
@@ -788,7 +789,6 @@ impl TypingContext {
match valid_impls.as_slice() {
[] => {
dbg!(&self.inferred);
let mut best_errors = usize::MAX;
let mut error_inputs = Vec::new();
for node_io in impls.keys() {

View File

@@ -2,7 +2,6 @@ use crate::Node;
use bezier_rs::{ManipulatorGroup, Subpath};
use graphene_core::transform::Footprint;
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::misc::BooleanOperation;
pub use graphene_core::vector::*;
@@ -69,10 +68,10 @@ fn from_svg_string(svg_string: &str) -> VectorData {
return VectorData::empty();
};
VectorData::from_subpaths(convert_usvg_path(path))
VectorData::from_subpaths(convert_usvg_path(path), false)
}
pub fn convert_usvg_path(path: &usvg::Path) -> Vec<Subpath<ManipulatorGroupId>> {
pub fn convert_usvg_path(path: &usvg::Path) -> Vec<Subpath<PointId>> {
let mut subpaths = Vec::new();
let mut groups = Vec::new();

View File

@@ -243,7 +243,7 @@ pub struct DrawImageFrameNode<Surface> {
}
#[node_macro::node_fn(DrawImageFrameNode)]
async fn draw_image_frame_node<'a: 'input>(image: ImageFrame<SRGBA8>, surface_handle: Arc<SurfaceHandle<HtmlCanvasElement>>) -> SurfaceHandleFrame<HtmlCanvasElement> {
async fn draw_image_frame_node<'a: 'input>(image: ImageFrame<SRGBA8>, surface_handle: Arc<WasmSurfaceHandle>) -> SurfaceHandleFrame<HtmlCanvasElement> {
let image_data = image.image.data;
let array: Clamped<&[u8]> = Clamped(bytemuck::cast_slice(image_data.as_slice()));
if image.image.width > 0 && image.image.height > 0 {
@@ -290,9 +290,9 @@ fn decode_image_node<'a: 'input>(data: Arc<[u8]>) -> ImageFrame<Color> {
pub use graph_craft::document::value::RenderOutput;
pub struct RenderNode<Data, Surface, Parameter> {
data: Data,
#[cfg(any(feature = "resvg", feature = "vello"))]
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
surface_handle: Surface,
#[cfg(not(any(feature = "resvg", feature = "vello")))]
#[cfg(not(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32")))]
surface_handle: PhantomData<Surface>,
parameter: PhantomData<Parameter>,
}
@@ -426,7 +426,7 @@ impl<'input, 'a: 'input, T: 'input + GraphicElementRendered, F: 'input + Future<
where
Data: Node<'input, Footprint, Output = F>,
Surface: Node<'input, (), Output = SurfaceFuture>,
SurfaceFuture: core::future::Future<Output = Arc<SurfaceHandle<HtmlCanvasElement>>>,
SurfaceFuture: core::future::Future<Output = Arc<SurfaceHandle<<crate::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>>,
{
type Output = core::pin::Pin<Box<dyn core::future::Future<Output = RenderOutput> + 'input>>;
@@ -441,7 +441,7 @@ where
let output_format = editor.render_config.export_format;
match output_format {
ExportFormat::Svg => render_svg(self.data.eval(footprint).await, SvgRender::new(), render_params, footprint),
#[cfg(any(feature = "resvg", feature = "vello"))]
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
ExportFormat::Canvas => render_canvas(self.data.eval(footprint).await, SvgRender::new(), render_params, footprint, editor, self.surface_handle.eval(()).await),
_ => todo!("Non-SVG render output for {output_format:?}"),
}
@@ -455,7 +455,7 @@ impl<'input, 'a: 'input, T: 'input + GraphicElementRendered, F: 'input + Future<
where
Data: Node<'input, (), Output = F>,
Surface: Node<'input, (), Output = SurfaceFuture>,
SurfaceFuture: core::future::Future<Output = Arc<SurfaceHandle<HtmlCanvasElement>>>,
SurfaceFuture: core::future::Future<Output = Arc<SurfaceHandle<<crate::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>>,
{
type Output = core::pin::Pin<Box<dyn core::future::Future<Output = RenderOutput> + 'input>>;
#[inline]
@@ -469,7 +469,7 @@ where
let output_format = editor.render_config.export_format;
match output_format {
ExportFormat::Svg => render_svg(self.data.eval(()).await, SvgRender::new(), render_params, footprint),
#[cfg(any(feature = "resvg", feature = "vello"))]
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
ExportFormat::Canvas => render_canvas(self.data.eval(()).await, SvgRender::new(), render_params, footprint, editor, self.surface_handle.eval(()).await),
_ => todo!("Non-SVG render output for {output_format:?}"),
}
@@ -481,9 +481,9 @@ impl<Data, Surface, Parameter> RenderNode<Data, Surface, Parameter> {
pub fn new(data: Data, surface_handle: Surface) -> Self {
Self {
data,
#[cfg(any(feature = "resvg", feature = "vello"))]
#[cfg(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32"))]
surface_handle,
#[cfg(not(any(feature = "resvg", feature = "vello")))]
#[cfg(not(all(any(feature = "resvg", feature = "vello"), target_arch = "wasm32")))]
surface_handle: PhantomData,
parameter: PhantomData,
}

View File

@@ -347,7 +347,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: graphene_core::GraphicGroup, fn_params: [Footprint => graphene_core::GraphicGroup]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: graphene_core::GraphicElement, fn_params: [Footprint => graphene_core::GraphicElement]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: Artboard, fn_params: [Footprint => graphene_core::Artboard]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Footprint, output: Artboard, fn_params: [Footprint => Artboard]),
async_node!(graphene_std::wasm_application_io::LoadResourceNode<_>, input: WasmEditorApi, output: Arc<[u8]>, params: [String]),
register_node!(graphene_std::wasm_application_io::DecodeImageNode, input: Arc<[u8]>, params: []),
async_node!(graphene_std::wasm_application_io::CreateSurfaceNode, input: WasmEditorApi, output: Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>, params: []),
@@ -522,7 +522,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
}
})
},
NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), vec![fn_type!(f32), fn_type!(f32), fn_type!(bool)]),
NodeIOTypes::new(concrete!(ImageFrame<Color>), concrete!(ImageFrame<Color>), vec![fn_type!(f64), fn_type!(f64), fn_type!(bool)]),
)],
vec![
(
@@ -666,7 +666,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: ImageFrame<Color>, params: [ImageFrame<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: QuantizationChannels, params: [QuantizationChannels]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Vec<DVec2>, params: [Vec<DVec2>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Arc<WasmSurfaceHandle>, params: [Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>, params: [Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: (), output: ShaderInputFrame<WgpuExecutor>, params: [ShaderInputFrame<WgpuExecutor>]),
#[cfg(feature = "gpu")]
@@ -683,23 +683,23 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
register_node!(graphene_core::quantization::QuantizeNode<_>, input: Color, params: [QuantizationChannels]),
register_node!(graphene_core::quantization::DeQuantizeNode<_>, input: PackedPixel, params: [QuantizationChannels]),
register_node!(graphene_core::ops::CloneNode<_>, input: &QuantizationChannels, params: []),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => ImageFrame<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => VectorData, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Artboard, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => ArtboardGroup, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Option<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Vec<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [ImageFrame<Color>, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [VectorData, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [GraphicGroup, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Artboard, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [bool, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [f32, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [f64, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [String, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Option<Color>, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Vec<Color>, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => ImageFrame<Color>, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => VectorData, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => GraphicGroup, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Artboard, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => ArtboardGroup, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Option<Color>, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Vec<Color>, () => Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [ImageFrame<Color>, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [VectorData, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [GraphicGroup, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Artboard, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [bool, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [f32, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [f64, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [String, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Option<Color>, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, params: [Vec<Color>, Arc<SurfaceHandle<<graphene_std::wasm_application_io::WasmApplicationIo as graphene_core::application_io::ApplicationIo>::Surface>>]),
async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: VectorData, output: ImageFrame<Color>, params: [Footprint, Arc<WasmSurfaceHandle>]),
async_node!(graphene_std::wasm_application_io::RasterizeNode<_, _>, input: GraphicGroup, output: ImageFrame<Color>, params: [Footprint, Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]),
@@ -800,9 +800,10 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
register_node!(graphene_core::vector::generator_nodes::SplineGenerator<_>, input: (), params: [Vec<DVec2>]),
register_node!(
graphene_core::vector::generator_nodes::PathGenerator<_>,
input: Vec<graphene_core::vector::bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>,
params: [Vec<graphene_core::uuid::ManipulatorGroupId>]
input: Vec<graphene_core::vector::bezier_rs::Subpath<graphene_core::vector::PointId>>,
params: [Vec<graphene_core::vector::PointId>]
),
register_node!(graphene_core::vector::PathModify<_>, input: VectorData, params: [graphene_core::vector::VectorModification]),
register_node!(graphene_core::text::TextGeneratorNode<_, _, _>, input: WasmEditorApi, params: [String, graphene_core::text::Font, f64]),
register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []),
register_node!(graphene_core::ExtractImageFrame, input: WasmEditorApi, params: []),

View File

@@ -33,14 +33,14 @@ use syn::{
///
/// - Mapping the type of the function's first argument (the node's primary input) to the impl'd `Node`'s generic type, e.g.:
///
/// ```
/// ```ignore
/// Node<'input, Color>
/// ```
///
/// for a `Color` primary input type.
/// - Mapping the type of the function's remaining arguments (the node's secondary inputs) to the given struct fields' generic types, e.g.:
///
/// ```
/// ```ignore
/// TheGivenStruct<S0, S1>
/// where S0: Node<'input, (), Output = f64>,
/// where S1: Node<'input, (), Output = f64>,
@@ -49,7 +49,7 @@ use syn::{
/// for two `f64` parameter (secondary input) types. Since Graphene works by having each function evaluate its upstream node as a lambda that returns output data, these secondary inputs are not directly `f64` values but rather `Node`s that output `f64` values when evaluated (in this case, with an empty input of `()`).
/// - Mapping the function's return type to the impl'd `Node` trait's associated type, e.g.:
///
/// ```
/// ```ignore
/// Output = Color
/// ```
///
@@ -67,7 +67,7 @@ use syn::{
///
/// The function body runs with the actual primary input value from the `eval` method's argument and the secondary input values from the `eval` method's `let` declarations. The result looks like this:
///
/// ```
/// ```ignore
/// fn eval(&'input self, color: Color) -> Self::Output {
/// let secondaryA = self.secondaryA.eval(());
/// let secondaryB = self.secondaryB.eval(());