Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::transform_utils;
use crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type, resolve_network_node_type, resolve_proto_node_type};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::{self, FlowType, InputConnector, NodeNetworkInterface, OutputConnector};
use crate::messages::portfolio::document::utility_types::network_interface::{self, FlowType, InputConnector, NodeNetworkInterface};
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils::{get_fill_input_node_id, get_upstream_gradient_value_node_id, gradient_chain_target_input};
use glam::{DAffine2, DVec2, IVec2};
Expand Down Expand Up @@ -58,72 +58,6 @@ impl<'a> ModifyInputsContext<'a> {
Some(document)
}

/// Starts at any folder, or the output, and skips layer nodes based on insert_index. Non layer nodes are always skipped. Returns the post node InputConnector and pre node OutputConnector
/// Non layer nodes directly upstream of a layer are treated as part of that layer. See insert_index == 2 in the diagram
/// -----> Post node
/// | if insert_index == 0, return (Post node, Some(Layer1))
/// -> Layer1
/// ↑ if insert_index == 1, return (Layer1, Some(Layer2))
/// -> Layer2
/// ↑
/// -> NonLayerNode
/// ↑ if insert_index == 2, return (NonLayerNode, Some(Layer3))
/// -> Layer3
/// if insert_index == 3, return (Layer3, None)
pub fn get_post_node_with_index(network_interface: &NodeNetworkInterface, parent: LayerNodeIdentifier, insert_index: usize) -> InputConnector {
let mut post_node_input_connector = if parent == LayerNodeIdentifier::ROOT_PARENT {
InputConnector::Export(0)
} else {
InputConnector::node(parent.to_node(), 1)
};
// Skip layers based on skip_layer_nodes, which inserts the new layer at a certain index of the layer stack.
let mut current_index = 0;

// Set the post node to the layer node at insert_index
loop {
if current_index == insert_index {
break;
}
let next_node_in_stack_id = network_interface
.input_from_connector(&post_node_input_connector, &[])
.and_then(|input_from_connector| if let NodeInput::Node { node_id, .. } = input_from_connector { Some(node_id) } else { None });

if let Some(next_node_in_stack_id) = next_node_in_stack_id {
// Only increment index for layer nodes
if network_interface.is_layer(next_node_in_stack_id, &[]) {
current_index += 1;
}
// Input as a sibling to the Layer node above
post_node_input_connector = InputConnector::node(*next_node_in_stack_id, 0);
} else {
log::error!("Error getting post node: insert_index out of bounds");
break;
};
}

let layer_input_connector = post_node_input_connector;

// Sink post_node down to the end of the non layer chain that feeds into post_node, such that pre_node is the layer node at insert_index + 1, or None if insert_index is the last layer
loop {
let pre_node_output_connector = network_interface.upstream_output_connector(&post_node_input_connector, &[]);

match pre_node_output_connector {
Some(OutputConnector::Node { node_id: pre_node_id, .. }) if !network_interface.is_layer(&pre_node_id, &[]) => {
// Update post_node_input_connector for the next iteration
post_node_input_connector = InputConnector::node(pre_node_id, 0);
// Insert directly under layer if moving to the end of a layer stack that ends with a non layer node that does not have an exposed primary input
let primary_is_exposed = network_interface.input_from_connector(&post_node_input_connector, &[]).is_some_and(|input| input.is_exposed());
if !primary_is_exposed {
return layer_input_connector;
}
}
_ => break, // Break if pre_node_output_connector is None or if pre_node_id is a layer
}
}

post_node_input_connector
}

