mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Convert the node catalog to rank-polymorphic kernels, materialize stored values as ranked wires, and display wire rank in the graph
Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
committed by
Dennis Kobert
parent
83cfd0225a
commit
04d6c0d5cf
@@ -16,7 +16,7 @@ wgpu = ["dep:raster-types", "raster-types/wgpu"]
|
||||
# Local dependencies
|
||||
dyn-any = { workspace = true }
|
||||
core-types = { workspace = true }
|
||||
graphene-hash = { workspace = true }
|
||||
graphene-hash = { workspace = true, features = ["derive"] }
|
||||
vector-types = { workspace = true }
|
||||
text-nodes = { workspace = true }
|
||||
graphene-resource = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use core_types::transform::Footprint;
|
||||
use dyn_any::DynAny;
|
||||
use glam::DVec2;
|
||||
use graphene_hash::CacheHash;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ptr::addr_of;
|
||||
@@ -61,7 +62,7 @@ pub trait GetEditorPreferences {
|
||||
fn max_render_region_area(&self) -> u32;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, graphene_hash::CacheHash)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, CacheHash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum ExportFormat {
|
||||
#[default]
|
||||
@@ -69,14 +70,14 @@ pub enum ExportFormat {
|
||||
Raster,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, graphene_hash::CacheHash)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, CacheHash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TimingInformation {
|
||||
pub time: f64,
|
||||
pub animation_time: Duration,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, graphene_hash::CacheHash)]
|
||||
#[derive(Debug, Default, Clone, Copy, PartialEq, DynAny, CacheHash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct RenderConfig {
|
||||
pub viewport: Footprint,
|
||||
@@ -138,7 +139,7 @@ impl<Io> Hash for EditorApi<Io> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Io> core_types::graphene_hash::CacheHash for EditorApi<Io> {
|
||||
impl<Io> CacheHash for EditorApi<Io> {
|
||||
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
core::hash::Hash::hash(self, state);
|
||||
}
|
||||
|
||||
@@ -601,6 +601,11 @@ impl ItemAttributeValues {
|
||||
self.0.iter().map(|(key, value)| (key.as_str(), &**value))
|
||||
}
|
||||
|
||||
/// Returns a type-erased reference to the value of the attribute with the given key, if it exists.
|
||||
pub fn get_any(&self, key: &str) -> Option<&dyn std::any::Any> {
|
||||
self.0.iter().find_map(|(existing_key, value)| if existing_key == key { Some((**value).as_any()) } else { None })
|
||||
}
|
||||
|
||||
/// Returns a debug-formatted string representation of the attribute value for the given key, if it exists.
|
||||
/// The `overrides` function can provide custom formatting for specific type.
|
||||
pub fn display_value(&self, key: &str, overrides: fn(&dyn std::any::Any) -> Option<String>) -> Option<String> {
|
||||
|
||||
@@ -61,6 +61,27 @@ impl Clampable for DVec2 {
|
||||
}
|
||||
}
|
||||
|
||||
// Implement for ranked wires (element-wise clamping across the frame)
|
||||
use crate::list::{Item, List};
|
||||
impl<T: Clampable> Clampable for Item<T> {
|
||||
fn clamp_hard_min(self, min: f64) -> Self {
|
||||
let (element, attributes) = self.into_parts();
|
||||
Item::from_parts(element.clamp_hard_min(min), attributes)
|
||||
}
|
||||
fn clamp_hard_max(self, max: f64) -> Self {
|
||||
let (element, attributes) = self.into_parts();
|
||||
Item::from_parts(element.clamp_hard_max(max), attributes)
|
||||
}
|
||||
}
|
||||
impl<T: Clampable> Clampable for List<T> {
|
||||
fn clamp_hard_min(self, min: f64) -> Self {
|
||||
self.into_iter().map(|item| item.clamp_hard_min(min)).collect()
|
||||
}
|
||||
fn clamp_hard_max(self, max: f64) -> Self {
|
||||
self.into_iter().map(|item| item.clamp_hard_max(max)).collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
#[derive(serde::Deserialize)]
|
||||
struct LegacyTable<T> {
|
||||
|
||||
@@ -77,8 +77,7 @@ impl Convert<DVec2, ()> for DVec2 {
|
||||
}
|
||||
|
||||
/// Constructs `Self` from a single anchor point at the given position. Implemented by the vector crate's
|
||||
/// path type so the `Convert` impl below can build a single-point path without core-types depending on
|
||||
/// that crate (mirroring how [`ListConvert`] bridges per-item list conversions).
|
||||
/// path type so a position wire can convert to a single-point path without core-types depending on that crate.
|
||||
pub trait FromAnchorPosition {
|
||||
fn from_anchor_position(position: DVec2) -> Self;
|
||||
}
|
||||
|
||||
@@ -217,6 +217,23 @@ impl From<()> for Footprint {
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes an item's `transform` attribute by baking it into the underlying value itself.
|
||||
pub trait BakeTransform {
|
||||
fn bake_transform(&mut self, transform: &DAffine2);
|
||||
}
|
||||
|
||||
impl BakeTransform for DAffine2 {
|
||||
fn bake_transform(&mut self, transform: &DAffine2) {
|
||||
*self = *transform * *self;
|
||||
}
|
||||
}
|
||||
|
||||
impl BakeTransform for DVec2 {
|
||||
fn bake_transform(&mut self, transform: &DAffine2) {
|
||||
*self = transform.transform_point2(*self);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ApplyTransform {
|
||||
fn apply_transform(&mut self, modification: &DAffine2);
|
||||
fn left_apply_transform(&mut self, modification: &DAffine2);
|
||||
|
||||
@@ -228,36 +228,33 @@ fn flatten_graphic_list<T>(content: List<Graphic>, extract_variant: fn(Graphic)
|
||||
/// enabling type-directed casting of typed `List`s from a `Graphic` value.
|
||||
pub trait TryFromGraphic: Clone + Sized {
|
||||
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>>;
|
||||
|
||||
/// The leaf's element, borrowed, where `graphic` is this type's variant.
|
||||
fn leaf_of<'a>(graphic: &'a Graphic<'_>) -> Option<&'a Self>;
|
||||
}
|
||||
|
||||
impl TryFromGraphic for Vector {
|
||||
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
|
||||
if let Graphic::Vector(t) = graphic { Some(List::new_from_element(t)) } else { None }
|
||||
}
|
||||
macro_rules! try_from_graphic {
|
||||
($($variant:ident: $element:ty;)*) => {
|
||||
$(
|
||||
impl TryFromGraphic for $element {
|
||||
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
|
||||
if let Graphic::$variant(t) = graphic { Some(List::new_from_element(t)) } else { None }
|
||||
}
|
||||
|
||||
fn leaf_of<'a>(graphic: &'a Graphic<'_>) -> Option<&'a Self> {
|
||||
if let Graphic::$variant(t) = graphic { Some(t) } else { None }
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl TryFromGraphic for Raster<CPU> {
|
||||
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
|
||||
if let Graphic::RasterCPU(t) = graphic { Some(List::new_from_element(t)) } else { None }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromGraphic for Color {
|
||||
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
|
||||
if let Graphic::Color(t) = graphic { Some(List::new_from_element(t)) } else { None }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromGraphic for Gradient {
|
||||
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
|
||||
if let Graphic::Gradient(t) = graphic { Some(List::new_from_element(t)) } else { None }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromGraphic for String {
|
||||
fn try_from_graphic(graphic: Graphic) -> Option<List<Self>> {
|
||||
if let Graphic::Text(t) = graphic { Some(List::new_from_element(t)) } else { None }
|
||||
}
|
||||
try_from_graphic! {
|
||||
Vector: Vector;
|
||||
RasterCPU: Raster<CPU>;
|
||||
Color: Color;
|
||||
Gradient: Gradient;
|
||||
Text: String;
|
||||
}
|
||||
|
||||
// Local trait to convert types to List<Graphic> (avoids orphan rule issues)
|
||||
|
||||
@@ -1767,7 +1767,7 @@ fn render_vector_vello<S: LaneSource<Element = Vector>>(source: &S, scene: &mut
|
||||
}
|
||||
|
||||
fn collect_vector_metadata<S: LaneSource<Element = Vector>>(source: &S, metadata: &mut RenderMetadata, footprint: Footprint, caller_element_id: Option<NodeId>) {
|
||||
// Aggregate all items' targets per element_id so multi-item lists (e.g. 'Text' node with "Separate Glyphs" active) produce hit areas for every glyph.
|
||||
// Aggregate all items' targets per element_id so multi-item lists (e.g. the "Text to Vector Glyphs" node) produce hit areas for every glyph.
|
||||
// Targets are baked relative to item 0's transform since `Graphic::collect_metadata` records that as `local_transforms[element_id]`.
|
||||
let item_zero_transform: DAffine2 = if source.lane_count() > 0 { source.attr::<Transform>(0) } else { DAffine2::IDENTITY };
|
||||
let item_zero_inverse = if transform_is_invertible(item_zero_transform) {
|
||||
|
||||
@@ -59,6 +59,13 @@ impl core_types::ops::FromAnchorPosition for Vector {
|
||||
}
|
||||
}
|
||||
|
||||
// Lets a position wire feed a ranked vector connector through the input adapter's element conversion
|
||||
impl From<DVec2> for Vector {
|
||||
fn from(position: DVec2) -> Self {
|
||||
<Self as core_types::ops::FromAnchorPosition>::from_anchor_position(position)
|
||||
}
|
||||
}
|
||||
|
||||
// Identity item conversion so `List<Vector>` satisfies the blanket `Convert<List<U>, ()> for List<T>`, letting its
|
||||
// auto-inserted input wrapper be a `ConvertNode` (which also accepts a `DVec2` anchor position) rather than an `IntoNode`.
|
||||
impl core_types::ops::ListConvert<Vector> for Vector {
|
||||
@@ -67,6 +74,15 @@ impl core_types::ops::ListConvert<Vector> for Vector {
|
||||
}
|
||||
}
|
||||
|
||||
impl core_types::transform::BakeTransform for Vector {
|
||||
fn bake_transform(&mut self, transform: &glam::DAffine2) {
|
||||
for (_, point) in self.point_domain.positions_mut() {
|
||||
*point = transform.transform_point2(*point);
|
||||
}
|
||||
self.segment_domain.transform(*transform);
|
||||
}
|
||||
}
|
||||
|
||||
impl Vector {
|
||||
/// Add a subpath to this vector path.
|
||||
pub fn append_subpath(&mut self, subpath: impl Borrow<Subpath<PointId>>, preserve_id: bool) {
|
||||
|
||||
@@ -20,7 +20,7 @@ fn upload_to_texture(device: &wgpu::Device, queue: &wgpu::Queue, image: &Raster<
|
||||
device.create_texture_with_data(
|
||||
queue,
|
||||
&TextureDescriptor {
|
||||
label: Some("upload_texture node texture"),
|
||||
label: Some("upload_to_texture staging texture"),
|
||||
size: Extent3d {
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
|
||||
Reference in New Issue
Block a user