mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Accept any paint element on the fill and stroke inputs
This commit is contained in:
@@ -1060,7 +1060,7 @@ impl NodeNetwork {
|
||||
let (tagged_value, exposed) = match previous_export {
|
||||
NodeInput::Value { tagged_value, exposed } => (tagged_value, exposed),
|
||||
NodeInput::Reflection(reflect) => match reflect {
|
||||
DocumentNodeMetadata::DocumentNodePath => (TaggedValue::NodeIdPath(core_types::list::NodeIdPath::from(path.to_vec())).into(), false),
|
||||
DocumentNodeMetadata::DocumentNodePath => (TaggedValue::NodeIdPath(path.to_vec()).into(), false),
|
||||
DocumentNodeMetadata::SourceId => {
|
||||
let source_id = Self::source_id_for_path(path);
|
||||
if let Some(context_features) = context_features.as_deref_mut() {
|
||||
|
||||
@@ -6,7 +6,7 @@ use brush_nodes::{BrushCache, Stroke};
|
||||
use core_types::color::SRGBA8;
|
||||
use core_types::context::Context;
|
||||
use core_types::gpoll::GPoll;
|
||||
use core_types::list::{Item, List, NodeIdPath};
|
||||
use core_types::list::{Item, List};
|
||||
use core_types::registry::SourceHandle;
|
||||
use core_types::transform::Footprint;
|
||||
use core_types::uuid::NodeId;
|
||||
@@ -108,9 +108,9 @@ macro_rules! tagged_value {
|
||||
// =======================
|
||||
#[serde(skip)]
|
||||
RenderOutput(RenderOutput),
|
||||
/// Path to the consumer of a `NodeInput::Reflection(DocumentNodePath)`. Materializes an `Item<NodeIdPath>` at runtime via `to_dynany`/`to_any` during graph flattening, matching the ranked connectors it feeds.
|
||||
/// Path to the consumer of a `NodeInput::Reflection(DocumentNodePath)`, in the `Vec<NodeId>` form our node catalog consumes.
|
||||
#[serde(skip)]
|
||||
NodeIdPath(NodeIdPath),
|
||||
NodeIdPath(Vec<NodeId>),
|
||||
/// The `DocumentNode` value carried by an `Extract` proto node, populated at flatten time by `resolve_extract_nodes`. The on-disk placeholder uses `TypeDefault(concrete!(DocumentNode))`.
|
||||
#[serde(skip)]
|
||||
DocumentNode(DocumentNode),
|
||||
@@ -174,6 +174,14 @@ macro_rules! tagged_value {
|
||||
if name == core_types::normalize_type_name(std::any::type_name::<$type_default>()) { return Box::new(<$type_default>::default()); }
|
||||
};
|
||||
}
|
||||
macro_rules! check_list {
|
||||
($element:ty) => {
|
||||
if name == core_types::normalize_type_name(std::any::type_name::<List<$element>>()) { return Box::new(List::<$element>::default()); }
|
||||
};
|
||||
}
|
||||
for_each_list_type_default!(check_list);
|
||||
for_each_item_type_default!(check);
|
||||
for_each_bare_type_default!(check);
|
||||
Self::from_type_or_none(&Type::Concrete(td.clone())).to_dynany()
|
||||
}
|
||||
Self::F64Array(values) => {
|
||||
@@ -219,6 +227,14 @@ macro_rules! tagged_value {
|
||||
if name == core_types::normalize_type_name(std::any::type_name::<$type_default>()) { return Arc::new(<$type_default>::default()); }
|
||||
};
|
||||
}
|
||||
macro_rules! check_list {
|
||||
($element:ty) => {
|
||||
if name == core_types::normalize_type_name(std::any::type_name::<List<$element>>()) { return Arc::new(List::<$element>::default()); }
|
||||
};
|
||||
}
|
||||
for_each_list_type_default!(check_list);
|
||||
for_each_item_type_default!(check);
|
||||
for_each_bare_type_default!(check);
|
||||
Self::from_type_or_none(&Type::Concrete(td.clone())).to_any()
|
||||
}
|
||||
Self::F64Array(values) => {
|
||||
@@ -282,7 +298,7 @@ macro_rules! tagged_value {
|
||||
// NON-SERIALIZED VARIANTS
|
||||
// =======================
|
||||
Self::RenderOutput(_) => concrete!(RenderOutput),
|
||||
Self::NodeIdPath(_) => concrete!(core_types::list::NodeIdPath),
|
||||
Self::NodeIdPath(_) => concrete!(Vec<NodeId>),
|
||||
Self::DocumentNode(_) => concrete!(DocumentNode),
|
||||
Self::ContextModification(_) => concrete!(ContextModification),
|
||||
Self::EditorApi(_) => concrete!(Arc<PlatformEditorApi>),
|
||||
@@ -328,7 +344,7 @@ macro_rules! tagged_value {
|
||||
Self::BrushCache(_) => scalar::<BrushCache>(),
|
||||
$( Self::$identifier(_) => scalar::<$ty>(), )*
|
||||
Self::RenderOutput(_) => scalar::<RenderOutput>(),
|
||||
Self::NodeIdPath(_) => scalar::<core_types::list::NodeIdPath>(),
|
||||
Self::NodeIdPath(_) => scalar::<Vec<NodeId>>(),
|
||||
Self::DocumentNode(_) => scalar::<DocumentNode>(),
|
||||
Self::ContextModification(_) => scalar::<ContextModification>(),
|
||||
Self::EditorApi(_) => scalar::<Arc<PlatformEditorApi>>(),
|
||||
@@ -366,8 +382,8 @@ macro_rules! tagged_value {
|
||||
Self::from_type_or_none(&Type::Concrete(td)).to_edge()
|
||||
}
|
||||
Self::F64Array(values) => Ok(leveled_record_value_source(values)),
|
||||
Self::Color(color) => Ok(record_value_source(color)),
|
||||
Self::GradientRamp(stops) => Ok(leveled_record_value_source(vec![stops])),
|
||||
Self::Color(color) => Ok(leveled_record_value_source(vec![color])),
|
||||
Self::GradientRamp(ramp) => Ok(leveled_record_value_source(vec![Gradient::from(ramp)])),
|
||||
Self::Strokes(strokes) => Ok(leveled_record_value_source(strokes)),
|
||||
Self::DashPattern(lengths) => Ok(record_value_source(DashPattern(lengths.into_iter().map(core_types::list::Item::new_from_element).collect()))),
|
||||
Self::BoxCorners(values) => Ok(record_value_source(BoxCorners(values.into_iter().map(core_types::list::Item::new_from_element).collect()))),
|
||||
@@ -489,7 +505,6 @@ macro_rules! tagged_value {
|
||||
pub fn from_type(input: &Type) -> Option<Self> {
|
||||
match input {
|
||||
Type::Generic(_) => None,
|
||||
Type::Record(inner) => Self::from_type(inner),
|
||||
Type::Concrete(concrete_type) => {
|
||||
let name = concrete_type.name.as_ref();
|
||||
// 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.
|
||||
@@ -500,6 +515,13 @@ macro_rules! tagged_value {
|
||||
$( if name == core_types::normalize_type_name(std::any::type_name::<$ty>()) { return Some(TaggedValue::$identifier(Default::default())) } )*
|
||||
if name == core_types::normalize_type_name(std::any::type_name::<List<f64>>()) { return Some(TaggedValue::F64Array(Vec::new())) }
|
||||
if name == core_types::normalize_type_name(std::any::type_name::<List<Stroke>>()) { return Some(TaggedValue::Strokes(Vec::new())) }
|
||||
// The manual variants are not in the generated `$ty` list, so their defaults are named here
|
||||
if name == core_types::normalize_type_name(std::any::type_name::<BoxCorners>()) { return Some(TaggedValue::BoxCorners(Vec::new())) }
|
||||
if name == core_types::normalize_type_name(std::any::type_name::<DashPattern>()) { return Some(TaggedValue::DashPattern(Vec::new())) }
|
||||
if name == core_types::normalize_type_name(std::any::type_name::<List<BoxCorners>>()) { return Some(TaggedValue::BoxCorners(Vec::new())) }
|
||||
if name == core_types::normalize_type_name(std::any::type_name::<List<DashPattern>>()) { return Some(TaggedValue::DashPattern(Vec::new())) }
|
||||
if name == core_types::normalize_type_name(std::any::type_name::<BrushCache>()) { return Some(TaggedValue::BrushCache(Default::default())) }
|
||||
if name == core_types::normalize_type_name(std::any::type_name::<GradientRamp>()) { return Some(TaggedValue::GradientRamp(GradientRamp::default())) }
|
||||
// Leveled inputs type by their element; each element name maps to the
|
||||
// same tagged default as its legacy list form.
|
||||
if name == core_types::normalize_type_name(std::any::type_name::<Color>()) { return Some(TaggedValue::Color(Color::default())) }
|
||||
@@ -516,6 +538,12 @@ macro_rules! tagged_value {
|
||||
};
|
||||
}
|
||||
for_each_bare_type_default!(check);
|
||||
macro_rules! check_list {
|
||||
($element:ty) => {
|
||||
if name == core_types::normalize_type_name(std::any::type_name::<List<$element>>()) { return Some(TaggedValue::TypeDefault(core_types::descriptor!(List<$element>))); }
|
||||
};
|
||||
}
|
||||
for_each_list_type_default!(check_list);
|
||||
None
|
||||
}
|
||||
Type::Fn(_, output) => TaggedValue::from_type(output),
|
||||
@@ -793,6 +821,10 @@ impl TaggedValue {
|
||||
() if ty == TypeId::of::<Color>() => to_color(string).map(TaggedValue::Color)?,
|
||||
// The Fill/Stroke paint wires carry `Graphic` or `Gradient` elements, so a paint default parses through the element recursion as a color or gradient literal
|
||||
() if ty == TypeId::of::<Graphic>() => to_color(string).map(TaggedValue::Color)?,
|
||||
// A rank-1 paint wire names its list type, so it parses the same literal as its element does
|
||||
() if ty == TypeId::of::<List<Graphic>>() => to_color(string).map(TaggedValue::Color)?,
|
||||
() if ty == TypeId::of::<List<Color>>() => to_color(string).map(TaggedValue::Color)?,
|
||||
() if ty == TypeId::of::<List<Gradient>>() => to_gradient(string).map(|gradient| TaggedValue::GradientRamp(gradient.into()))?,
|
||||
() if ty == TypeId::of::<Gradient>() => to_gradient(string).map(|gradient| TaggedValue::GradientRamp(gradient.into()))?,
|
||||
() if ty == TypeId::of::<ReferencePoint>() => to_reference_point(string).map(TaggedValue::ReferencePoint)?,
|
||||
() if ty == TypeId::of::<DashPattern>() => TaggedValue::DashPattern(core_types::misc::parse_f64_list(string)),
|
||||
@@ -803,7 +835,6 @@ impl TaggedValue {
|
||||
}
|
||||
Type::Fn(_, output) => TaggedValue::from_primitive_string(string, output),
|
||||
Type::Future(fut) => TaggedValue::from_primitive_string(string, fut),
|
||||
Type::Record(element) => TaggedValue::from_primitive_string(string, element),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -875,6 +906,35 @@ pub fn deserialize_tagged_value_with_legacy_migration<'de, D: serde::Deserialize
|
||||
}
|
||||
return Ok(MemoHash::new(TaggedValue::TypeDefault(core_types::descriptor!(List<Vector>))));
|
||||
}
|
||||
// Documents written against the structural rank model store the payload as a `Type`
|
||||
// (`{"List": {"Concrete": {..}}}`). Our one wire kind reduces that to the element it names,
|
||||
// keeping the `List<..>` spelling for a rank-1 wire so the existing name lookups still match.
|
||||
"TypeDefault" if content.as_object().is_some_and(|c| c.contains_key("Concrete") || c.contains_key("Item") || c.contains_key("List")) => {
|
||||
fn structural_type_name(value: &serde_json::Value) -> Option<String> {
|
||||
let object = value.as_object()?;
|
||||
if let Some(concrete) = object.get("Concrete") {
|
||||
return Some(concrete.as_object()?.get("name")?.as_str()?.to_string());
|
||||
}
|
||||
if let Some(item) = object.get("Item") {
|
||||
return structural_type_name(item);
|
||||
}
|
||||
if let Some(list) = object.get("List") {
|
||||
return Some(format!("core_types::list::List<{}>", structural_type_name(list)?));
|
||||
}
|
||||
object.get("Generic")?.as_str().map(str::to_string)
|
||||
}
|
||||
let Some(name) = structural_type_name(&content) else {
|
||||
return Err(serde::de::Error::custom("a structural TypeDefault payload named no type"));
|
||||
};
|
||||
let descriptor = TypeDescriptor {
|
||||
id: None,
|
||||
name: std::borrow::Cow::Owned(name),
|
||||
alias: None,
|
||||
size: 0,
|
||||
align: 0,
|
||||
};
|
||||
return Ok(MemoHash::new(TaggedValue::TypeDefault(descriptor)));
|
||||
}
|
||||
// The `TypeDefault` payload is a bare `TypeDescriptor`: our one wire kind needs no structural rank in it
|
||||
"TypeDefault" if content.as_object().is_some_and(|c| c.contains_key("name")) => {
|
||||
let descriptor: TypeDescriptor = serde_json::from_value(content.clone()).map_err(serde::de::Error::custom)?;
|
||||
|
||||
@@ -12,7 +12,9 @@ use graphene_std::registry::{ConstructionError, SourceHandle, NodeIOTypes, Regis
|
||||
use graphene_std::runtime::RuntimeHandle;
|
||||
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::{Context, Graphic, ProtoNodeIdentifier, concrete};
|
||||
use graphene_std::gradient::Gradient;
|
||||
use graphene_std::brush::Stroke;
|
||||
use graphene_std::{Color, Context, Graphic, ProtoNodeIdentifier, concrete};
|
||||
use node_registry_macros::{convert_node, into_node};
|
||||
use std::collections::HashMap;
|
||||
#[cfg(feature = "gpu")]
|
||||
@@ -28,6 +30,16 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, Vec<RegistryEntry>> {
|
||||
#[cfg(feature = "gpu")]
|
||||
into_node!(from: List<Raster<GPU>>, to: List<Raster<GPU>>),
|
||||
convert_node!(from: List<Vector>, to: List<Graphic>),
|
||||
convert_node!(from: List<Color>, to: List<Graphic>),
|
||||
convert_node!(from: List<Gradient>, to: List<Graphic>),
|
||||
convert_node!(from: List<String>, to: List<Graphic>),
|
||||
convert_node!(from: List<Stroke>, to: List<Graphic>),
|
||||
convert_node!(from: Vector, to: Graphic),
|
||||
convert_node!(from: Color, to: Graphic),
|
||||
convert_node!(from: Gradient, to: Graphic),
|
||||
convert_node!(from: String, to: Graphic),
|
||||
convert_node!(from: Stroke, to: Graphic),
|
||||
convert_node!(from: Raster<CPU>, to: Graphic),
|
||||
convert_node!(from: List<Raster<CPU>>, to: List<Graphic>),
|
||||
#[cfg(feature = "gpu")]
|
||||
convert_node!(from: List<Raster<GPU>>, to: List<Graphic>),
|
||||
|
||||
@@ -280,7 +280,8 @@ impl PartialEq for TypeDescriptor {
|
||||
/// Graph runtime type information used for type inference.
|
||||
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||
#[derive(Clone, PartialEq, Eq, Hash, graphene_hash::CacheHash)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
|
||||
#[cfg_attr(feature = "serde", serde(rename_all = "PascalCase"))]
|
||||
pub enum Type {
|
||||
/// A wrapper for some type variable used within the inference system. Resolved at inference time and replaced with a concrete type.
|
||||
Generic(Cow<'static, str>),
|
||||
@@ -294,6 +295,41 @@ pub enum Type {
|
||||
Record(Box<Type>),
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for Type {
|
||||
/// Documents written against the structural rank model store `Item` and `List` wire types.
|
||||
/// Our one wire kind has neither, so an `Item` reduces to the element it wraps and a `List`
|
||||
/// becomes the concrete `List<..>` type it names.
|
||||
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||
#[derive(serde::Deserialize)]
|
||||
enum Stored {
|
||||
Generic(Cow<'static, str>),
|
||||
Concrete(TypeDescriptor),
|
||||
Fn(Box<Type>, Box<Type>),
|
||||
Future(Box<Type>),
|
||||
Record(Box<Type>),
|
||||
Item(Box<Type>),
|
||||
List(Box<Type>),
|
||||
}
|
||||
|
||||
Ok(match Stored::deserialize(deserializer)? {
|
||||
Stored::Generic(name) => Type::Generic(name),
|
||||
Stored::Concrete(descriptor) => Type::Concrete(descriptor),
|
||||
Stored::Fn(input, output) => Type::Fn(input, output),
|
||||
Stored::Future(inner) => Type::Future(inner),
|
||||
Stored::Record(inner) => Type::Record(inner),
|
||||
Stored::Item(element) => *element,
|
||||
Stored::List(element) => Type::Concrete(TypeDescriptor {
|
||||
id: None,
|
||||
name: Cow::Owned(format!("core_types::list::List<{}>", element.identifier_name())),
|
||||
alias: None,
|
||||
size: 0,
|
||||
align: 0,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Type {
|
||||
fn default() -> Self {
|
||||
concrete!(())
|
||||
|
||||
@@ -516,6 +516,46 @@ impl<'e> ListConvert<Graphic<'e>> for Raster<GPU> {
|
||||
Graphic::RasterGPU(self)
|
||||
}
|
||||
}
|
||||
// A leveled paint input types by its element, so the same embedding is needed one rank down.
|
||||
macro_rules! convert_leaf_to_graphic {
|
||||
($($element:ty),* $(,)?) => {
|
||||
$(
|
||||
impl<'e> core_types::ops::Convert<Graphic<'e>, ()> for $element {
|
||||
fn convert(self, _: core_types::transform::Footprint, _: ()) -> Graphic<'e> {
|
||||
core_types::ops::ListConvert::convert_item(self)
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
convert_leaf_to_graphic!(Vector, Raster<CPU>, Raster<GPU>, Color, Gradient, String, Stroke);
|
||||
|
||||
// The paint wires accept any leaf element, the role master's `From<X> for Graphic` embedding adapters play.
|
||||
impl<'e> ListConvert<Graphic<'e>> for Graphic<'e> {
|
||||
fn convert_item(self) -> Graphic<'e> {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<'e> ListConvert<Graphic<'e>> for Color {
|
||||
fn convert_item(self) -> Graphic<'e> {
|
||||
Graphic::Color(self)
|
||||
}
|
||||
}
|
||||
impl<'e> ListConvert<Graphic<'e>> for Gradient {
|
||||
fn convert_item(self) -> Graphic<'e> {
|
||||
Graphic::Gradient(self)
|
||||
}
|
||||
}
|
||||
impl<'e> ListConvert<Graphic<'e>> for String {
|
||||
fn convert_item(self) -> Graphic<'e> {
|
||||
Graphic::Text(self)
|
||||
}
|
||||
}
|
||||
impl<'e> ListConvert<Graphic<'e>> for Stroke {
|
||||
fn convert_item(self) -> Graphic<'e> {
|
||||
Graphic::Stroke(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderComplexity for Graphic<'_> {
|
||||
fn render_complexity(&self) -> usize {
|
||||
|
||||
@@ -375,29 +375,40 @@ fn default_gradient_paint(paint: &mut List<Graphic>, bounds: Option<[DVec2; 2]>,
|
||||
|
||||
/// The materialized paint level as the canonical owned paint list, content
|
||||
/// kept in its native form.
|
||||
fn paint_table(paint: core_types::node::List<'_, Graphic<'_>>) -> List<Graphic<'static>> {
|
||||
/// A paint level as its legacy list, embedding each leaf element into `Graphic` and keeping its lane attributes.
|
||||
/// This is the role master's `From<X> for Graphic` embedding adapters play for its monomorphic paint input.
|
||||
fn paint_table<P>(paint: core_types::node::List<'_, P>) -> List<Graphic<'static>>
|
||||
where
|
||||
P: Clone + Send + Sync + dyn_any::StaticTypeSized + core_types::ops::ListConvert<Graphic<'static>>,
|
||||
{
|
||||
let item = paint.as_group_item();
|
||||
graphic_types::graphic::run_to_list::<Graphic>(&item).expect("a paint level holds graphic lanes")
|
||||
let typed = graphic_types::graphic::run_to_list::<P>(&item).expect("a paint level holds its declared lanes");
|
||||
let mut out = List::new();
|
||||
for row in typed.into_iter() {
|
||||
let (element, attributes) = row.into_parts();
|
||||
out.push(Item::from_parts(core_types::ops::ListConvert::convert_item(element), attributes));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Applies a fill style to the vector content, giving an appearance to the area within the interior of the geometry.
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("fill_properties"))]
|
||||
fn fill<'e>(
|
||||
fn fill<'e, P: Clone + Send + Sync + dyn_any::StaticTypeSized + core_types::ops::ListConvert<Graphic<'static>>>(
|
||||
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
|
||||
/// The content with vector paths to apply the fill style to.
|
||||
(element, _content_fill): (Vector, Attr<Fill>),
|
||||
/// The fill to paint the path with.
|
||||
#[default(Color::BLACK)]
|
||||
paint: IList<Graphic<'static>>,
|
||||
#[implementations(Graphic<'static>, Color, Gradient)]
|
||||
paint: IList<P>,
|
||||
_backup_color: IList<Color>,
|
||||
_backup_gradient: IList<Gradient>,
|
||||
_gradient_form: GradientForm,
|
||||
_gradient_spread: GradientSpread,
|
||||
_has_transform: bool,
|
||||
_transform: DAffine2,
|
||||
) -> Result<(Vector, Attr<'e, Fill>), Interrupt> {
|
||||
let mut paint = paint_table(paint);
|
||||
default_gradient_paint(&mut paint, element.bounding_box(), _gradient_form, _gradient_spread, _has_transform.then_some(_transform));
|
||||
default_gradient_paint(&mut paint, element.bounding_box(), _gradient_form, GradientSpread::default(), _has_transform.then_some(_transform));
|
||||
let parked = park_paint(ctx.arena(), paint)?;
|
||||
Ok((element, Attr(Some(parked))))
|
||||
}
|
||||
@@ -413,7 +424,6 @@ fn fill_graphic_leveled<'e>(
|
||||
_backup_color: IList<Color>,
|
||||
_backup_gradient: IList<Gradient>,
|
||||
_gradient_form: GradientForm,
|
||||
_gradient_spread: GradientSpread,
|
||||
_has_transform: bool,
|
||||
_transform: DAffine2,
|
||||
) -> Result<(Graphic<'static>, Attr<'e, Fill>), Interrupt> {
|
||||
@@ -422,20 +432,21 @@ fn fill_graphic_leveled<'e>(
|
||||
_ => None,
|
||||
};
|
||||
let mut paint = paint_table(paint);
|
||||
default_gradient_paint(&mut paint, bounds, _gradient_form, _gradient_spread, _has_transform.then_some(_transform));
|
||||
default_gradient_paint(&mut paint, bounds, _gradient_form, GradientSpread::default(), _has_transform.then_some(_transform));
|
||||
let parked = park_paint(ctx.arena(), paint)?;
|
||||
Ok((element, Attr(Some(parked))))
|
||||
}
|
||||
|
||||
/// Applies a stroke style to the vector content, giving an appearance to the area within the outline of the geometry.
|
||||
#[node_macro::node(category("Vector: Style"), path(graphene_core::vector), properties("stroke_properties"))]
|
||||
fn stroke<'e>(
|
||||
fn stroke<'e, P: Clone + Send + Sync + dyn_any::StaticTypeSized + core_types::ops::ListConvert<Graphic<'static>>>(
|
||||
ctx: impl Ctx + ExtractArena<'e> + ExtractIndex + InjectIndex + Copy,
|
||||
/// The content with vector paths to apply the stroke style to.
|
||||
(element, content_transform): (Vector, Attr<TransformAttr>),
|
||||
/// The stroke paint.
|
||||
#[default(Color::BLACK)]
|
||||
paint: IList<Graphic<'static>>,
|
||||
#[implementations(Graphic<'static>, Color, Gradient)]
|
||||
paint: IList<P>,
|
||||
/// The stroke thickness.
|
||||
#[unit(" px")]
|
||||
#[default(2.)]
|
||||
|
||||
Reference in New Issue
Block a user