/// Creates a new layer and adds it to the document network. network_interface.move_layer_to_stack should be called after
pub fn create_layer(&mut self, new_id: NodeId) -> LayerNodeIdentifier {
let new_merge_node = resolve_network_node_type("Merge").expect("Merge node").default_node_template();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -530,19 +530,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG

network_interface.set_input(&encapsulating_connector, input, network_path);

let Some(outward_wires) = network_interface.outward_wires(breadcrumb_network_path) else {
log::error!("Could not get outward wires in remove_import");
return;
};
let Some(downstream_connections) = outward_wires.get(&OutputConnector::Import(0)).cloned() else {
log::error!("Could not get outward wires for import in remove_import");
return;
};

// Disconnect all connections in the encapsulating network
for downstream_connection in &downstream_connections {
network_interface.disconnect_input(downstream_connection, breadcrumb_network_path);
}
// Disconnect all connections fed by the hidden import in the nested network
network_interface.disconnect_import_wires(0, breadcrumb_network_path);

responses.add(NodeGraphMessage::UpdateImportsExports);
responses.add(NodeGraphMessage::SendWires);
Expand All @@ -565,18 +554,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG

// Disconnect all connections in the encapsulating network
if let Some((encapsulating_node, encapsulating_path)) = breadcrumb_network_path.split_last() {
let Some(outward_wires) = network_interface.outward_wires(encapsulating_path) else {
log::error!("Could not get outward wires in remove_import");
return;
};
let Some(downstream_connections) = outward_wires.get(&OutputConnector::node(*encapsulating_node, 0)).cloned() else {
log::error!("Could not get outward wires for import in remove_import");
return;
};

for downstream_connection in &downstream_connections {
network_interface.disconnect_input(downstream_connection, encapsulating_path);
}
network_interface.disconnect_output_wires(&OutputConnector::node(*encapsulating_node, 0), encapsulating_path);
}

responses.add(NodeGraphMessage::UpdateImportsExports);
Expand Down Expand Up @@ -1352,7 +1330,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
self.shift_without_push = false;

// Reset all offsets to end the rubber banding while dragging
network_interface.unload_stack_dependents_y_offset(selection_network_path);
network_interface.clear_drag_offsets(selection_network_path);
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
log::error!("Could not get selected nodes in PointerUp");
return;
Expand Down Expand Up @@ -1812,7 +1790,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
network_interface.shift_selected_nodes(direction, self.shift_without_push, selection_network_path);

if !rubber_band {
network_interface.unload_stack_dependents_y_offset(selection_network_path);
network_interface.clear_drag_offsets(selection_network_path);
}

if graph_view_overlay_open {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@ use super::utility_types::{DrawHandles, OverlayContext};
use crate::consts::HIDE_HANDLE_DISTANCE;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::portfolio::fonts::FALLBACK_FONT_RESOURCE;
pub use crate::messages::portfolio::document::utility_types::text_metrics::text_width;
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
use crate::messages::tool::tool_messages::tool_prelude::DocumentMessageHandler;
use glam::{DAffine2, DVec2};
use graphene_std::subpath::{Bezier, BezierHandles};
use graphene_std::text::{TextAlign, TextContext, TypesettingConfig};
use graphene_std::vector::misc::ManipulatorPointId;
use graphene_std::vector::{PointId, SegmentId, Vector};
use std::collections::HashMap;
use std::sync::{LazyLock, Mutex};
#[cfg(target_family = "wasm")]
use wasm_bindgen::JsCast;

Expand Down Expand Up @@ -222,24 +220,6 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
}
}

pub static GLOBAL_TEXT_CONTEXT: LazyLock<Mutex<TextContext>> = LazyLock::new(|| Mutex::new(TextContext::default()));

pub fn text_width(text: &str, font_size: f64) -> f64 {
let typesetting = TypesettingConfig {
font_size,
line_height_ratio: 1.2,
letter_spacing: 0.,
letter_tilt: 0.,
max_width: None,
max_height: None,
align: TextAlign::AlignLeft,
};

let mut text_context = GLOBAL_TEXT_CONTEXT.lock().expect("Failed to lock global text context");
let bounds = text_context.bounding_box(text, &FALLBACK_FONT_RESOURCE, typesetting, false);
bounds.x
}

