Improve native UI network

This commit is contained in:
Adam
2025-09-03 11:27:53 -07:00
parent 405bd5d36f
commit b47b3b72cb
18 changed files with 553 additions and 129 deletions

View File

@@ -427,6 +427,16 @@ impl Color {
Color { red, green, blue, alpha }.to_linear_srgb().map_rgb(|channel| channel * alpha)
}
pub fn from_rgba8(red: u8, green: u8, blue: u8, alpha: u8) -> Color {
let map_range = |int_color| int_color as f32 / 255.;
let red = map_range(red);
let green = map_range(green);
let blue = map_range(blue);
let alpha = map_range(alpha);
Color { red, green, blue, alpha }
}
/// Create a [Color] from a hue, saturation, lightness and alpha (all between 0 and 1)
///
/// # Examples
@@ -940,6 +950,17 @@ impl Color {
Some(Color::from_rgb8_srgb(r, g, b))
}
pub fn from_rgba8_no_srgb(color_str: &str) -> Option<Color> {
if color_str.len() != 6 {
return None;
}
let r = u8::from_str_radix(&color_str[0..2], 16).ok()?;
let g = u8::from_str_radix(&color_str[2..4], 16).ok()?;
let b = u8::from_str_radix(&color_str[4..6], 16).ok()?;
let a = 255;
Some(Color::from_rgba8(r, g, b, a))
}
/// Linearly interpolates between two colors based on t.
///
/// T must be between 0 and 1.

View File

