General purpose value node

This commit is contained in:
Adam
2025-07-05 20:33:50 -07:00
parent 8803cb4079
commit 007812077b
9 changed files with 339 additions and 92 deletions

View File

@@ -13,28 +13,28 @@ impl<'i, const N: u32, I> Node<'i, I> for IntNode<N> {
}
}
#[derive(Default, Debug, Clone, Copy)]
pub struct ValueNode<T>(pub T);
// #[derive(Default, Debug, Clone, Copy)]
// pub struct ValueNode<T>(pub T);
impl<'i, T: 'i, I> Node<'i, I> for ValueNode<T> {
type Output = &'i T;
#[inline(always)]
fn eval(&'i self, _input: I) -> Self::Output {
&self.0
}
}
// impl<'i, T: 'i, I> Node<'i, I> for ValueNode<T> {
// type Output = &'i T;
// #[inline(always)]
// fn eval(&'i self, _input: I) -> Self::Output {
// &self.0
// }
// }
impl<T> ValueNode<T> {
pub const fn new(value: T) -> ValueNode<T> {
ValueNode(value)
}
}
// impl<T> ValueNode<T> {
// pub const fn new(value: T) -> ValueNode<T> {
// ValueNode(value)
// }
// }
impl<T> From<T> for ValueNode<T> {
fn from(value: T) -> Self {
ValueNode::new(value)
}
}
// impl<T> From<T> for ValueNode<T> {
// fn from(value: T) -> Self {
// ValueNode::new(value)
// }
// }
#[derive(Default, Debug, Clone, Copy)]
pub struct AsRefNode<T: AsRef<U>, U>(pub T, PhantomData<U>);

View File

@@ -9,11 +9,13 @@ use graphene_brush::brush_cache::BrushCache;
use graphene_brush::brush_stroke::BrushStroke;
use graphene_core::raster::Image;
use graphene_core::raster_types::CPU;
use graphene_core::registry::{ChoiceTypeStatic, ChoiceWidgetHint, VariantMetadata};
use graphene_core::transform::ReferencePoint;
use graphene_core::uuid::NodeId;
use graphene_core::vector::style::Fill;
use graphene_core::{Color, MemoHash, Node, Type};
use graphene_core::{AsU32, Color, MemoHash, Node, Type};
use graphene_svg_renderer::RenderMetadata;
use std::borrow::Cow;
use std::fmt::Display;
use std::hash::Hash;
use std::marker::PhantomData;
@@ -37,6 +39,65 @@ macro_rules! tagged_value {
EditorApi(Arc<WasmEditorApi>)
}
#[derive(Clone, Copy, Debug)]
#[repr(u32)]
pub enum TaggedValueChoice {
None,
$($identifier,)*
}
impl TaggedValueChoice {
pub fn to_tagged_value(&self) -> TaggedValue {
match self {
TaggedValueChoice::None => TaggedValue::None,
$(TaggedValueChoice::$identifier => TaggedValue::$identifier(Default::default()),)*
}
}
pub fn from_tagged_value(value: &TaggedValue) -> Option<Self> {
match value {
TaggedValue::None => Some(TaggedValueChoice::None),
$( TaggedValue::$identifier(_) => Some(TaggedValueChoice::$identifier), )*
_ => None
}
}
}
impl ChoiceTypeStatic for TaggedValueChoice {
const WIDGET_HINT: ChoiceWidgetHint = ChoiceWidgetHint::Dropdown; // or your preferred hint
const DESCRIPTION: Option<&'static str> = Some("Select a value");
fn list() -> &'static [&'static [(Self, VariantMetadata)]] {
const COUNT: usize = 0 $( + one!($identifier) )*;
// Define static array of (choice, metadata) tuples
static VALUES: [(TaggedValueChoice, VariantMetadata); 1 + COUNT] = [
(TaggedValueChoice::None, VariantMetadata {
name: Cow::Borrowed(stringify!(None)),
label: Cow::Borrowed(stringify!(None)),
docstring: None,
icon: None,
}),
$(
(TaggedValueChoice::$identifier, VariantMetadata {
name: Cow::Borrowed(stringify!($identifier)),
label: Cow::Borrowed(stringify!($identifier)),
docstring: None,
icon: None,
}),
)*
];
// Static reference to the slice of VALUES
static LIST: [&'static [(TaggedValueChoice, VariantMetadata)]; 1] = [
&VALUES,
];
&LIST
}
}
// 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 {
@@ -156,6 +217,12 @@ macro_rules! tagged_value {
};
}
macro_rules! one {
($anything:tt) => {
1
};
}
tagged_value! {
// ===============
// PRIMITIVE TYPES
@@ -388,6 +455,12 @@ impl Display for TaggedValue {
}
}
impl AsU32 for TaggedValueChoice {
fn as_u32(&self) -> u32 {
*self as u32
}
}
pub struct UpcastNode {
value: MemoHash<TaggedValue>,
}

View File

@@ -46,3 +46,21 @@ pub fn input_node<O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<(),
pub fn downcast_node<I: StaticType, O: StaticType>(n: SharedNodeContainer) -> DowncastBothNode<I, O> {
DowncastBothNode::new(n)
}
// Same idea as the identity node, but with a hidden primary input in order to have an auto generated properties widget for the value
pub struct Value {
value: SharedNodeContainer,
}
impl<'i> Node<'i, Any<'i>> for Value {
type Output = DynFuture<'i, Any<'i>>;
fn eval(&'i self, input: Any<'i>) -> Self::Output {
Box::pin(async move { self.value.eval(input).await })
}
}
impl Value {
pub const fn new(value: SharedNodeContainer) -> Self {
Value { value }
}
}

View File

@@ -14,7 +14,7 @@ use graphene_std::Context;
use graphene_std::GraphicElement;
#[cfg(feature = "gpu")]
use graphene_std::any::DowncastBothNode;
use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode};
use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode, Value};
use graphene_std::application_io::{ImageTexture, SurfaceFrame};
#[cfg(feature = "gpu")]
use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle};
@@ -114,6 +114,16 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
vec![Type::Fn(Box::new(generic!(T)), Box::new(generic!(V))), Type::Fn(Box::new(generic!(V)), Box::new(generic!(U)))],
),
),
(
ProtoNodeIdentifier::new("graphene_core::any::ValueNode"),
|args| {
Box::pin(async move {
let node = Value::new(args[0].clone());
node.into_type_erased()
})
},
NodeIOTypes::new(generic!(T), generic!(U), vec![Type::Fn(Box::new(concrete!(Context)), Box::new(generic!(U)))]),
),
#[cfg(feature = "gpu")]
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => WgpuSurface]),
#[cfg(feature = "gpu")]