pub fn hex_to_rgba_u8(hex: &str) -> [u8; 4] {
let hex = hex.trim().trim_start_matches('#');
if hex.len() != 6 && hex.len() != 8 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::consts::{
COLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS,
GRADIENT_MIDPOINT_DIAMOND_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, RESIZE_HANDLE_SIZE, SKEW_TRIANGLE_OFFSET, SKEW_TRIANGLE_SIZE,
};
use crate::messages::portfolio::document::overlays::utility_functions::{GLOBAL_TEXT_CONTEXT, hex_to_rgba_u8};
use crate::messages::portfolio::document::overlays::utility_functions::hex_to_rgba_u8;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::fonts::FALLBACK_FONT_RESOURCE;
use crate::messages::prelude::Message;
Expand All @@ -16,7 +16,7 @@ use graphene_std::ATTR_TRANSFORM;
use graphene_std::list::List;
use graphene_std::math::quad::Quad;
use graphene_std::subpath::{self, Subpath};
use graphene_std::text::{TextAlign, TypesettingConfig};
use graphene_std::text::{TextAlign, TextContext, TypesettingConfig};
use graphene_std::vector::click_target::ClickTargetType;
use graphene_std::vector::misc::point_to_dvec2;
use graphene_std::vector::{PointId, SegmentId, Vector};
Expand Down Expand Up @@ -1117,17 +1117,17 @@ impl OverlayContextInternal {
align: TextAlign::AlignLeft,
};

// Get text dimensions directly from layout
let mut text_context = GLOBAL_TEXT_CONTEXT.lock().expect("Failed to lock global text context");
let text_size = text_context.bounding_box(text, &FALLBACK_FONT_RESOURCE, typesetting, false);
// Lay out the text once, taking its dimensions and vector paths from the same thread-local context pass
let (text_size, text_list) = TextContext::with_thread_local(|text_context| {
let text_size = text_context.bounding_box(text, &FALLBACK_FONT_RESOURCE, typesetting, false);
let text_list = text_context.to_path(text, &FALLBACK_FONT_RESOURCE, typesetting, false);
(text_size, text_list)
});
let text_width = text_size.x;
let text_height = text_size.y;
// Create a rect from the size (assuming text starts at origin)
let text_bounds = kurbo::Rect::new(0., 0., text_width, text_height);

// Convert text to vector paths for rendering
let text_list = text_context.to_path(text, &FALLBACK_FONT_RESOURCE, typesetting, false);

// Calculate position based on pivot
let mut position = DVec2::ZERO;
match pivot[0] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ pub mod error;
pub mod misc;
pub mod network_interface;
pub mod nodes;
pub mod text_metrics;
pub mod transformation;
pub mod wires;
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,10 @@ use crate::consts::{
EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP, EXPORTS_TO_TOP_EDGE_PIXEL_GAP, GRID_SIZE, HALF_GRID_SIZE, IMPORTS_TO_LEFT_EDGE_PIXEL_GAP, IMPORTS_TO_TOP_EDGE_PIXEL_GAP, LAYER_INDENT_OFFSET, NODE_CHAIN_WIDTH,
STACK_VERTICAL_GAP,
};
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::node_graph::document_node_definitions::{DefinitionIdentifier, resolve_document_node_type};
use crate::messages::portfolio::document::node_graph::utility_types::{Direction, FrontendClickTargets, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput};
use crate::messages::portfolio::document::overlays::utility_functions::text_width;
use crate::messages::portfolio::document::utility_types::network_interface::resolved_types::ResolvedDocumentNodeTypes;
use crate::messages::portfolio::document::utility_types::text_metrics::text_width;
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_thick_wire_center_line, build_vector_wire};
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::tool_messages::tool_prelude::NumberInputMode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,12 @@ impl NodeNetworkInterface {
log::error!("Could not get selected nodes in load_stack_dependents");
return;
};
self.load_stack_dependents_for_nodes(selected_nodes.selected_nodes().cloned().collect(), network_path);
}