@@ -1,38 +1,55 @@
use graphene_core_shaders::{Ctx, color::Color};
use kurbo::{BezPath, Point};
use crate::{ExtractFootprint, table::Table, vector::Vector};
use crate::{
node_graph_overlay::{
nodes_and_wires::{draw_layers, draw_nodes},
types::NodeGraphOverlayData,
ui_context::{UIContext, UIRuntimeResponse},
},
table::Table,
transform::ApplyTransform,
vector::Vector,
};
pub mod consts;
pub mod nodes_and_wires;
pub mod types;
pub mod ui_context;
#[node_macro::node(category(""))]
pub fn generate_nodes(_: impl Ctx, _node_graph_overlay_data: types::NodeGraphOverlayData) -> Table<Vector> {
Table::new()
#[node_macro::node(skip_impl)]
pub fn generate_nodes(_: impl Ctx, node_graph_overlay_data: NodeGraphOverlayData) -> Table<Vector> {
let mut nodes_and_wires = Table::new();
let layers = draw_layers(&node_graph_overlay_data.nodes_to_render);
nodes_and_wires.extend(layers);
let nodes = draw_nodes(&node_graph_overlay_data.nodes_to_render);
nodes_and_wires.extend(nodes);
nodes_and_wires
}
#[node_macro::node(category(""))]
pub fn transform_nodes(_ctx: impl Ctx + ExtractFootprint, nodes: Table<Vector>) -> Table<Vector> {
#[node_macro::node(skip_impl)]
pub fn transform_nodes(ui_context: UIContext, mut nodes: Table<Vector>) -> Table<Vector> {
let matrix = ui_context.transform.to_daffine2();
nodes.apply_transform(&matrix);
nodes
}
#[node_macro::node(category(""))]
pub fn dot_grid_background(ctx: impl Ctx + ExtractFootprint, opacity: f64) -> Table<Vector> {
let Some(footprint) = ctx.try_footprint() else {
log::error!("Could not get footprint from context in dot_grid_background");
return Table::new();
};
#[node_macro::node(skip_impl)]
pub fn dot_grid_background(ui_context: UIContext, opacity: f64) -> Table<Vector> {
// From --color-2-mildblack: --color-2-mildblack-rgb: 34, 34, 34;
let gray = (34. / 255.) as f32;
let Some(bg_color) = Color::from_rgbaf32(gray, gray, gray, opacity as f32) else {
let Some(bg_color) = Color::from_rgbaf32(gray, gray, gray, (opacity / 100.) as f32) else {
log::error!("Could not create color in dot grid background");
return Table::new();
};
let mut bez_path = BezPath::new();
let p0 = Point::new(0., 0.); // bottom-left
let p1 = Point::new(footprint.resolution.x as f64, 0.); // bottom-right
let p2 = Point::new(footprint.resolution.x as f64, footprint.resolution.y as f64); // top-right
let p3 = Point::new(0., footprint.resolution.y as f64); // top-left
let p1 = Point::new(ui_context.resolution.x as f64, 0.); // bottom-right
let p2 = Point::new(ui_context.resolution.x as f64, ui_context.resolution.y as f64); // top-right
let p3 = Point::new(0., ui_context.resolution.y as f64); // top-left
bez_path.move_to(p0);
bez_path.line_to(p1);
@@ -45,3 +62,14 @@ pub fn dot_grid_background(ctx: impl Ctx + ExtractFootprint, opacity: f64) -> Ta
Table::new_from_element(vector)
}
#[node_macro::node(skip_impl)]
pub fn node_graph_ui_extend(_: impl Ctx, new: Table<Vector>, mut base: Table<Vector>) -> Table<Vector> {
base.extend(new);
base
}
#[node_macro::node(skip_impl)]
pub fn send_render(ui_context: UIContext, render: String) -> () {
let _ = ui_context.response_sender.send(UIRuntimeResponse::OverlaySVG(render));
}

View File

@@ -0,0 +1,42 @@
pub const GRID_SIZE: f64 = 24.;
pub const BEZ_PATH_TOLERANCE: f64 = 0.1;
// Keep in sync with colors in Editor.svelte
pub const COLOR_0_BLACK: &str = "000000";
pub const COLOR_1_NEARBLACK: &str = "111111";
pub const COLOR_2_MILDBLACK: &str = "222222";
pub const COLOR_3_DARKGRAY: &str = "333333";
pub const COLOR_4_DIMGRAY: &str = "444444";
pub const COLOR_5_DULLGRAY: &str = "555555";
pub const COLOR_6_LOWERGRAY: &str = "666666";
pub const COLOR_7_MIDDLEGRAY: &str = "777777";
pub const COLOR_8_UPPERGRAY: &str = "888888";
pub const COLOR_9_PALEGRAY: &str = "999999";
pub const COLOR_A_SOFTGRAY: &str = "AAAAAA";
pub const COLOR_B_LIGHTGRAY: &str = "BBBBBB";
pub const COLOR_C_BRIGHTGRAY: &str = "CCCCCC";
pub const COLOR_D_MILDWHITE: &str = "DDDDDD";
pub const COLOR_E_NEARWHITE: &str = "EEEEEE";
pub const COLOR_F_WHITE: &str = "FFFFFF";
pub const COLOR_ERROR_RED: &str = "D6536E";
pub const COLOR_WARNING_YELLOW: &str = "D5AA43";
pub const COLOR_DATA_GENERAL: &str = "CFCFCF";
pub const COLOR_DATA_GENERAL_DIM: &str = "8A8A8A";
pub const COLOR_DATA_NUMBER: &str = "C9A699";
pub const COLOR_DATA_NUMBER_DIM: &str = "886B60";
pub const COLOR_DATA_ARTBOARD: &str = "FBF9EB";
pub const COLOR_DATA_ARTBOARD_DIM: &str = "B9B9A9";
pub const COLOR_DATA_GRAPHIC: &str = "68C587";
pub const COLOR_DATA_GRAPHIC_DIM: &str = "37754C";
pub const COLOR_DATA_RASTER: &str = "E4BB72";
pub const COLOR_DATA_RASTER_DIM: &str = "9A7B43";
pub const COLOR_DATA_VECTOR: &str = "65BBE5";
pub const COLOR_DATA_VECTOR_DIM: &str = "417892";
pub const COLOR_DATA_COLOR: &str = "CE6EA7";
pub const COLOR_DATA_COLOR_DIM: &str = "924071";
pub const COLOR_DATA_GRADIENT: &str = "AF81EB";
pub const COLOR_DATA_GRADIENT_DIM: &str = "6C489B";
pub const COLOR_DATA_TYPOGRAPHY: &str = "EEA7A7";
pub const COLOR_DATA_TYPOGRAPHY_DIM: &str = "955252";

View File

@@ -0,0 +1,175 @@
use graphene_core_shaders::color::{AlphaMut, Color};
use kurbo::{BezPath, RoundedRect, Shape};
use crate::{
node_graph_overlay::{
consts::*,
types::{FrontendGraphDataType, FrontendNodeToRender},
},
table::{Table, TableRow},
vector::{Vector, style::Fill},
};
pub fn draw_nodes(nodes: &Vec<FrontendNodeToRender>) -> Table<Vector> {
let mut node_table = Table::new();
for node_to_render in nodes {
if let Some(frontend_node) = node_to_render.node_or_layer.node.as_ref() {
let x = frontend_node.position.x as f64 * GRID_SIZE;
let y = frontend_node.position.y as f64 * GRID_SIZE + GRID_SIZE / 2.;
let w = GRID_SIZE * 5.0;
let number_of_exposed_inputs = frontend_node.inputs.iter().skip(1).filter(|x| x.is_some()).count();
let height = 1 + number_of_exposed_inputs;
let h = height as f64 * GRID_SIZE;
let border_rect = RoundedRect::new(x, y, x + w, y + h, 2.);
let bez_path = border_rect.to_path(BEZ_PATH_TOLERANCE);
let mut border_vector = Vector::from_bezpath(bez_path);
let primary_output_color = frontend_node.outputs[0]
.as_ref()
.map(|primary_output| primary_output.data_type.data_color_dim())
.unwrap_or(FrontendGraphDataType::General.data_color_dim());
let border_color = Color::from_rgba8_no_srgb(primary_output_color).unwrap();
border_vector.style.stroke = Some(crate::vector::style::Stroke::new(Some(border_color), 1.));
let node_color = if node_to_render.metadata.selected {
let mut selection_color = Color::from_rgba8_no_srgb(COLOR_F_WHITE).unwrap();
selection_color.set_alpha(0.15);
selection_color
} else {
let mut bg_color = Color::from_rgba8_no_srgb(COLOR_0_BLACK).unwrap();
bg_color.set_alpha(0.33);
bg_color
};
border_vector.style.fill = crate::vector::style::Fill::Solid(node_color);
// Make primary input brighter
if number_of_exposed_inputs == 0 {
// Draw the first row with rounded bottom corners
node_table.push(TableRow::new_from_element(node_first_row(x, y, true)));
} else {
// Draw the first row without rounded bottom corners
node_table.push(TableRow::new_from_element(node_first_row(x, y, false)));
// for node_index in 0..(number_of_exposed_inputs - 1) {
// node_table.push(TableRow::new_from_element(node_secondary_row(x, y, node_index + 1, false)));
// }
// // Draw the last row with bottom corners
// node_table.push(TableRow::new_from_element(node_secondary_row(x, y, number_of_exposed_inputs, true)));
};
node_table.push(TableRow::new_from_element(border_vector));
}
}
node_table
}
pub fn draw_layers(nodes: &Vec<FrontendNodeToRender>) -> Table<Vector> {
let mut layer_table = Table::new();
for node_to_render in nodes {
if let Some(frontend_layer) = node_to_render.node_or_layer.layer.as_ref() {
let chain_width = if frontend_layer.chain_width > 0 {
frontend_layer.chain_width as f64 * GRID_SIZE + 0.5 * GRID_SIZE
} else {
0.
};
let x0 = frontend_layer.position.x as f64 * GRID_SIZE - chain_width + 0.5 * GRID_SIZE;
let y0 = frontend_layer.position.y as f64 * GRID_SIZE;
let h = 2. * GRID_SIZE;
let w = chain_width + 8. * GRID_SIZE - 0.5 * GRID_SIZE;
let rect = RoundedRect::new(x0, y0, x0 + w, y0 + h, 8.);
let bez_path = rect.to_path(BEZ_PATH_TOLERANCE);
let mut vector = Vector::from_bezpath(bez_path);
let border_color = Color::from_rgba8_no_srgb(COLOR_5_DULLGRAY).unwrap();
vector.style.stroke = Some(crate::vector::style::Stroke::new(Some(border_color), 1.));
let mut background = if node_to_render.metadata.selected {
Color::from_rgba8_no_srgb(COLOR_6_LOWERGRAY).unwrap()
} else {
Color::from_rgba8_no_srgb(COLOR_0_BLACK).unwrap()
};
background.set_alpha(0.33);
vector.style.fill = crate::vector::style::Fill::Solid(background);
layer_table.push(TableRow::new_from_element(vector));
}
}
layer_table
}
fn node_first_row(x0: f64, y0: f64, rounded_bottom: bool) -> Vector {
let x1 = x0 + GRID_SIZE * 5.;
let y1 = y0 + GRID_SIZE;
let r = 2.;
let bez_path = if rounded_bottom {
let mut path = BezPath::new();
// Start at bottom-left
path.move_to((x0, y1));
// Left side up
path.line_to((x0, y0 + r));
// Top-left corner arc
path.quad_to((x0, y0), (x0 + r, y0));
// Top edge
path.line_to((x1 - r, y0));
// Top-right corner arc
path.quad_to((x1, y0), (x1, y0 + r));
// Right side down
path.line_to((x1, y1));
// Bottom edge
path.line_to((x0, y1));
path.close_path();
path
} else {
RoundedRect::new(x0, y0, x1, y1, r).to_path(BEZ_PATH_TOLERANCE)
};
let mut vector = Vector::from_bezpath(bez_path);
let mut color = Color::from_rgba8_no_srgb(COLOR_F_WHITE).unwrap();
color.set_alpha(0.05);
vector.style.fill = Fill::Solid(color);
vector
}
// fn node_secondary_row(x0: f64, y: f64, index: usize, rounded_bottom: bool) -> Vector {
// let y0 = y + index as f64 * GRID_SIZE;
// let x1 = x0 + GRID_SIZE * 5.;
// let y1 = y0 + GRID_SIZE;
// let r = 2.;
// let bez_path = if rounded_bottom {
// let mut path = BezPath::new();
// path.move_to((x0, y0));
// // Top edge
// path.line_to((x1, y0));
// // Right side down
// path.line_to((x1, y1 - r));
// // Bottom-right corner arc
// path.quad_to((x1, y1), (x1 - r, y1));
// // Bottom edge
// path.line_to((x0 + r, y1));
// // Bottom-left corner arc
// path.quad_to((x0, y1), (x0, y1 - r));
// // Left side up
// path.line_to((x0, y0));
// path.close_path();
// path
// } else {
// Rect::new(x0, y0, x1, y1).to_path(BEZ_PATH_TOLERANCE)
// };
// let mut vector = Vector::from_bezpath(bez_path);
// let mut color = Color::from_rgba8_no_srgb(COLOR_0_BLACK).unwrap();
// color.set_alpha(0.33);
// vector.style.fill = Fill::Solid(color);
// vector
// }

View File

@@ -1,7 +1,30 @@
use crate::uuid::NodeId;
use glam::{DAffine2, DVec2};
use crate::{node_graph_overlay::consts::*, uuid::NodeId};
use std::hash::{Hash, Hasher};
#[derive(Clone, Debug, Default, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct NodeGraphTransform {
pub scale: f64,
pub x: f64,
pub y: f64,
}
impl NodeGraphTransform {
pub fn to_daffine2(&self) -> DAffine2 {
DAffine2::from_scale_angle_translation(DVec2::splat(self.scale), 0.0, DVec2::new(self.x, self.y))
}
}
impl Hash for NodeGraphTransform {
fn hash<H: Hasher>(&self, state: &mut H) {
self.scale.to_bits().hash(state);
self.x.to_bits().hash(state);
self.y.to_bits().hash(state);
}
}
#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct NodeGraphOverlayData {
pub nodes_to_render: Vec<FrontendNodeToRender>,
pub open: bool,
@@ -21,7 +44,6 @@ pub struct FrontendNodeToRender {
// Metadata that is common to nodes and layers
#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeMetadata {
#[serde(rename = "nodeId")]
pub node_id: NodeId,
@@ -41,7 +63,6 @@ pub struct FrontendNodeMetadata {
}
#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNode {
// pub position: FrontendNodePosition,
pub position: FrontendXY,
@@ -50,7 +71,6 @@ pub struct FrontendNode {
}
#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendLayer {
#[serde(rename = "bottomInput")]
pub bottom_input: FrontendGraphInput,
@@ -71,7 +91,6 @@ pub struct FrontendLayer {
}
#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendXY {
pub x: i32,
pub y: i32,
@@ -93,15 +112,32 @@ pub struct FrontendXY {
// pub stack: Option<u32>,
// }
// Should be an enum but those are hard to serialize/deserialize to TS
#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeOrLayer {
pub node: Option<FrontendNode>,
pub layer: Option<FrontendLayer>,
}
#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)]
impl FrontendNodeOrLayer {
pub fn to_enum(self) -> NodeOrLayer {
let node_or_layer = if let Some(node) = self.node {
Some(NodeOrLayer::Node(node))
} else if let Some(layer) = self.layer {
Some(NodeOrLayer::Layer(layer))
} else {
None
};
node_or_layer.unwrap()
}
}
pub enum NodeOrLayer {
Node(FrontendNode),
Layer(FrontendLayer),
}
#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendGraphInput {
#[serde(rename = "dataType")]
pub data_type: FrontendGraphDataType,
@@ -118,7 +154,6 @@ pub struct FrontendGraphInput {
}
#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendGraphOutput {
#[serde(rename = "dataType")]
pub data_type: FrontendGraphDataType,
@@ -140,7 +175,6 @@ pub struct FrontendExport {
}
#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendExports {
/// If the primary export is not visible, then it is None.
pub exports: Vec<Option<FrontendExport>>,
@@ -149,7 +183,6 @@ pub struct FrontendExports {
}
#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendImport {
pub port: FrontendGraphOutput,
pub wires: Vec<String>,
@@ -168,3 +201,32 @@ pub enum FrontendGraphDataType {
Gradient,
Typography,
}
impl FrontendGraphDataType {
pub fn data_color(&self) -> &'static str {
match self {
FrontendGraphDataType::General => COLOR_DATA_GENERAL,
FrontendGraphDataType::Number => COLOR_DATA_NUMBER,
FrontendGraphDataType::Artboard => COLOR_DATA_ARTBOARD,
FrontendGraphDataType::Graphic => COLOR_DATA_GRAPHIC,
FrontendGraphDataType::Raster => COLOR_DATA_RASTER,
FrontendGraphDataType::Vector => COLOR_DATA_VECTOR,
FrontendGraphDataType::Color => COLOR_DATA_COLOR,
FrontendGraphDataType::Gradient => COLOR_DATA_GRADIENT,
FrontendGraphDataType::Typography => COLOR_DATA_TYPOGRAPHY,
}
}
pub fn data_color_dim(&self) -> &'static str {
match self {
FrontendGraphDataType::General => COLOR_DATA_GENERAL_DIM,
FrontendGraphDataType::Number => COLOR_DATA_NUMBER_DIM,
FrontendGraphDataType::Artboard => COLOR_DATA_ARTBOARD_DIM,
FrontendGraphDataType::Graphic => COLOR_DATA_GRAPHIC_DIM,
FrontendGraphDataType::Raster => COLOR_DATA_RASTER_DIM,
FrontendGraphDataType::Vector => COLOR_DATA_VECTOR_DIM,
FrontendGraphDataType::Color => COLOR_DATA_COLOR_DIM,
FrontendGraphDataType::Gradient => COLOR_DATA_GRADIENT_DIM,
FrontendGraphDataType::Typography => COLOR_DATA_TYPOGRAPHY_DIM,
}
}
}

View File

@@ -0,0 +1,26 @@
use std::sync::{Arc, mpsc::Sender};
use glam::UVec2;
use graphene_core_shaders::{Ctx, context::ArcCtx};
use crate::node_graph_overlay::types::NodeGraphTransform;
pub type UIContext = Arc<UIContextImpl>;
#[derive(Debug, Clone, dyn_any::DynAny)]
pub struct UIContextImpl {
pub transform: NodeGraphTransform,
pub resolution: UVec2,
pub response_sender: Sender<UIRuntimeResponse>,
}
#[derive(Debug, Clone, dyn_any::DynAny)]
pub enum UIRuntimeResponse {
RuntimeReady,
OverlaySVG(String),
OverlayTexture(wgpu::Texture),
// OverlayClickTargets(NodeId, ClickTarget)
}
impl Ctx for UIContextImpl {}
impl ArcCtx for UIContextImpl {}

View File

@@ -672,7 +672,7 @@ impl TypingContext {
// If the node has a value input we can infer the return type from it
ConstructionArgs::Value(ref v) => {
// TODO: This should return a reference to the value
let types = NodeIOTypes::new(concrete!(Context), Type::Future(Box::new(v.ty())), vec![]);
let types = NodeIOTypes::new(generic!(T), Type::Future(Box::new(v.ty())), vec![]);
self.inferred.insert(node_id, types.clone());
return Ok(types);
}

View File

@@ -1,3 +1,4 @@
use glam::DVec2;
use graph_craft::document::value::RenderOutput;
pub use graph_craft::document::value::RenderOutputType;
pub use graph_craft::wasm_application_io::*;
@@ -6,6 +7,7 @@ use graphene_core::Artboard;
use graphene_core::gradient::GradientStops;
#[cfg(target_family = "wasm")]
use graphene_core::math::bbox::Bbox;
use graphene_core::node_graph_overlay::ui_context::UIContext;
use graphene_core::raster::image::Image;
use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
@@ -349,3 +351,24 @@ async fn render<'a: 'n, T: 'n + Render + WasmNotSend>(
};
RenderOutput { data, metadata }
}
#[node_macro::node(skip_impl)]
async fn render_node_graph_ui<T: Render + WasmNotSend>(
ui_context: UIContext,
#[implementations(
UIContext -> Table<Artboard>,
UIContext -> Table<Graphic>,
UIContext -> Table<Vector>,
UIContext -> Table<Raster<CPU>>,
UIContext -> Table<Color>,
UIContext -> Table<GradientStops>,
)]
data: impl Node<UIContext, Output = T>,
) -> String {
let data = data.eval(ui_context.clone()).await;
let render_params = RenderParams::default();
let mut render = SvgRender::new();
data.render_svg(&mut render, &render_params);
render.format_svg(DVec2::ZERO, ui_context.resolution.as_dvec2());
render.svg.to_svg_string()
}

