wires and clip paths

This commit is contained in:
Adam
2025-09-04 23:09:39 -07:00
parent c5ab893f46
commit 3566fb8c10
10 changed files with 239 additions and 154 deletions

View File

@@ -4,7 +4,7 @@ use crate::{
Graphic,
node_graph_overlay::{
background::generate_background,
nodes_and_wires::{draw_layers, draw_nodes},
nodes_and_wires::{draw_layers, draw_nodes, draw_wires},
types::NodeGraphOverlayData,
ui_context::{UIContext, UIRuntimeResponse},
},
@@ -19,11 +19,14 @@ pub mod types;
pub mod ui_context;
#[node_macro::node(skip_impl)]
pub fn generate_nodes(_: impl Ctx, node_graph_overlay_data: NodeGraphOverlayData) -> Table<Graphic> {
pub fn generate_nodes(_: impl Ctx, mut node_graph_overlay_data: NodeGraphOverlayData) -> Table<Graphic> {
let mut nodes_and_wires = Table::new();
let layers = draw_layers(&node_graph_overlay_data.nodes_to_render);
nodes_and_wires.extend(layers);
let wires = draw_wires(&mut node_graph_overlay_data.nodes_to_render);
nodes_and_wires.extend(wires);
let nodes = draw_nodes(&node_graph_overlay_data.nodes_to_render);
nodes_and_wires.extend(nodes);

View File

@@ -13,7 +13,10 @@ use crate::{
table::{Table, TableRow},
text::{self, TextAlign, TypesettingConfig},
transform::ApplyTransform,
vector::{Vector, style::Fill},
vector::{
Vector,
style::{Fill, Stroke},
},
};
pub fn draw_nodes(nodes: &Vec<FrontendNodeToRender>) -> Table<Graphic> {
@@ -22,12 +25,13 @@ pub fn draw_nodes(nodes: &Vec<FrontendNodeToRender>) -> Table<Graphic> {
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 node_width = 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 number_of_exposed_outputs = frontend_node.outputs.iter().skip(1).filter(|x| x.is_some()).count();
let number_of_rows = 1 + number_of_exposed_inputs.max(number_of_exposed_outputs);
let node_height = number_of_rows as f64 * GRID_SIZE;
let node_rect = RoundedRect::new(x, y, x + w, y + h, 2.);
let node_rect = RoundedRect::new(x, y, x + node_width, y + node_height, 2.);
let node_bez_path = node_rect.to_path(BEZ_PATH_TOLERANCE);
// Background table
@@ -54,7 +58,51 @@ pub fn draw_nodes(nodes: &Vec<FrontendNodeToRender>) -> Table<Graphic> {
};
node_table.push(TableRow::new_from_element(Graphic::Vector(bg_table)));
// Border table
// Border mask table is the region where to display the border
let mut border_mask_path = BezPath::new();
border_mask_path.move_to((-2., -2.));
border_mask_path.line_to((node_width + 2., -2.));
if frontend_node.outputs[0].is_some() {
border_mask_path.line_to((node_width + 2., 4.));
border_mask_path.line_to((node_width - 2., 4.));
border_mask_path.line_to((node_width - 2., 20.));
}
border_mask_path.line_to((node_width + 2., 20.));
for row in 1..number_of_rows {
border_mask_path.line_to((node_width + 2., row as f64 * GRID_SIZE + 4.));
if frontend_node.outputs.get(row).is_some_and(|output| output.is_some()) {
border_mask_path.line_to((node_width - 2., row as f64 * GRID_SIZE + 4.));
border_mask_path.line_to((node_width - 2., row as f64 * GRID_SIZE + 20.));
border_mask_path.line_to((node_width + 2., row as f64 * GRID_SIZE + 20.));
}
}
border_mask_path.line_to((node_width + 2., number_of_rows as f64 * GRID_SIZE + 2.));
border_mask_path.line_to((-2., number_of_rows as f64 * GRID_SIZE + 2.));
for row in (1..number_of_rows).rev() {
border_mask_path.line_to((-2., row as f64 * GRID_SIZE + 20.));
if frontend_node.inputs.iter().skip(1).filter(|input| input.is_some()).nth(row - 1).is_some_and(|input| input.is_some()) {
border_mask_path.line_to((2., row as f64 * GRID_SIZE + 20.));
border_mask_path.line_to((2., row as f64 * GRID_SIZE + 4.));
border_mask_path.line_to((2., row as f64 * GRID_SIZE + 4.));
}
}
if frontend_node.inputs[0].is_some() {
border_mask_path.line_to((-2., 20.));
border_mask_path.line_to((2., 20.));
border_mask_path.line_to((2., 4.));
border_mask_path.line_to((-2., 4.));
}
border_mask_path.line_to((-2., -2.));
border_mask_path.close_path();
let mut border_mask_vector = Vector::from_bezpath(border_mask_path);
border_mask_vector.style.fill = Fill::Solid(Color::WHITE);
let mut border_mask_row = TableRow::new_from_element(border_mask_vector);
border_mask_row.alpha_blending.fill = 0.;
border_mask_row.transform = DAffine2::from_translation(DVec2::new(x, y));
let border_mask_table = Table::new_from_row(border_mask_row);
node_table.push(TableRow::new_from_element(Graphic::Vector(border_mask_table)));
// Border table is implemented as a clip mask
let mut border_table = Table::new();
let mut border_vector = Vector::from_bezpath(node_bez_path);
let primary_output_color = frontend_node.outputs[0]
@@ -63,35 +111,74 @@ pub fn draw_nodes(nodes: &Vec<FrontendNodeToRender>) -> Table<Graphic> {
.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.));
border_table.push(TableRow::new_from_element(border_vector));
let mut border_vector_row = TableRow::new_from_element(border_vector);
border_vector_row.alpha_blending.clip = true;
border_table.push(border_vector_row);
node_table.push(TableRow::new_from_element(Graphic::Vector(border_table)));
// Border mask table
let mut border_mask_table = Table::new();
let mut border_masks = BezPath::new();
if frontend_node.inputs[0].is_some() {
let index = 0;
border_masks.extend(Rect::new(-8., index as f64 * GRID_SIZE + 4., 8., index as f64 * GRID_SIZE + 20.).to_path(BEZ_PATH_TOLERANCE));
let typesetting = TypesettingConfig {
font_size: 14.,
line_height_ratio: 1.2,
character_spacing: 0.0,
max_width: None,
max_height: None,
tilt: 0.0,
align: TextAlign::Left,
};
// Names for each row
let font_blob = Some(text::load_font(SOURCE_SANS_FONT_DATA));
let mut node_text = crate::text::to_path(&node_to_render.metadata.display_name, font_blob, typesetting, false);
for text_row in node_text.iter_mut() {
*text_row.transform = DAffine2::from_translation(DVec2::new(x + 8., y + 3.));
}
for index in 1..=frontend_node.inputs.iter().skip(1).filter(|input| input.is_some()).count() {
border_masks.extend(Rect::new(-8., index as f64 * GRID_SIZE + 4., 8., index as f64 * GRID_SIZE + 20.).to_path(BEZ_PATH_TOLERANCE));
for (row, input) in frontend_node.inputs.iter().enumerate().skip(1) {
if let Some(input) = input {
let font_blob = Some(text::load_font(SOURCE_SANS_FONT_DATA));
let mut input_row_text = crate::text::to_path(&input.name, font_blob, typesetting, false);
for text_row in input_row_text.iter_mut() {
*text_row.transform = DAffine2::from_translation(DVec2::new(x + 8., y + 24. * row as f64 + 3.));
}
node_text.extend(input_row_text);
} else if let Some(Some(output)) = frontend_node.outputs.get(row) {
let font_blob = Some(text::load_font(SOURCE_SANS_FONT_DATA));
let mut output_row_text = crate::text::to_path(&output.name, font_blob, typesetting, false);
// Find width to right align text
let full_text_width = if let RenderBoundingBox::Rectangle(bbox) = output_row_text.bounding_box(DAffine2::default(), true) {
bbox[1].x - bbox[0].x
} else {
0.
};
// Account for clipping
let text_width = full_text_width.min(5. * GRID_SIZE - 16.);
let left_offset = 5. * GRID_SIZE - 8. - text_width;
for text_row in output_row_text.iter_mut() {
*text_row.transform = DAffine2::from_translation(DVec2::new(x + 8. + left_offset, y + 24. * row as f64 + 3.));
}
node_text.extend(output_row_text);
}
}
if frontend_node.outputs[0].is_some() {
let index = 0;
border_masks.extend(Rect::new(-8. + 5. * GRID_SIZE, index as f64 * GRID_SIZE + 4., 8. + 5. * GRID_SIZE, index as f64 * GRID_SIZE + 20.).to_path(BEZ_PATH_TOLERANCE));
}
for index in 1..=frontend_node.outputs.iter().skip(1).filter(|output| output.is_some()).count() {
border_masks.extend(Rect::new(-8. + 5. * GRID_SIZE, index as f64 * GRID_SIZE + 4., 8. + 5. * GRID_SIZE, index as f64 * GRID_SIZE + 20.).to_path(BEZ_PATH_TOLERANCE));
}
let mut border_masks_vector = Vector::from_bezpath(border_masks);
border_masks_vector.style.fill = Fill::Solid(node_color);
let mut border_masks_row = TableRow::new_from_element(border_masks_vector);
border_masks_row.alpha_blending.clip = true;
border_masks_row.transform.left_apply_transform(&DAffine2::from_translation(DVec2::new(x, y)));
border_mask_table.push(border_masks_row);
node_table.push(TableRow::new_from_element(Graphic::Vector(border_mask_table)));
// for text_row in node_text.iter_mut() {
// text_row.element.style.fill = Fill::Solid(Color::WHITE);
// }
let node_text_row = TableRow::new_from_element(Graphic::Vector(node_text));
// node_text_row.transform.left_apply_transform(&DAffine2::from_translation(DVec2::new(x + 8., y + 8.)));
// log::debug!("node_text_row {:?}", node_text_row.transform);
node_table.push(node_text_row);
// Add black clipping path to view text in node
let text_area = Rect::new(x + 8., y, x + node_width - 8., y + node_height);
let mut text_area_vector = Vector::from_bezpath(text_area.to_path(BEZ_PATH_TOLERANCE));
text_area_vector.style.fill = Fill::Solid(Color::WHITE);
let mut text_area_row = TableRow::new_from_element(text_area_vector);
text_area_row.alpha_blending.clip = true;
let text_area_table = Table::new_from_row(text_area_row);
node_table.push(TableRow::new_from_element(Graphic::Vector(text_area_table)));
}
}
node_table
}
@@ -162,35 +249,55 @@ pub fn draw_layers(nodes: &Vec<FrontendNodeToRender>) -> Table<Graphic> {
background_table.push(TableRow::new_from_element(bg_vector));
layer_table.push(TableRow::new_from_element(Graphic::Vector(background_table)));
// Border
// Border mask is a transparent region for where to draw the border
let mut border_mask_table = Table::new();
let mut border_mask = BezPath::new();
border_mask.move_to((-2., -2.));
border_mask.line_to((chain_width - 8., -2.));
border_mask.line_to((chain_width - 8., 2.));
border_mask.line_to((chain_width + GRID_SIZE * 3. + 8., 2.));
border_mask.line_to((chain_width + GRID_SIZE * 3. + 8., -2.));
border_mask.line_to((full_layer_width + 2., -2.));
border_mask.line_to((full_layer_width + 2., 12.));
border_mask.line_to((full_layer_width - 2., 12.));
border_mask.line_to((full_layer_width - 2., 36.));
border_mask.line_to((full_layer_width + 2., 36.));
border_mask.line_to((full_layer_width + 2., 50.));
border_mask.line_to((full_layer_width + 2., 50.));
border_mask.line_to((chain_width + GRID_SIZE * 3. + 8., 50.));
border_mask.line_to((chain_width + GRID_SIZE * 3. + 8., 46.));
border_mask.line_to((chain_width - 8., 46.));
border_mask.line_to((chain_width - 8., 50.));
border_mask.line_to((-2., 50.));
border_mask.line_to((-2., 32.));
if frontend_layer.layer_has_left_border_gap && chain_width > 0.1 {
border_mask.line_to((2., 32.));
border_mask.line_to((2., 16.));
}
border_mask.line_to((-2., 16.));
border_mask.line_to((-2., -2.));
border_mask.close_path();
let mut border_mask_vector = Vector::from_bezpath(border_mask);
border_mask_vector.style.fill = Fill::Solid(Color::WHITE);
let mut border_mask_row = TableRow::new_from_element(border_mask_vector);
border_mask_row.alpha_blending.fill = 0.;
border_mask_row.transform.left_apply_transform(&DAffine2::from_translation(DVec2::new(x0, y0)));
border_mask_table.push(border_mask_row);
layer_table.push(TableRow::new_from_element(Graphic::Vector(border_mask_table)));
// Border is implemented as a mask
let mut border_table = Table::new();
let border_rect = RoundedRect::new(x0, y0, x0 + full_layer_width, y0 + h, 8.);
let bez_path = border_rect.to_path(BEZ_PATH_TOLERANCE);
let mut border_vector = Vector::from_bezpath(bez_path);
let border_color = Color::from_rgba8_no_srgb(COLOR_5_DULLGRAY).unwrap();
border_vector.style.stroke = Some(crate::vector::style::Stroke::new(Some(border_color), 1.));
border_table.push(TableRow::new_from_element(border_vector));
let mut layer_border_clip = TableRow::new_from_element(border_vector);
layer_border_clip.alpha_blending.clip = true;
border_table.push(layer_border_clip);
layer_table.push(TableRow::new_from_element(Graphic::Vector(border_table)));
// Border mask
let mut border_mask_table = Table::new();
let mut border_mask = BezPath::new();
if frontend_layer.layer_has_left_border_gap && chain_width > 0.1 {
let left_input_mask = Rect::new(-8., 16., 8., 32.);
border_mask.extend(left_input_mask.to_path(BEZ_PATH_TOLERANCE));
}
let thumbnail_mask = Rect::new(chain_width - 8., -2., chain_width + 72. + 8. * 2., 2. * GRID_SIZE + 2.);
border_mask.extend(thumbnail_mask.to_path(BEZ_PATH_TOLERANCE));
let right_visibility_mask = Rect::new(full_layer_width - 12., 12., full_layer_width + 12., 36.);
border_mask.extend(right_visibility_mask.to_path(BEZ_PATH_TOLERANCE));
let mut border_mask_vector = Vector::from_bezpath(border_mask);
border_mask_vector.style.fill = Fill::Solid(background);
let mut border_mask_row = TableRow::new_from_element(border_mask_vector);
border_mask_row.alpha_blending.clip = true;
border_mask_row.transform.left_apply_transform(&DAffine2::from_translation(DVec2::new(x0, y0)));
border_mask_table.push(border_mask_row);
layer_table.push(TableRow::new_from_element(Graphic::Vector(border_mask_table)));
// The top layer contains the ports,thumbnail,text, etc
for text_row in text_table.iter_mut() {
text_row.element.style.fill = Fill::Solid(Color::WHITE);
@@ -245,41 +352,21 @@ fn node_first_row(x0: f64, y0: f64, rounded_bottom: bool) -> Vector {
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));
fn port() -> BezPath {
BezPath::from_svg("M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z").unwrap_or_else(|e| {
panic!("Could not parse port svg from string: {}", e);
})
}
// // 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
// }
pub fn draw_wires(nodes: &mut Vec<FrontendNodeToRender>) -> Table<Graphic> {
let mut wire_table = Table::new();
for node in nodes {
for (wire_string, thick, data_type) in &mut node.wires {
let mut wire_vector = Vector::from_bezpath(std::mem::take(wire_string));
let weight = if *thick { 8. } else { 2. };
wire_vector.style.set_stroke(Stroke::new(Some(Color::from_rgba8_no_srgb(data_type.data_color_dim()).unwrap()), weight));
wire_table.push(TableRow::new_from_element(wire_vector));
}
}
Table::new_from_element(Graphic::Vector(wire_table))
}

View File

@@ -1,4 +1,5 @@
use glam::{DAffine2, DVec2};
use kurbo::BezPath;
use crate::{node_graph_overlay::consts::*, uuid::NodeId};
use std::hash::{Hash, Hasher};
@@ -10,21 +11,22 @@ pub struct NodeGraphTransform {
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) {
// Convert f64 to u64 bit pattern for hashing
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)]
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))
}
}
#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]
pub struct NodeGraphOverlayData {
pub nodes_to_render: Vec<FrontendNodeToRender>,
pub open: bool,
@@ -33,13 +35,20 @@ pub struct NodeGraphOverlayData {
pub previewed_node: Option<NodeId>,
}
#[derive(Clone, Debug, Default, PartialEq, Hash, dyn_any::DynAny, serde::Serialize, serde::Deserialize, specta::Type)]
#[derive(Clone, Debug, Default, PartialEq, dyn_any::DynAny, serde::Serialize, serde::Deserialize)]
pub struct FrontendNodeToRender {
pub metadata: FrontendNodeMetadata,
#[serde(rename = "nodeOrLayer")]
pub node_or_layer: FrontendNodeOrLayer,
//TODO: Remove
pub wires: Vec<(String, bool, FrontendGraphDataType)>,
pub wires: Vec<(BezPath, bool, FrontendGraphDataType)>,
}
impl Hash for FrontendNodeToRender {
fn hash<H: Hasher>(&self, state: &mut H) {
self.metadata.hash(state);
self.node_or_layer.hash(state);
}
}
// Metadata that is common to nodes and layers

View File

@@ -14,6 +14,20 @@ pub struct UIContextImpl {
pub response_sender: Sender<UIRuntimeResponse>,
}
use std::hash::{Hash, Hasher};
impl Hash for UIContextImpl {
fn hash<H: Hasher>(&self, state: &mut H) {
self.transform.hash(state);
self.resolution.hash(state);
}
}
impl PartialEq for UIContextImpl {
fn eq(&self, other: &Self) -> bool {
self.transform == other.transform && self.resolution == other.resolution
}
}
#[derive(Debug, Clone, dyn_any::DynAny)]
pub enum UIRuntimeResponse {
RuntimeReady,

View File

@@ -253,6 +253,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
node_io
},
),
async_node!(graphene_core::memo::MemoNode<_, _>, input: UIContext, fn_params: [UIContext => ()]),
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<Graphic>]),
async_node!(graphene_core::node_graph_overlay::DotGridBackgroundNode<_>, input: UIContext, fn_params: [UIContext =>f64]),