let mut selected_layers = selected_nodes.selected_nodes().filter(|node_id| self.is_layer(node_id, network_path)).cloned().collect::<HashSet<_>>();
/// Builds the stack dependents as if `seed_nodes` were the selection, for shifts driven by a node set other than the selection.
pub(crate) fn load_stack_dependents_for_nodes(&self, seed_nodes: Vec<NodeId>, network_path: &[NodeId]) {
let mut selected_layers = seed_nodes.iter().filter(|node_id| self.is_layer(node_id, network_path)).copied().collect::<HashSet<_>>();

// Deselect all layers that are upstream of other selected layers
let mut removed_layers = Vec::new();
Expand Down Expand Up @@ -222,7 +226,7 @@ impl NodeNetworkInterface {

for sole_dependent in sole_dependents {
if !owned_sole_dependents.contains(&sole_dependent) {
stack_dependents.insert(sole_dependent, LayerOwner::None(0));
stack_dependents.insert(sole_dependent, LayerOwner::None);
}
}
}
Expand All @@ -241,22 +245,32 @@ impl NodeNetworkInterface {
return;
};
network_metadata.transient_metadata.stack_dependents.unload();

// Drag offsets are only meaningful relative to the stack dependents snapshot they were accumulated against, so they must not outlive it
network_metadata.transient_metadata.drag_offsets.borrow_mut().clear();
}

/// Resets all the offsets for nodes with no LayerOwner when the drag ends
pub fn unload_stack_dependents_y_offset(&mut self, network_path: &[NodeId]) {
let Some(network_metadata) = self.network_metadata_mut(network_path) else {
log::error!("Could not get nested network_metadata in unload_stack_dependents_y_offset");
/// The vertical distance the node has been pushed from its resting position during the current drag.
pub(crate) fn drag_offset(&self, node_id: &NodeId, network_path: &[NodeId]) -> i32 {
self.network_metadata(network_path)
.map_or(0, |network_metadata| network_metadata.transient_metadata.drag_offsets.borrow().get(node_id).copied().unwrap_or(0))
}

pub(crate) fn add_drag_offset(&self, node_id: &NodeId, delta: i32, network_path: &[NodeId]) {
let Some(network_metadata) = self.network_metadata(network_path) else {
log::error!("Could not get nested network_metadata in add_drag_offset");
return;
};
*network_metadata.transient_metadata.drag_offsets.borrow_mut().entry(*node_id).or_insert(0) += delta;
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
}

if let Some(stack_dependents) = network_metadata.transient_metadata.stack_dependents.get_loaded_mut() {
for layer_owner in stack_dependents.values_mut() {
if let LayerOwner::None(offset) = layer_owner {
*offset = 0;
}
}
}
/// Discards all drag offsets when the drag ends.
pub fn clear_drag_offsets(&self, network_path: &[NodeId]) {
let Some(network_metadata) = self.network_metadata(network_path) else {
log::error!("Could not get nested network_metadata in clear_drag_offsets");
return;
};
network_metadata.transient_metadata.drag_offsets.borrow_mut().clear();
}

pub fn import_export_ports(&mut self, network_path: &[NodeId]) -> Option<&Ports> {
Expand Down Expand Up @@ -1081,7 +1095,7 @@ impl NodeNetworkInterface {
let name_left = node_top_left.x + NAME_LEFT_OFFSET;
let icons_reserve = VISIBILITY_INSET_FROM_LAYER_RIGHT + icons_width + GRIP_WIDTH;
let name_right_max = node_top_left.x + width as f64 - icons_reserve;
let text_w = crate::messages::portfolio::document::overlays::utility_functions::text_width(&display_name, FONT_SIZE);
let text_w = text_width(&display_name, FONT_SIZE);
let name_right = (name_left + text_w).min(name_right_max);
if name_right > name_left {
// The 1-grid-tall name strip is centered vertically in the 2-grid-tall layer.
Expand Down
Loading
Loading