View File

@@ -21,6 +21,8 @@ use graphene_std::application_io::{ImageTexture, SurfaceFrame};
use graphene_std::brush::brush_cache::BrushCache;
use graphene_std::brush::brush_stroke::BrushStroke;
use graphene_std::gradient::GradientStops;
use graphene_std::node_graph_overlay::types::NodeGraphOverlayData;
use graphene_std::node_graph_overlay::ui_context::UIContext;
use graphene_std::table::Table;
use graphene_std::transform::Footprint;
use graphene_std::uuid::NodeId;
@@ -251,6 +253,12 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
node_io
},
),
async_node!(graphene_core::node_graph_overlay::GenerateNodesNode<_>, input: UIContext, fn_params: [UIContext => NodeGraphOverlayData]),
async_node!(graphene_core::node_graph_overlay::TransformNodesNode<_>, input: UIContext, fn_params: [UIContext =>Table<Vector>]),
async_node!(graphene_core::node_graph_overlay::DotGridBackgroundNode<_>, input: UIContext, fn_params: [UIContext =>f64]),
async_node!(graphene_core::node_graph_overlay::NodeGraphUiExtendNode<_, _>, input: UIContext, fn_params: [UIContext =>Table<Vector>, UIContext =>Table<Vector>]),
async_node!(graphene_std::wasm_application_io::RenderNodeGraphUiNode<_>, input: UIContext, fn_params: [UIContext =>Table<Vector>]),
async_node!(graphene_core::node_graph_overlay::SendRenderNode<_>, input: UIContext, fn_params: [UIContext => String]),
];
// =============
// CONVERT NODES